import { Body, Button, Container, Head, Heading, Hr, Html, Img, Link, Preview, pixelBasedPreset, Section, Tailwind, Text } from "@react-email/components" import type { ReactNode } from "react" import { cn } from "../../../lib/utils" import { type EmailClassNames, type EmailColors, EmailStyles } from "./email-styles" const organizationInvitationEmailLocalization = { YOU_RE_INVITED_TO_ORGANIZATION: "You're invited to {organizationName}", YOU_RE_INVITED: "You're invited", LOGO: "Logo", ORGANIZATION_LOGO: "Organization logo", INVITED_TO_JOIN_ORGANIZATION: "{inviterName} ({inviterEmail}) has invited you to join {organizationName} on {appName} as a {role}.", ACCEPT_INVITATION: "Accept invitation", VIEW_INVITATION: "View invitation", OR_COPY_AND_PASTE_URL: "Or copy and paste this URL into your browser:", THIS_INVITATION_EXPIRES_IN_HOURS: "This invitation expires in {expirationHours} hours.", EMAIL_SENT_BY: "Email sent by {appName}.", IF_YOU_DIDNT_EXPECT_THIS_INVITATION: "If you didn't expect this invitation, you can safely ignore this email.", POWERED_BY_BETTER_AUTH: "Powered by {betterAuth}" } /** * Localization strings for the OrganizationInvitationEmail component. * * Contains all text content used in the organization invitation email template. */ export type OrganizationInvitationEmailLocalization = typeof organizationInvitationEmailLocalization /** * Props for the OrganizationInvitationEmail component. */ export interface OrganizationInvitationEmailProps { /** * URL where the invitee can review and accept the invitation. * * @remarks Pass `{baseUrl}/settings/organizations` — this is where pending * organization invitations are listed in the settings UI. */ url: string /** Email address of the user being invited */ email?: string /** Name of the person who sent the invitation */ inviterName?: string /** Email address of the person who sent the invitation */ inviterEmail?: string /** Name of the organization the user is being invited to */ organizationName?: string /** Organization logo URL(s) - a single string or light/dark variants. */ organizationLogoURL?: string | { light: string; dark: string } /** Role being offered to the invitee (e.g. "member", "admin", "owner") */ role?: string /** Name of the application sending the email */ appName?: string /** Number of hours until the invitation expires */ expirationHours?: number /** Logo URL(s) - a single string or light/dark variants. If omitted, no logo is shown. */ logoURL?: string | { light: string; dark: string } /** Custom CSS class names for styling specific parts of the email */ classNames?: EmailClassNames /** Custom color scheme for light and dark modes */ colors?: EmailColors /** Whether to show the "Powered by better-auth" footer */ poweredBy?: boolean /** Whether to enable dark mode support */ darkMode?: boolean /** Additional React nodes to inject into the email head */ head?: ReactNode /** * Localization overrides for customizing email text * @remarks `OrganizationInvitationEmailLocalization` */ localization?: Partial } /** * Email template component that invites a user to join an organization. * * This email includes: * - Inviter and organization details * - Role being offered * - Accept invitation button linking to `{baseUrl}/settings/organizations` * - Fallback URL for manual copy/paste * - Optional expiration time information * - Security notice for unexpected invitations * - Customizable branding and styling * - Support for light/dark mode themes * * @example * ```tsx * * ``` */ export const OrganizationInvitationEmail = ({ url, email, inviterName, inviterEmail, organizationName, organizationLogoURL, role, appName, expirationHours = 48, logoURL, colors, classNames, darkMode = true, poweredBy, head, ...props }: OrganizationInvitationEmailProps) => { const localization = { ...OrganizationInvitationEmail.localization, ...props.localization } const previewText = organizationName ? localization.YOU_RE_INVITED_TO_ORGANIZATION.replace( "{organizationName}", organizationName ) : localization.YOU_RE_INVITED return ( {head} {previewText}
{logoURL && (typeof logoURL === "string" ? ( {appName ) : ( <> {appName {appName ))} {organizationName ? localization.YOU_RE_INVITED_TO_ORGANIZATION.replace( "{organizationName}", organizationName ) : localization.YOU_RE_INVITED} {organizationLogoURL && (typeof organizationLogoURL === "string" ? ( {organizationName ) : ( <> {organizationName {organizationName ))} {(() => { let text = localization.INVITED_TO_JOIN_ORGANIZATION.replace( "{appName}", appName || "" ) .replace("{organizationName}", organizationName || "") .replace("{role}", role || "") // If we have no inviter info, drop the parenthetical and name placeholders cleanly. if (!inviterName && !inviterEmail) { text = text .replace("{inviterName} ({inviterEmail})", "Someone") .replace("{inviterName}", "Someone") .replace("({inviterEmail})", "") } const [beforeInviterName, afterInviterName] = text.split("{inviterName}") const renderInviterEmail = (segment: string) => { const [beforeInviterEmail, afterInviterEmail] = segment.split("{inviterEmail}") if (!inviterEmail) { return segment .replace("({inviterEmail})", "") .replace("{inviterEmail}", "") .replace(/\s{2,}/g, " ") .replace(" .", ".") } return ( <> {beforeInviterEmail} {inviterEmail} {afterInviterEmail} ) } if (!inviterName) { return renderInviterEmail( text .replace("{inviterName}", "") .replace(/\s{2,}/g, " ") .replace(" .", ".") ) } return ( <> {beforeInviterName} {inviterName} {renderInviterEmail(afterInviterName ?? "")} ) })()}
{localization.OR_COPY_AND_PASTE_URL} {url}
{expirationHours || appName ? ( {expirationHours ? localization.THIS_INVITATION_EXPIRES_IN_HOURS.replace( "{expirationHours}", expirationHours.toString() ) : null} {appName && ( <> {expirationHours ? " " : ""} {localization.EMAIL_SENT_BY.replace("{appName}", appName)} )} ) : null} {localization.IF_YOU_DIDNT_EXPECT_THIS_INVITATION} {poweredBy && ( {(() => { const [beforeBetterAuth, afterBetterAuth] = localization.POWERED_BY_BETTER_AUTH.split("{betterAuth}") return ( <> {beforeBetterAuth} better-auth {afterBetterAuth} ) })()} )}
) } OrganizationInvitationEmail.localization = organizationInvitationEmailLocalization OrganizationInvitationEmail.PreviewProps = { url: "https://better-auth-ui.com/settings/organizations", email: "m@example.com", inviterName: "Jane Doe", inviterEmail: "jane@example.com", organizationName: "Acme Inc.", role: "member", appName: "Better Auth", expirationHours: 48, darkMode: true } as OrganizationInvitationEmailProps export default OrganizationInvitationEmail