{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "organization-invitation-email",
  "title": "Organization Invitation Email",
  "description": "Email template component that invites a user to join an organization.",
  "dependencies": [
    "@better-auth-ui/react@latest",
    "@better-auth-ui/core@latest",
    "react-email"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "../../packages/react/src/components/auth/email/organization-invitation.tsx",
      "content": "import type { ReactNode } from \"react\"\nimport {\n  Body,\n  Button,\n  Container,\n  Head,\n  Heading,\n  Hr,\n  Html,\n  Img,\n  Link,\n  Preview,\n  pixelBasedPreset,\n  Section,\n  Tailwind,\n  Text\n} from \"react-email\"\n\nimport { cn } from \"../../../lib/utils\"\nimport {\n  type EmailClassNames,\n  type EmailColors,\n  EmailStyles\n} from \"./email-styles\"\n\nconst organizationInvitationEmailLocalization = {\n  YOU_RE_INVITED_TO_ORGANIZATION: \"You're invited to {organizationName}\",\n  YOU_RE_INVITED: \"You're invited\",\n  LOGO: \"Logo\",\n  ORGANIZATION_LOGO: \"Organization logo\",\n  INVITED_TO_JOIN_ORGANIZATION:\n    \"{inviterName} ({inviterEmail}) has invited you to join {organizationName} on {appName} as a {role}.\",\n  ACCEPT_INVITATION: \"Accept invitation\",\n  VIEW_INVITATION: \"View invitation\",\n  OR_COPY_AND_PASTE_URL: \"Or copy and paste this URL into your browser:\",\n  THIS_INVITATION_EXPIRES_IN_HOURS:\n    \"This invitation expires in {expirationHours} hours.\",\n  EMAIL_SENT_BY: \"Email sent by {appName}.\",\n  IF_YOU_DIDNT_EXPECT_THIS_INVITATION:\n    \"If you didn't expect this invitation, you can safely ignore this email.\",\n  POWERED_BY_BETTER_AUTH: \"Powered by {betterAuth}\"\n}\n\n/**\n * Localization strings for the OrganizationInvitationEmail component.\n *\n * Contains all text content used in the organization invitation email template.\n */\nexport type OrganizationInvitationEmailLocalization =\n  typeof organizationInvitationEmailLocalization\n\n/**\n * Props for the OrganizationInvitationEmail component.\n */\nexport interface OrganizationInvitationEmailProps {\n  /**\n   * URL where the invitee can review and accept the invitation.\n   *\n   * @remarks Pass `{baseUrl}/settings/organizations` — this is where pending\n   * organization invitations are listed in the settings UI.\n   */\n  url: string\n  /** Email address of the user being invited */\n  email?: string\n  /** Name of the person who sent the invitation */\n  inviterName?: string\n  /** Email address of the person who sent the invitation */\n  inviterEmail?: string\n  /** Name of the organization the user is being invited to */\n  organizationName?: string\n  /** Organization logo URL(s) - a single string or light/dark variants. */\n  organizationLogoURL?: string | { light: string; dark: string }\n  /** Role being offered to the invitee (e.g. \"member\", \"admin\", \"owner\") */\n  role?: string\n  /** Name of the application sending the email */\n  appName?: string\n  /** Number of hours until the invitation expires */\n  expirationHours?: number\n  /** Logo URL(s) - a single string or light/dark variants. If omitted, no logo is shown. */\n  logoURL?: string | { light: string; dark: string }\n  /** Custom CSS class names for styling specific parts of the email */\n  classNames?: EmailClassNames\n  /** Custom color scheme for light and dark modes */\n  colors?: EmailColors\n  /** Whether to show the \"Powered by better-auth\" footer */\n  poweredBy?: boolean\n  /** Whether to enable dark mode support */\n  darkMode?: boolean\n  /** Additional React nodes to inject into the email head */\n  head?: ReactNode\n  /**\n   * Localization overrides for customizing email text\n   * @remarks `OrganizationInvitationEmailLocalization`\n   */\n  localization?: Partial<OrganizationInvitationEmailLocalization>\n}\n\n/**\n * Email template component that invites a user to join an organization.\n *\n * This email includes:\n * - Inviter and organization details\n * - Role being offered\n * - Accept invitation button linking to `{baseUrl}/settings/organizations`\n * - Fallback URL for manual copy/paste\n * - Optional expiration time information\n * - Security notice for unexpected invitations\n * - Customizable branding and styling\n * - Support for light/dark mode themes\n *\n * @example\n * ```tsx\n * <OrganizationInvitationEmail\n *   url=\"https://example.com/settings/organizations\"\n *   email=\"invitee@example.com\"\n *   inviterName=\"Jane Doe\"\n *   inviterEmail=\"jane@example.com\"\n *   organizationName=\"Acme Inc.\"\n *   organizationLogoURL=\"https://example.com/acme-logo.png\"\n *   role=\"member\"\n *   appName=\"My App\"\n *   expirationHours={48}\n *   darkMode={true}\n * />\n * ```\n */\nexport const OrganizationInvitationEmail = ({\n  url,\n  email,\n  inviterName,\n  inviterEmail,\n  organizationName,\n  organizationLogoURL,\n  role,\n  appName,\n  expirationHours = 48,\n  logoURL,\n  colors,\n  classNames,\n  darkMode = true,\n  poweredBy,\n  head,\n  ...props\n}: OrganizationInvitationEmailProps) => {\n  const localization = {\n    ...OrganizationInvitationEmail.localization,\n    ...props.localization\n  }\n\n  const previewText = organizationName\n    ? localization.YOU_RE_INVITED_TO_ORGANIZATION.replace(\n        \"{organizationName}\",\n        organizationName\n      )\n    : localization.YOU_RE_INVITED\n\n  return (\n    <Html>\n      <Head>\n        <meta content=\"light dark\" name=\"color-scheme\" />\n        <meta content=\"light dark\" name=\"supported-color-schemes\" />\n\n        <EmailStyles colors={colors} darkMode={darkMode} />\n\n        {head}\n      </Head>\n\n      <Preview>{previewText}</Preview>\n\n      <Tailwind config={{ presets: [pixelBasedPreset] }}>\n        <Body className={cn(\"bg-background font-sans\", classNames?.body)}>\n          <Container\n            className={cn(\n              \"mx-auto my-auto max-w-xl px-2 py-10\",\n              classNames?.container\n            )}\n          >\n            <Section\n              className={cn(\n                \"bg-card text-card-foreground rounded-none border border-border p-8\",\n                classNames?.card\n              )}\n            >\n              {logoURL &&\n                (typeof logoURL === \"string\" ? (\n                  <Img\n                    src={logoURL}\n                    width={48}\n                    height={48}\n                    alt={appName || localization.LOGO}\n                    className={cn(\"mx-auto mb-8\", classNames?.logo)}\n                  />\n                ) : (\n                  <>\n                    <Img\n                      src={logoURL.light}\n                      width={48}\n                      height={48}\n                      alt={appName || localization.LOGO}\n                      className={cn(\n                        \"mx-auto mb-8 logo-light\",\n                        classNames?.logo\n                      )}\n                    />\n                    <Img\n                      src={logoURL.dark}\n                      width={48}\n                      height={48}\n                      alt={appName || localization.LOGO}\n                      className={cn(\n                        \"hidden mx-auto mb-8 logo-dark\",\n                        classNames?.logo\n                      )}\n                    />\n                  </>\n                ))}\n\n              <Heading\n                className={cn(\n                  \"m-0 mb-5 text-2xl font-semibold\",\n                  classNames?.title\n                )}\n              >\n                {organizationName\n                  ? localization.YOU_RE_INVITED_TO_ORGANIZATION.replace(\n                      \"{organizationName}\",\n                      organizationName\n                    )\n                  : localization.YOU_RE_INVITED}\n              </Heading>\n\n              {organizationLogoURL &&\n                (typeof organizationLogoURL === \"string\" ? (\n                  <Img\n                    src={organizationLogoURL}\n                    width={56}\n                    height={56}\n                    alt={organizationName || localization.ORGANIZATION_LOGO}\n                    className={cn(\"mb-5 rounded-md\", classNames?.logo)}\n                  />\n                ) : (\n                  <>\n                    <Img\n                      src={organizationLogoURL.light}\n                      width={56}\n                      height={56}\n                      alt={organizationName || localization.ORGANIZATION_LOGO}\n                      className={cn(\n                        \"mb-5 rounded-md logo-light\",\n                        classNames?.logo\n                      )}\n                    />\n                    <Img\n                      src={organizationLogoURL.dark}\n                      width={56}\n                      height={56}\n                      alt={organizationName || localization.ORGANIZATION_LOGO}\n                      className={cn(\n                        \"hidden mb-5 rounded-md logo-dark\",\n                        classNames?.logo\n                      )}\n                    />\n                  </>\n                ))}\n\n              <Text\n                className={cn(\"m-0 text-sm font-normal\", classNames?.content)}\n              >\n                {(() => {\n                  let text = localization.INVITED_TO_JOIN_ORGANIZATION.replace(\n                    \"{appName}\",\n                    appName || \"\"\n                  )\n                    .replace(\"{organizationName}\", organizationName || \"\")\n                    .replace(\"{role}\", role || \"\")\n\n                  // If we have no inviter info, drop the parenthetical and name placeholders cleanly.\n                  if (!inviterName && !inviterEmail) {\n                    text = text\n                      .replace(\"{inviterName} ({inviterEmail})\", \"Someone\")\n                      .replace(\"{inviterName}\", \"Someone\")\n                      .replace(\"({inviterEmail})\", \"\")\n                  }\n\n                  const [beforeInviterName, afterInviterName] =\n                    text.split(\"{inviterName}\")\n\n                  const renderInviterEmail = (segment: string) => {\n                    const [beforeInviterEmail, afterInviterEmail] =\n                      segment.split(\"{inviterEmail}\")\n\n                    if (!inviterEmail) {\n                      return segment\n                        .replace(\"({inviterEmail})\", \"\")\n                        .replace(\"{inviterEmail}\", \"\")\n                        .replace(/\\s{2,}/g, \" \")\n                        .replace(\" .\", \".\")\n                    }\n\n                    return (\n                      <>\n                        {beforeInviterEmail}\n                        <Link\n                          href={`mailto:${inviterEmail}`}\n                          className=\"text-primary font-medium\"\n                        >\n                          {inviterEmail}\n                        </Link>\n                        {afterInviterEmail}\n                      </>\n                    )\n                  }\n\n                  if (!inviterName) {\n                    return renderInviterEmail(\n                      text\n                        .replace(\"{inviterName}\", \"\")\n                        .replace(/\\s{2,}/g, \" \")\n                        .replace(\" .\", \".\")\n                    )\n                  }\n\n                  return (\n                    <>\n                      {beforeInviterName}\n                      <span className=\"font-medium\">{inviterName}</span>\n                      {renderInviterEmail(afterInviterName ?? \"\")}\n                    </>\n                  )\n                })()}\n              </Text>\n\n              <Section className=\"my-6\">\n                <Button\n                  href={url}\n                  className={cn(\n                    \"inline-block whitespace-nowrap rounded-none text-sm font-medium py-2.5 px-6 bg-primary text-primary-foreground no-underline\",\n                    classNames?.button\n                  )}\n                >\n                  {localization.ACCEPT_INVITATION}\n                </Button>\n              </Section>\n\n              <Text\n                className={cn(\n                  \"m-0 mb-3 text-xs text-muted-foreground\",\n                  classNames?.description\n                )}\n              >\n                {localization.OR_COPY_AND_PASTE_URL}\n              </Text>\n\n              <Link\n                className={cn(\n                  \"break-all text-xs text-primary\",\n                  classNames?.link\n                )}\n                href={url}\n              >\n                {url}\n              </Link>\n\n              <Hr\n                className={cn(\n                  \"my-6 w-full border border-solid border-border\",\n                  classNames?.separator\n                )}\n              />\n\n              {expirationHours || appName ? (\n                <Text\n                  className={cn(\n                    \"m-0 mb-3 text-xs text-muted-foreground\",\n                    classNames?.description\n                  )}\n                >\n                  {expirationHours\n                    ? localization.THIS_INVITATION_EXPIRES_IN_HOURS.replace(\n                        \"{expirationHours}\",\n                        expirationHours.toString()\n                      )\n                    : null}\n                  {appName && (\n                    <>\n                      {expirationHours ? \" \" : \"\"}\n                      {localization.EMAIL_SENT_BY.replace(\"{appName}\", appName)}\n                    </>\n                  )}\n                </Text>\n              ) : null}\n\n              <Text\n                className={cn(\n                  \"m-0 text-xs text-muted-foreground\",\n                  classNames?.description\n                )}\n              >\n                {localization.IF_YOU_DIDNT_EXPECT_THIS_INVITATION}\n              </Text>\n\n              {poweredBy && (\n                <Text\n                  className={cn(\n                    \"m-0 mt-4 text-center text-[11px] text-muted-foreground\",\n                    classNames?.poweredBy\n                  )}\n                >\n                  {(() => {\n                    const [beforeBetterAuth, afterBetterAuth] =\n                      localization.POWERED_BY_BETTER_AUTH.split(\"{betterAuth}\")\n\n                    return (\n                      <>\n                        {beforeBetterAuth}\n                        <Link\n                          href=\"https://better-auth.com\"\n                          className={cn(\n                            \"text-primary underline\",\n                            classNames?.link\n                          )}\n                        >\n                          better-auth\n                        </Link>\n                        {afterBetterAuth}\n                      </>\n                    )\n                  })()}\n                </Text>\n              )}\n            </Section>\n          </Container>\n        </Body>\n      </Tailwind>\n    </Html>\n  )\n}\n\nOrganizationInvitationEmail.localization =\n  organizationInvitationEmailLocalization\n\nOrganizationInvitationEmail.PreviewProps = {\n  url: \"https://better-auth-ui.com/auth/accept-invitation?invitationId=example\",\n  email: \"m@example.com\",\n  inviterName: \"Jane Doe\",\n  inviterEmail: \"jane@example.com\",\n  organizationName: \"Acme Inc.\",\n  role: \"member\",\n  appName: \"Better Auth\",\n  expirationHours: 48,\n  darkMode: true\n} as OrganizationInvitationEmailProps\n\nexport default OrganizationInvitationEmail\n",
      "type": "registry:component",
      "target": "@components/auth/email/organization-invitation.tsx"
    },
    {
      "path": "../../packages/react/src/components/auth/email/email-styles.tsx",
      "content": "/**\n * Default color scheme for email templates in light and dark modes.\n *\n * Provides a consistent color palette that can be customized via the EmailColors type.\n */\nexport const defaultColors = {\n  light: {\n    background: \"#F5F5F5\",\n    border: \"#E5E5E5\",\n    card: \"#FFFFFF\",\n    cardForeground: \"#0A0A0A\",\n    foreground: \"#262626\",\n    muted: \"#F5F5F5\",\n    mutedForeground: \"#737373\",\n    primary: \"#171717\",\n    primaryForeground: \"#FAFAFA\"\n  },\n  dark: {\n    background: \"#0A0A0A\",\n    border: \"#2E2E2E\",\n    card: \"#171717\",\n    cardForeground: \"#FAFAFA\",\n    foreground: \"#D4D4D4\",\n    muted: \"#212121\",\n    mutedForeground: \"#A1A1A1\",\n    primary: \"#E5E5E5\",\n    primaryForeground: \"#171717\"\n  }\n}\n\n/**\n * Custom CSS class names for styling different parts of email templates.\n *\n * Allows fine-grained control over the appearance of email components.\n */\nexport type EmailClassNames = {\n  body?: string\n  container?: string\n  card?: string\n  logo?: string\n  title?: string\n  content?: string\n  button?: string\n  description?: string\n  separator?: string\n  link?: string\n  poweredBy?: string\n  codeBlock?: string\n}\n\n/**\n * Custom color scheme configuration for email templates.\n *\n * Supports separate color definitions for light and dark modes.\n * Any color not specified will fall back to the defaultColors values.\n */\nexport type EmailColors = {\n  light?: Partial<typeof defaultColors.light>\n  dark?: Partial<typeof defaultColors.dark>\n}\n\n/**\n * Props for the EmailStyles component.\n */\ninterface EmailStylesProps {\n  /** Custom color scheme for light and dark modes */\n  colors?: EmailColors\n  /** Whether to enable dark mode support */\n  darkMode?: boolean\n}\n\n/**\n * Component that injects CSS styles for email templates with support for light and dark modes.\n *\n * Generates inline styles that adapt to the user's color scheme preference and applies\n * custom colors if provided. Handles logo visibility switching between light and dark modes.\n *\n * @param props - Style configuration options\n * @returns A style element containing CSS for email template theming\n *\n * @example\n * ```tsx\n * <EmailStyles\n *   colors={{\n *     light: { primary: \"#000000\" },\n *     dark: { primary: \"#FFFFFF\" }\n *   }}\n *   darkMode={true}\n * />\n * ```\n */\nexport const EmailStyles = ({ colors, darkMode = true }: EmailStylesProps) => {\n  return (\n    <style type=\"text/css\">{`\n      .bg-background {\n        background-color: ${colors?.light?.background || defaultColors.light.background} !important;\n      }\n      .bg-card {\n        background-color: ${colors?.light?.card || defaultColors.light.card} !important;\n      }\n      .bg-primary {\n        background-color: ${colors?.light?.primary || defaultColors.light.primary} !important;\n      }\n      .bg-muted {\n        background-color: ${colors?.light?.muted || defaultColors.light.muted} !important;\n      }\n      .border-border {\n        border-color: ${colors?.light?.border || defaultColors.light.border} !important;\n      }\n      .text-card-foreground {\n        color: ${colors?.light?.cardForeground || defaultColors.light.cardForeground} !important;\n      }\n      .text-muted-foreground {\n        color: ${colors?.light?.mutedForeground || defaultColors.light.mutedForeground} !important;\n      }\n      .text-primary {\n        color: ${colors?.light?.primary || defaultColors.light.primary} !important;\n      }\n      .text-primary-foreground {\n        color: ${colors?.light?.primaryForeground || defaultColors.light.primaryForeground} !important;\n      }\n      .logo-dark {\n        display: none !important;\n      }\n      .logo-light {\n        display: block !important;\n      }\n\n      ${\n        darkMode\n          ? `@media (prefers-color-scheme: dark) {\n        .bg-background {\n          background-color: ${colors?.dark?.background || defaultColors.dark.background} !important;\n        }\n        .bg-card {\n          background-color: ${colors?.dark?.card || defaultColors.dark.card} !important;\n        }\n        .bg-primary {\n          background-color: ${colors?.dark?.primary || defaultColors.dark.primary} !important;\n        }\n        .bg-muted {\n          background-color: ${colors?.dark?.muted || defaultColors.dark.muted} !important;\n        }\n        .border-border {\n          border-color: ${colors?.dark?.border || defaultColors.dark.border} !important;\n        }\n        .text-card-foreground {\n          color: ${colors?.dark?.cardForeground || defaultColors.dark.cardForeground} !important;\n        }\n        .text-muted-foreground {\n          color: ${colors?.dark?.mutedForeground || defaultColors.dark.mutedForeground} !important;\n        }\n        .text-primary {\n          color: ${colors?.dark?.primary || defaultColors.dark.primary} !important;\n        }\n        .text-primary-foreground {\n          color: ${colors?.dark?.primaryForeground || defaultColors.dark.primaryForeground} !important;\n        }\n        .logo-dark {\n          display: block !important;\n        }\n        .logo-light {\n          display: none !important;\n        }\n        * {\n          box-shadow: none !important;\n        }\n      }`\n          : \"\"\n      }\n    `}</style>\n  )\n}\n\nexport default EmailStyles\n",
      "type": "registry:component",
      "target": "@components/auth/email/email-styles.tsx"
    }
  ],
  "type": "registry:component"
}