@frank-auth/react
Version:
Flexible and customizable React UI components for Frank Authentication
1 lines • 16 kB
Source Map (JSON)
{"version":3,"file":"sign-up-modal.cjs","sources":["../../../../../src/components/auth/sign-up/sign-up-modal.tsx"],"sourcesContent":["/**\n * @frank-auth/react - Sign Up Modal Component\n *\n * Modal wrapper for sign-up form with customizable overlay and behavior.\n */\n\n\"use client\";\n\nimport { Button, Modal, ModalBody, ModalHeader } from \"@/components/ui\";\nimport { XMarkIcon } from \"@heroicons/react/24/outline\";\nimport React, { useCallback } from \"react\";\n\nimport { useConfig } from \"../../../hooks/use-config\";\nimport type { SignUpModalProps } from \"./index\";\nimport { SignUpForm } from \"./sign-up-form\";\n\n// ============================================================================\n// Sign Up Modal Component\n// ============================================================================\n\nexport function SignUpModal({\n isOpen = false,\n onClose,\n modalSize = \"md\",\n closeOnBackdropClick = true,\n closeOnEscape = true,\n modalClassName = \"\",\n showCloseButton = true,\n methods = [\"password\", \"oauth\", \"magic-link\"],\n email,\n organizationId,\n invitationToken,\n redirectUrl,\n onSuccess,\n onError,\n title,\n subtitle,\n size = \"md\",\n showBranding = true,\n disabled = false,\n requireTerms = true,\n termsUrl = \"/terms\",\n privacyUrl = \"/privacy\",\n backdrop = \"blur\",\n placement = \"center\",\n}: SignUpModalProps) {\n const { components } = useConfig();\n\n // Custom component override\n const CustomSignUpModal = components.SignUpModal;\n if (CustomSignUpModal) {\n return (\n <CustomSignUpModal\n {...{\n isOpen,\n onClose,\n modalSize,\n closeOnBackdropClick,\n closeOnEscape,\n modalClassName,\n showCloseButton,\n methods,\n email,\n organizationId,\n invitationToken,\n redirectUrl,\n onSuccess,\n onError,\n title,\n subtitle,\n size,\n showBranding,\n disabled,\n requireTerms,\n termsUrl,\n privacyUrl,\n backdrop,\n placement,\n }}\n />\n );\n }\n\n // Handle successful sign-up\n const handleSuccess = useCallback(\n (result: any) => {\n onSuccess?.(result);\n\n // Auto-close modal on success unless redirecting\n if (!redirectUrl) {\n setTimeout(() => {\n onClose?.();\n }, 2000); // Allow time to see success message\n }\n },\n [onSuccess, onClose, redirectUrl],\n );\n\n // Handle modal close\n const handleClose = useCallback(() => {\n if (disabled) return;\n onClose?.();\n }, [onClose, disabled]);\n\n // Get modal size mapping\n const getSizeProps = () => {\n switch (modalSize) {\n case \"sm\":\n return {\n size: \"sm\" as const,\n className: \"max-w-sm max-h-[90vh]\",\n scrollBehavior: \"inside\" as const,\n };\n case \"md\":\n return {\n size: \"md\" as const,\n className: \"max-w-md max-h-[90vh]\",\n scrollBehavior: \"inside\" as const,\n };\n case \"lg\":\n return {\n size: \"lg\" as const,\n className: \"max-w-lg max-h-[90vh]\",\n scrollBehavior: \"inside\" as const,\n };\n case \"xl\":\n return {\n size: \"xl\" as const,\n className: \"max-w-xl max-h-[90vh]\",\n scrollBehavior: \"inside\" as const,\n };\n case \"full\":\n return {\n size: \"full\" as const,\n className: \"max-w-full h-full\",\n scrollBehavior: \"outside\" as const,\n };\n default:\n return {\n size: \"md\" as const,\n className: \"max-w-md max-h-[90vh]\",\n scrollBehavior: \"inside\" as const,\n };\n }\n };\n\n const sizeProps = getSizeProps();\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={handleClose}\n size={sizeProps.size}\n backdrop={backdrop}\n placement={placement}\n scrollBehavior={sizeProps.scrollBehavior}\n closeButton={false} // We'll handle the close button manually\n isDismissable={closeOnBackdropClick && !disabled}\n isKeyboardDismissDisabled={!closeOnEscape || disabled}\n className={`${modalClassName}`}\n classNames={{\n wrapper: \"z-[9999]\",\n backdrop: \"z-[9998]\",\n base: `${sizeProps.className}`,\n body: \"px-0 py-0\",\n }}\n >\n <>\n {/* Modal Header */}\n <ModalHeader className=\"flex items-center justify-between px-6 py-4 border-b border-divider\">\n <div className=\"flex-1\">\n {title && (\n <h2 className=\"text-lg font-semibold text-foreground\">{title}</h2>\n )}\n {subtitle && (\n <p className=\"text-sm text-default-500 mt-1\">{subtitle}</p>\n )}\n </div>\n\n {showCloseButton && (\n <Button\n isIconOnly\n variant=\"light\"\n size=\"sm\"\n onPress={handleClose}\n className=\"text-default-400 hover:text-default-600 -mr-2\"\n isDisabled={disabled}\n >\n <XMarkIcon className=\"w-4 h-4\" />\n </Button>\n )}\n </ModalHeader>\n\n {/* Modal Body */}\n <ModalBody>\n <div className=\"px-6 py-6\">\n <SignUpForm\n methods={methods}\n email={email}\n organizationId={organizationId}\n invitationToken={invitationToken}\n redirectUrl={redirectUrl}\n onSuccess={handleSuccess}\n onError={onError}\n title={undefined} // Title is in header\n subtitle={undefined} // Subtitle is in header\n size={size}\n showBranding={showBranding}\n disabled={disabled}\n requireTerms={requireTerms}\n termsUrl={termsUrl}\n privacyUrl={privacyUrl}\n variant=\"minimal\"\n className=\"space-y-4\"\n showSignInLink={true}\n autoFocus={true}\n />\n </div>\n </ModalBody>\n </>\n </Modal>\n );\n}\n\n// ============================================================================\n// Sign Up Modal Hook\n// ============================================================================\n\n/**\n * Hook for managing sign-up modal state\n */\nexport function useSignUpModal() {\n const [isOpen, setIsOpen] = React.useState(false);\n const [modalData, setModalData] = React.useState<{\n email?: string;\n organizationId?: string;\n invitationToken?: string;\n }>({});\n\n const open = useCallback((data?: typeof modalData) => {\n if (data) {\n setModalData(data);\n }\n setIsOpen(true);\n }, []);\n\n const close = useCallback(() => {\n setIsOpen(false);\n // Clear data after modal closes\n setTimeout(() => {\n setModalData({});\n }, 300);\n }, []);\n\n const toggle = useCallback(() => setIsOpen((prev) => !prev), []);\n\n return {\n isOpen,\n open,\n close,\n toggle,\n setIsOpen,\n modalData,\n setModalData,\n };\n}\n\n// ============================================================================\n// Sign Up Modal with Trigger\n// ============================================================================\n\nexport interface SignUpModalWithTriggerProps extends SignUpModalProps {\n /**\n * Trigger element\n */\n trigger?: React.ReactElement;\n\n /**\n * Trigger props (for default button)\n */\n triggerProps?: {\n children?: React.ReactNode;\n variant?:\n | \"solid\"\n | \"bordered\"\n | \"light\"\n | \"flat\"\n | \"faded\"\n | \"shadow\"\n | \"ghost\";\n color?:\n | \"default\"\n | \"primary\"\n | \"secondary\"\n | \"success\"\n | \"warning\"\n | \"danger\";\n size?: \"sm\" | \"md\" | \"lg\";\n fullWidth?: boolean;\n startContent?: React.ReactNode;\n endContent?: React.ReactNode;\n className?: string;\n disabled?: boolean;\n };\n\n /**\n * Pre-fill modal with data when opened\n */\n modalData?: {\n email?: string;\n organizationId?: string;\n invitationToken?: string;\n };\n}\n\n/**\n * Sign Up Modal with built-in trigger button\n */\nexport function SignUpModalWithTrigger({\n trigger,\n triggerProps = {},\n modalData,\n ...modalProps\n}: SignUpModalWithTriggerProps) {\n const { isOpen, open, close } = useSignUpModal();\n\n // Default trigger button\n const defaultTrigger = (\n <Button\n variant={triggerProps.variant || \"solid\"}\n color={triggerProps.color || \"primary\"}\n size={triggerProps.size || \"md\"}\n fullWidth={triggerProps.fullWidth}\n startContent={triggerProps.startContent}\n endContent={triggerProps.endContent}\n className={triggerProps.className}\n isDisabled={triggerProps.disabled}\n onPress={() => open(modalData)}\n >\n {triggerProps.children || \"Sign Up\"}\n </Button>\n );\n\n // Use custom trigger or default\n const triggerElement = trigger\n ? React.cloneElement(trigger, {\n onClick: (e: React.MouseEvent) => {\n trigger.props.onClick?.(e);\n open(modalData);\n },\n })\n : defaultTrigger;\n\n return (\n <>\n {triggerElement}\n <SignUpModal\n {...modalProps}\n isOpen={isOpen}\n onClose={close}\n email={modalData?.email || modalProps.email}\n organizationId={modalData?.organizationId || modalProps.organizationId}\n invitationToken={\n modalData?.invitationToken || modalProps.invitationToken\n }\n />\n </>\n );\n}\n\n// ============================================================================\n// Specialized Modal Variants\n// ============================================================================\n\n/**\n * Compact Sign Up Modal\n */\nexport function CompactSignUpModal(props: SignUpModalProps) {\n return <SignUpModal {...props} modalSize=\"sm\" size=\"sm\" />;\n}\n\n/**\n * Large Sign Up Modal\n */\nexport function LargeSignUpModal(props: SignUpModalProps) {\n return <SignUpModal {...props} modalSize=\"lg\" size=\"lg\" />;\n}\n\n/**\n * Full Screen Sign Up Modal\n */\nexport function FullScreenSignUpModal(props: SignUpModalProps) {\n return (\n <SignUpModal {...props} modalSize=\"full\" size=\"lg\" backdrop=\"opaque\" />\n );\n}\n\n/**\n * Organization Invitation Modal\n */\nexport interface OrganizationInviteModalProps\n extends Omit<SignUpModalProps, \"invitationToken\" | \"organizationId\"> {\n /**\n * Invitation token\n */\n invitationToken: string;\n\n /**\n * Whether modal opens automatically\n */\n autoOpen?: boolean;\n}\n\nexport function OrganizationInviteModal({\n invitationToken,\n autoOpen = false,\n title,\n subtitle,\n ...props\n}: OrganizationInviteModalProps) {\n const [isOpen, setIsOpen] = React.useState(autoOpen);\n\n // Parse invitation token to get organization info\n const invitationData = React.useMemo(() => {\n try {\n const decoded = atob(invitationToken);\n return JSON.parse(decoded);\n } catch {\n return null;\n }\n }, [invitationToken]);\n\n const finalTitle =\n title ||\n (invitationData?.orgName\n ? `Join ${invitationData.orgName}`\n : \"Organization Invitation\");\n\n const finalSubtitle =\n subtitle ||\n (invitationData?.inviterName\n ? `${invitationData.inviterName} has invited you to join`\n : \"You've been invited to join an organization\");\n\n return (\n <SignUpModal\n {...props}\n isOpen={isOpen}\n onClose={() => setIsOpen(false)}\n invitationToken={invitationToken}\n organizationId={invitationData?.orgId}\n title={finalTitle}\n subtitle={finalSubtitle}\n showBranding={true}\n backdrop=\"blur\"\n closeOnBackdropClick={false} // Require explicit close for invitations\n />\n );\n}\n\n// ============================================================================\n// Export\n// ============================================================================\n\nexport default SignUpModal;\n"],"names":["SignUpModal","isOpen","onClose","modalSize","closeOnBackdropClick","closeOnEscape","modalClassName","showCloseButton","methods","email","organizationId","invitationToken","redirectUrl","onSuccess","onError","title","subtitle","size","showBranding","disabled","requireTerms","termsUrl","privacyUrl","backdrop","placement","components","useConfig","CustomSignUpModal","jsx","handleSuccess","useCallback","result","handleClose","sizeProps","Modal","jsxs","Fragment","ModalHeader","Button","XMarkIcon","ModalBody","SignUpForm","useSignUpModal","setIsOpen","React","modalData","setModalData","open","data","close","toggle","prev"],"mappings":"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"}