This commit is contained in:
2026-08-17 00:53:51 +02:00
parent 716604eb69
commit c2429873b3
12 changed files with 94 additions and 116 deletions
+12 -28
View File
@@ -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) ClientOrLog(w, http.StatusBadRequest, "could not create invite", err, auth.ClientError)
return return
} }
companyName, _ := s.Auth.CompanyName(r.Context(), cid) // Invites are link-based: always return the one-time accept URL for the admin to share.
smtpOn := s.Mail != nil && s.Mail.Enabled() // Email delivery is not used for team invites (SMTP may still be used for password reset, etc.).
sendOK := false JSON(w, http.StatusCreated, map[string]any{
if s.Mail != nil { "id": inv.ID,
msg := mail.InviteMessage(s.Config.WebOrigin, inv.Email, token, companyName) "email": inv.Email,
if err := s.Mail.Send(msg); err == nil { "role": inv.Role,
sendOK = true "expires_at": inv.ExpiresAt,
} "mail_sent": false,
} "smtp_enabled": s.Mail != nil && s.Mail.Enabled(),
// noop/disabled mailers return nil from Send; only count real SMTP as delivered. "token": token,
mailSent, includeToken := inviteMailResult(smtpOn, sendOK) "accept_url": mail.AcceptInviteURL(s.Config.WebOrigin, token),
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
} }
func (s *Server) handleRemoveMember(w http.ResponseWriter, r *http.Request) { func (s *Server) handleRemoveMember(w http.ResponseWriter, r *http.Request) {
@@ -1,31 +1,20 @@
package httpapi 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() t.Parallel()
cases := []struct { url := mail.AcceptInviteURL("https://app.example.com/", "tok_abc")
name string want := "https://app.example.com/accept-invite?token=tok_abc"
smtpEnabled bool if url != want {
sendOK bool t.Fatalf("AcceptInviteURL = %q want %q", url, want)
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},
} }
for _, tc := range cases { if strings.Contains(url, "mailto:") {
t.Run(tc.name, func(t *testing.T) { t.Fatal("invite URL must not be mailto")
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)
}
})
} }
} }
+6 -6
View File
@@ -202,7 +202,7 @@ export const de: MessageDict = {
"settings.inviteUser": "Benutzer einladen", "settings.inviteUser": "Benutzer einladen",
"settings.teamAdminOnly": "Nur Unternehmens-Admins können Teammitglieder einladen, befördern, herabstufen oder entfernen.", "settings.teamAdminOnly": "Nur Unternehmens-Admins können Teammitglieder einladen, befördern, herabstufen oder entfernen.",
"settings.shareAcceptLink": "Annahmelink teilen", "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.acceptLinkLabel": "Einmaliger Einladungs-Annahmelink",
"settings.copyLink": "Link kopieren", "settings.copyLink": "Link kopieren",
"settings.linkCopied": "Annahmelink kopiert.", "settings.linkCopied": "Annahmelink kopiert.",
@@ -223,9 +223,9 @@ export const de: MessageDict = {
"settings.needOneAdmin": "Unternehmen benötigen mindestens einen Admin", "settings.needOneAdmin": "Unternehmen benötigen mindestens einen Admin",
"settings.expires": "Läuft ab am {date}", "settings.expires": "Läuft ab am {date}",
"settings.invalidEmail": "Geben Sie eine gültige E-Mail-Adresse ein.", "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.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.",
"settings.inviteSent": "Einladung an {email} als {role} gesendet. Die Person sollte die E-Mail öffnen und vor Ablauf annehmen.", "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.",
"settings.inviteFailed": "Einladung konnte nicht gesendet werden", "settings.inviteFailed": "Could not create invite",
"settings.revokeConfirm": "Diese Einladung widerrufen?", "settings.revokeConfirm": "Diese Einladung widerrufen?",
"settings.revoked": "Einladung widerrufen.", "settings.revoked": "Einladung widerrufen.",
"settings.revokeFailed": "Einladung konnte nicht widerrufen werden", "settings.revokeFailed": "Einladung konnte nicht widerrufen werden",
@@ -238,12 +238,12 @@ export const de: MessageDict = {
"settings.promote": "Befördern", "settings.promote": "Befördern",
"settings.demote": "Herabstufen", "settings.demote": "Herabstufen",
"settings.inviteTitle": "Teammitglied einladen", "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.inviteEmail": "E-Mail",
"settings.inviteEmailPlaceholder": "kollege@beispiel.com", "settings.inviteEmailPlaceholder": "kollege@beispiel.com",
"settings.inviteRole": "Rolle", "settings.inviteRole": "Rolle",
"settings.inviteRoleHint": "Mitglieder verwalten Produkte und Feeds. Admins können auch Teammitglieder einladen und Unternehmenseinstellungen ändern.", "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.companyHeading": "Unternehmenseinstellungen",
"settings.activeCompany": "Aktives Unternehmen", "settings.activeCompany": "Aktives Unternehmen",
"settings.creditsOverview": "Credit-Übersicht", "settings.creditsOverview": "Credit-Übersicht",
+7 -7
View File
@@ -222,7 +222,7 @@ export const en: MessageDict = {
"settings.inviteUser": "Invite user", "settings.inviteUser": "Invite user",
"settings.teamAdminOnly": "Only company admins can invite, promote, demote, or remove teammates.", "settings.teamAdminOnly": "Only company admins can invite, promote, demote, or remove teammates.",
"settings.shareAcceptLink": "Share accept link", "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.acceptLinkLabel": "One-time accept invite link",
"settings.copyLink": "Copy link", "settings.copyLink": "Copy link",
"settings.linkCopied": "Accept link copied.", "settings.linkCopied": "Accept link copied.",
@@ -243,9 +243,9 @@ export const en: MessageDict = {
"settings.needOneAdmin": "Companies need at least one admin", "settings.needOneAdmin": "Companies need at least one admin",
"settings.expires": "Expires {date}", "settings.expires": "Expires {date}",
"settings.invalidEmail": "Enter a valid email address.", "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.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.",
"settings.inviteSent": "Invite sent to {email} as {role}. They should open the email and accept before it expires.", "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.",
"settings.inviteFailed": "Could not send invite", "settings.inviteFailed": "Could not create invite",
"settings.revokeConfirm": "Revoke this invitation?", "settings.revokeConfirm": "Revoke this invitation?",
"settings.revoked": "Invitation revoked.", "settings.revoked": "Invitation revoked.",
"settings.revokeFailed": "Could not revoke invite", "settings.revokeFailed": "Could not revoke invite",
@@ -258,12 +258,12 @@ export const en: MessageDict = {
"settings.promote": "Promote", "settings.promote": "Promote",
"settings.demote": "Demote", "settings.demote": "Demote",
"settings.inviteTitle": "Invite teammate", "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.inviteEmail": "Email",
"settings.inviteEmailPlaceholder": "colleague@example.com", "settings.inviteEmailPlaceholder": "colleague@example.com",
"settings.inviteRole": "Role", "settings.inviteRole": "Role",
"settings.inviteRoleHint": "Members manage products and feeds. Admins can also invite teammates and change company settings.", "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.companyHeading": "Company Settings",
"settings.activeCompany": "Active company", "settings.activeCompany": "Active company",
"settings.creditsOverview": "Credits overview", "settings.creditsOverview": "Credits overview",
@@ -2095,7 +2095,7 @@ export const en: MessageDict = {
"settings.companyNameHelp": "This name appears in invitations and organization settings", "settings.companyNameHelp": "This name appears in invitations and organization settings",
"settings.contentSettingsHelp": "Configure language and product matching for generated content", "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.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.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.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", "settings.openAiIntegrations": "Open AI integrations",
+6 -6
View File
@@ -202,7 +202,7 @@ export const es: MessageDict = {
"settings.inviteUser": "Invitar usuario", "settings.inviteUser": "Invitar usuario",
"settings.teamAdminOnly": "Solo los administradores de la empresa pueden invitar, ascender, degradar o eliminar compañeros.", "settings.teamAdminOnly": "Solo los administradores de la empresa pueden invitar, ascender, degradar o eliminar compañeros.",
"settings.shareAcceptLink": "Compartir enlace de aceptación", "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.acceptLinkLabel": "Enlace de aceptación de invitación de un solo uso",
"settings.copyLink": "Copiar enlace", "settings.copyLink": "Copiar enlace",
"settings.linkCopied": "Enlace de aceptación copiado.", "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.needOneAdmin": "Las empresas necesitan al menos un administrador",
"settings.expires": "Caduca el {date}", "settings.expires": "Caduca el {date}",
"settings.invalidEmail": "Introduce una dirección de correo válida.", "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.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.",
"settings.inviteSent": "Invitación enviada a {email} como {role}. Debe abrir el correo y aceptar antes de que caduque.", "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.",
"settings.inviteFailed": "No se pudo enviar la invitación", "settings.inviteFailed": "Could not create invite",
"settings.revokeConfirm": "¿Revocar esta invitación?", "settings.revokeConfirm": "¿Revocar esta invitación?",
"settings.revoked": "Invitación revocada.", "settings.revoked": "Invitación revocada.",
"settings.revokeFailed": "No se pudo revocar la invitación", "settings.revokeFailed": "No se pudo revocar la invitación",
@@ -238,12 +238,12 @@ export const es: MessageDict = {
"settings.promote": "Ascender", "settings.promote": "Ascender",
"settings.demote": "Degradar", "settings.demote": "Degradar",
"settings.inviteTitle": "Invitar compañero", "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.inviteEmail": "Correo electrónico",
"settings.inviteEmailPlaceholder": "colega@ejemplo.com", "settings.inviteEmailPlaceholder": "colega@ejemplo.com",
"settings.inviteRole": "Rol", "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.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.companyHeading": "Configuración de la empresa",
"settings.activeCompany": "Empresa activa", "settings.activeCompany": "Empresa activa",
"settings.creditsOverview": "Resumen de créditos", "settings.creditsOverview": "Resumen de créditos",
+6 -6
View File
@@ -202,7 +202,7 @@ export const fr: MessageDict = {
"settings.inviteUser": "Inviter un utilisateur", "settings.inviteUser": "Inviter un utilisateur",
"settings.teamAdminOnly": "Seuls les administrateurs de l'entreprise peuvent inviter, promouvoir, rétrograder ou retirer des coéquipiers.", "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.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.acceptLinkLabel": "Lien d'acceptation d'invitation à usage unique",
"settings.copyLink": "Copier le lien", "settings.copyLink": "Copier le lien",
"settings.linkCopied": "Lien d'acceptation copié.", "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.needOneAdmin": "Les entreprises ont besoin d'au moins un administrateur",
"settings.expires": "Expire le {date}", "settings.expires": "Expire le {date}",
"settings.invalidEmail": "Saisissez une adresse e-mail valide.", "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.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.",
"settings.inviteSent": "Invitation envoyée à {email} en tant que {role}. La personne doit ouvrir l'e-mail et accepter avant expiration.", "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.",
"settings.inviteFailed": "Impossible d'envoyer l'invitation", "settings.inviteFailed": "Could not create invite",
"settings.revokeConfirm": "Révoquer cette invitation ?", "settings.revokeConfirm": "Révoquer cette invitation ?",
"settings.revoked": "Invitation révoquée.", "settings.revoked": "Invitation révoquée.",
"settings.revokeFailed": "Impossible de révoquer l'invitation", "settings.revokeFailed": "Impossible de révoquer l'invitation",
@@ -238,12 +238,12 @@ export const fr: MessageDict = {
"settings.promote": "Promouvoir", "settings.promote": "Promouvoir",
"settings.demote": "Rétrograder", "settings.demote": "Rétrograder",
"settings.inviteTitle": "Inviter un coéquipier", "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.inviteEmail": "E-mail",
"settings.inviteEmailPlaceholder": "collegue@exemple.com", "settings.inviteEmailPlaceholder": "collegue@exemple.com",
"settings.inviteRole": "Rôle", "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.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.companyHeading": "Paramètres de l'entreprise",
"settings.activeCompany": "Entreprise active", "settings.activeCompany": "Entreprise active",
"settings.creditsOverview": "Aperçu des crédits", "settings.creditsOverview": "Aperçu des crédits",
+6 -6
View File
@@ -202,7 +202,7 @@ export const it: MessageDict = {
"settings.inviteUser": "Invita utente", "settings.inviteUser": "Invita utente",
"settings.teamAdminOnly": "Solo gli amministratori dell'azienda possono invitare, promuovere, degradare o rimuovere compagni di team.", "settings.teamAdminOnly": "Solo gli amministratori dell'azienda possono invitare, promuovere, degradare o rimuovere compagni di team.",
"settings.shareAcceptLink": "Condividi link di accettazione", "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.acceptLinkLabel": "Link monouso di accettazione invito",
"settings.copyLink": "Copia link", "settings.copyLink": "Copia link",
"settings.linkCopied": "Link di accettazione copiato.", "settings.linkCopied": "Link di accettazione copiato.",
@@ -223,9 +223,9 @@ export const it: MessageDict = {
"settings.needOneAdmin": "Le aziende necessitano di almeno un amministratore", "settings.needOneAdmin": "Le aziende necessitano di almeno un amministratore",
"settings.expires": "Scade il {date}", "settings.expires": "Scade il {date}",
"settings.invalidEmail": "Inserisci un indirizzo email valido.", "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.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.",
"settings.inviteSent": "Invito inviato a {email} come {role}. Deve aprire l'email e accettare prima della scadenza.", "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.",
"settings.inviteFailed": "Impossibile inviare l'invito", "settings.inviteFailed": "Could not create invite",
"settings.revokeConfirm": "Revocare questo invito?", "settings.revokeConfirm": "Revocare questo invito?",
"settings.revoked": "Invito revocato.", "settings.revoked": "Invito revocato.",
"settings.revokeFailed": "Impossibile revocare l'invito", "settings.revokeFailed": "Impossibile revocare l'invito",
@@ -238,12 +238,12 @@ export const it: MessageDict = {
"settings.promote": "Promuovi", "settings.promote": "Promuovi",
"settings.demote": "Degrada", "settings.demote": "Degrada",
"settings.inviteTitle": "Invita un collega", "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.inviteEmail": "Posta",
"settings.inviteEmailPlaceholder": "collega@esempio.com", "settings.inviteEmailPlaceholder": "collega@esempio.com",
"settings.inviteRole": "Ruolo", "settings.inviteRole": "Ruolo",
"settings.inviteRoleHint": "I membri gestiscono prodotti e feed. Gli amministratori possono anche invitare colleghi e modificare le impostazioni dell'azienda.", "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.companyHeading": "Impostazioni azienda",
"settings.activeCompany": "Azienda attiva", "settings.activeCompany": "Azienda attiva",
"settings.creditsOverview": "Panoramica crediti", "settings.creditsOverview": "Panoramica crediti",
+6 -6
View File
@@ -202,7 +202,7 @@ export const ja: MessageDict = {
"settings.inviteUser": "ユーザーを招待", "settings.inviteUser": "ユーザーを招待",
"settings.teamAdminOnly": "同僚の招待、昇格、降格、削除ができるのは会社の管理者のみです。", "settings.teamAdminOnly": "同僚の招待、昇格、降格、削除ができるのは会社の管理者のみです。",
"settings.shareAcceptLink": "承認リンクを共有", "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.acceptLinkLabel": "一回限りの招待承認リンク",
"settings.copyLink": "リンクをコピー", "settings.copyLink": "リンクをコピー",
"settings.linkCopied": "承認リンクをコピーしました。", "settings.linkCopied": "承認リンクをコピーしました。",
@@ -223,9 +223,9 @@ export const ja: MessageDict = {
"settings.needOneAdmin": "会社には少なくとも1人の管理者が必要です", "settings.needOneAdmin": "会社には少なくとも1人の管理者が必要です",
"settings.expires": "{date} に期限切れ", "settings.expires": "{date} に期限切れ",
"settings.invalidEmail": "有効なメールアドレスを入力してください。", "settings.invalidEmail": "有効なメールアドレスを入力してください。",
"settings.inviteCreatedNoMail": "{email} を {role} として招待を作成しました。下の承認リンクをコピーして共有してください — 送信メールは設定されていません。", "settings.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.",
"settings.inviteSent": "{email} に {role} として招待を送信しました。期限前にメールを開いて承認してください。", "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.",
"settings.inviteFailed": "招待を送信できませんでした", "settings.inviteFailed": "Could not create invite",
"settings.revokeConfirm": "この招待を取り消しますか?", "settings.revokeConfirm": "この招待を取り消しますか?",
"settings.revoked": "招待を取り消しました。", "settings.revoked": "招待を取り消しました。",
"settings.revokeFailed": "招待を取り消せませんでした", "settings.revokeFailed": "招待を取り消せませんでした",
@@ -238,12 +238,12 @@ export const ja: MessageDict = {
"settings.promote": "昇格", "settings.promote": "昇格",
"settings.demote": "降格", "settings.demote": "降格",
"settings.inviteTitle": "チームメイトを招待", "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.inviteEmail": "メール",
"settings.inviteEmailPlaceholder": "taro@example.com", "settings.inviteEmailPlaceholder": "taro@example.com",
"settings.inviteRole": "ロール", "settings.inviteRole": "ロール",
"settings.inviteRoleHint": "メンバーは商品とフィードを管理します。管理者は同僚の招待と会社設定の変更もできます。", "settings.inviteRoleHint": "メンバーは商品とフィードを管理します。管理者は同僚の招待と会社設定の変更もできます。",
"settings.sendInvite": "招待を送信", "settings.sendInvite": "Create invite link",
"settings.companyHeading": "会社の設定", "settings.companyHeading": "会社の設定",
"settings.activeCompany": "アクティブな会社", "settings.activeCompany": "アクティブな会社",
"settings.creditsOverview": "クレジット概要", "settings.creditsOverview": "クレジット概要",
+6 -6
View File
@@ -202,7 +202,7 @@ export const nl: MessageDict = {
"settings.inviteUser": "Gebruiker uitnodigen", "settings.inviteUser": "Gebruiker uitnodigen",
"settings.teamAdminOnly": "Alleen bedrijfsbeheerders kunnen teamleden uitnodigen, promoveren, degraderen of verwijderen.", "settings.teamAdminOnly": "Alleen bedrijfsbeheerders kunnen teamleden uitnodigen, promoveren, degraderen of verwijderen.",
"settings.shareAcceptLink": "Acceptatielink delen", "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.acceptLinkLabel": "Eenmalige acceptatie-uitnodigingslink",
"settings.copyLink": "Link kopiëren", "settings.copyLink": "Link kopiëren",
"settings.linkCopied": "Acceptatielink gekopieerd.", "settings.linkCopied": "Acceptatielink gekopieerd.",
@@ -223,9 +223,9 @@ export const nl: MessageDict = {
"settings.needOneAdmin": "Bedrijven hebben minstens één beheerder nodig", "settings.needOneAdmin": "Bedrijven hebben minstens één beheerder nodig",
"settings.expires": "Verloopt op {date}", "settings.expires": "Verloopt op {date}",
"settings.invalidEmail": "Voer een geldig e-mailadres in.", "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.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.",
"settings.inviteSent": "Uitnodiging verzonden naar {email} als {role}. Die moet de e-mail openen en accepteren vóór de vervaldatum.", "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.",
"settings.inviteFailed": "Uitnodiging kon niet worden verzonden", "settings.inviteFailed": "Could not create invite",
"settings.revokeConfirm": "Deze uitnodiging intrekken?", "settings.revokeConfirm": "Deze uitnodiging intrekken?",
"settings.revoked": "Uitnodiging ingetrokken.", "settings.revoked": "Uitnodiging ingetrokken.",
"settings.revokeFailed": "Uitnodiging kon niet worden ingetrokken", "settings.revokeFailed": "Uitnodiging kon niet worden ingetrokken",
@@ -238,12 +238,12 @@ export const nl: MessageDict = {
"settings.promote": "Promoveren", "settings.promote": "Promoveren",
"settings.demote": "Degraderen", "settings.demote": "Degraderen",
"settings.inviteTitle": "Teamlid uitnodigen", "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.inviteEmail": "E-mail",
"settings.inviteEmailPlaceholder": "collega@voorbeeld.com", "settings.inviteEmailPlaceholder": "collega@voorbeeld.com",
"settings.inviteRole": "Rol", "settings.inviteRole": "Rol",
"settings.inviteRoleHint": "Leden beheren producten en feeds. Beheerders kunnen ook teamleden uitnodigen en bedrijfsinstellingen wijzigen.", "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.companyHeading": "Bedrijfsinstellingen",
"settings.activeCompany": "Actief bedrijf", "settings.activeCompany": "Actief bedrijf",
"settings.creditsOverview": "Credits-overzicht", "settings.creditsOverview": "Credits-overzicht",
+6 -6
View File
@@ -202,7 +202,7 @@ export const pl: MessageDict = {
"settings.inviteUser": "Zaproś użytkownika", "settings.inviteUser": "Zaproś użytkownika",
"settings.teamAdminOnly": "Tylko administratorzy firmy mogą zapraszać, awansować, degradować lub usuwać członków zespołu.", "settings.teamAdminOnly": "Tylko administratorzy firmy mogą zapraszać, awansować, degradować lub usuwać członków zespołu.",
"settings.shareAcceptLink": "Udostępnij link akceptacji", "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.acceptLinkLabel": "Jednorazowy link akceptacji zaproszenia",
"settings.copyLink": "Kopiuj link", "settings.copyLink": "Kopiuj link",
"settings.linkCopied": "Skopiowano link akceptacji.", "settings.linkCopied": "Skopiowano link akceptacji.",
@@ -223,9 +223,9 @@ export const pl: MessageDict = {
"settings.needOneAdmin": "Firmy potrzebujÄ… co najmniej jednego administratora", "settings.needOneAdmin": "Firmy potrzebujÄ… co najmniej jednego administratora",
"settings.expires": "Wygasa {date}", "settings.expires": "Wygasa {date}",
"settings.invalidEmail": "Wprowadź prawidłowy adres e-mail.", "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.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.",
"settings.inviteSent": "Wysłano zaproszenie do {email} jako {role}. Osoba powinna otworzyć e-mail i zaakceptować przed wygaśnięciem.", "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.",
"settings.inviteFailed": "Nie można wysłać zaproszenia", "settings.inviteFailed": "Could not create invite",
"settings.revokeConfirm": "Unieważnić to zaproszenie?", "settings.revokeConfirm": "Unieważnić to zaproszenie?",
"settings.revoked": "Zaproszenie unieważnione.", "settings.revoked": "Zaproszenie unieważnione.",
"settings.revokeFailed": "Nie można unieważnić zaproszenia", "settings.revokeFailed": "Nie można unieważnić zaproszenia",
@@ -238,12 +238,12 @@ export const pl: MessageDict = {
"settings.promote": "Awansuj", "settings.promote": "Awansuj",
"settings.demote": "Degraduj", "settings.demote": "Degraduj",
"settings.inviteTitle": "Zaproś członka zespołu", "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.inviteEmail": "E-mail",
"settings.inviteEmailPlaceholder": "kolega@przyklad.com", "settings.inviteEmailPlaceholder": "kolega@przyklad.com",
"settings.inviteRole": "Rola", "settings.inviteRole": "Rola",
"settings.inviteRoleHint": "Członkowie zarządzają produktami i feedami. Administratorzy mogą też zapraszać współpracowników i zmieniać ustawienia firmy.", "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.companyHeading": "Ustawienia firmy",
"settings.activeCompany": "Aktywna firma", "settings.activeCompany": "Aktywna firma",
"settings.creditsOverview": "Przegląd kredytów", "settings.creditsOverview": "Przegląd kredytów",
+6 -6
View File
@@ -202,7 +202,7 @@ export const pt: MessageDict = {
"settings.inviteUser": "Convidar utilizador", "settings.inviteUser": "Convidar utilizador",
"settings.teamAdminOnly": "Apenas administradores da empresa podem convidar, promover, despromover ou remover colegas.", "settings.teamAdminOnly": "Apenas administradores da empresa podem convidar, promover, despromover ou remover colegas.",
"settings.shareAcceptLink": "Partilhar link de aceitação", "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.acceptLinkLabel": "Link de aceitação de convite de utilização única",
"settings.copyLink": "Copiar link", "settings.copyLink": "Copiar link",
"settings.linkCopied": "Link de aceitação copiado.", "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.needOneAdmin": "As empresas precisam de pelo menos um administrador",
"settings.expires": "Expira a {date}", "settings.expires": "Expira a {date}",
"settings.invalidEmail": "Introduza um endereço de e-mail válido.", "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.inviteCreatedNoMail": "Invite created for {email} as {role}. Copy the accept link below and share it with them.",
"settings.inviteSent": "Convite enviado para {email} como {role}. Deve abrir o e-mail e aceitar antes de expirar.", "settings.inviteSent": "Invite created for {email} as {role}. Share the accept link before it expires.",
"settings.inviteFailed": "Não foi possível enviar o convite", "settings.inviteFailed": "Could not create invite",
"settings.revokeConfirm": "Revogar este convite?", "settings.revokeConfirm": "Revogar este convite?",
"settings.revoked": "Convite revogado.", "settings.revoked": "Convite revogado.",
"settings.revokeFailed": "Não foi possível revogar o convite", "settings.revokeFailed": "Não foi possível revogar o convite",
@@ -238,12 +238,12 @@ export const pt: MessageDict = {
"settings.promote": "Promover", "settings.promote": "Promover",
"settings.demote": "Despromover", "settings.demote": "Despromover",
"settings.inviteTitle": "Convidar colega", "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.inviteEmail": "E-mail",
"settings.inviteEmailPlaceholder": "colega@exemplo.com", "settings.inviteEmailPlaceholder": "colega@exemplo.com",
"settings.inviteRole": "Papel", "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.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.companyHeading": "Definições da empresa",
"settings.activeCompany": "Empresa ativa", "settings.activeCompany": "Empresa ativa",
"settings.creditsOverview": "Resumo de créditos", "settings.creditsOverview": "Resumo de créditos",
+13 -8
View File
@@ -589,19 +589,24 @@
invites: [] as PendingInvite[] invites: [] as PendingInvite[]
})); }));
pendingInvites = invites.invites ?? []; pendingInvites = invites.invites ?? [];
const link =
created.accept_url?.trim() ||
(created.token
? `${window.location.origin}/accept-invite?token=${encodeURIComponent(created.token)}`
: "");
trackEvent("invite_sent", { trackEvent("invite_sent", {
role: roleForEvent, 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; inviteOpen = false;
success = i18n.t("settings.inviteCreatedNoMail", { email: emailed, role: roleName }); if (!link) {
} else { error = i18n.t("settings.inviteFailed");
inviteOpen = false; notifyError(error);
inviteAcceptLink = null; return;
success = i18n.t("settings.inviteSent", { email: emailed, role: roleName });
} }
inviteAcceptLink = link;
success = i18n.t("settings.inviteCreatedNoMail", { email: emailed, role: roleName });
notifySuccess(success); notifySuccess(success);
} catch (err) { } catch (err) {
error = applyApiFormError(err, i18n.t("settings.inviteFailed"), "invite teammates"); error = applyApiFormError(err, i18n.t("settings.inviteFailed"), "invite teammates");