@frank-auth/react
Version:
Flexible and customizable React UI components for Frank Authentication
1 lines • 20.6 kB
Source Map (JSON)
{"version":3,"file":"user-profile.cjs","sources":["../../../../../src/components/auth/user-profile/user-profile.tsx"],"sourcesContent":["/**\n * @frank-auth/react - User Profile Component\n *\n * Comprehensive user profile management interface with sections for\n * personal information, security settings, organization management,\n * and account preferences.\n */\n\n'use client';\n\nimport React from 'react';\nimport {Button, Card, CardBody, CardHeader, Divider, Spinner, Tab, Tabs,} from '@heroui/react';\nimport {useAuth} from '../../../hooks/use-auth';\nimport {useUser} from '../../../hooks/use-user';\nimport {useConfig} from '../../../hooks/use-config';\nimport {ProfileForm} from './profile-form';\nimport {SecurityPanel} from './security-panel';\nimport {MFASetup} from './mfa-setup';\nimport {PasskeySetup} from './passkey-setup';\n\n// ============================================================================\n// User Profile Interface\n// ============================================================================\n\nexport interface UserProfileProps {\n /**\n * Default active tab\n */\n defaultTab?: string;\n\n /**\n * Available tabs\n */\n tabs?: UserProfileTab[];\n\n /**\n * Hide specific tabs\n */\n hideTabs?: string[];\n\n /**\n * Show organization settings\n */\n showOrganizationSettings?: boolean;\n\n /**\n * Show security settings\n */\n showSecuritySettings?: boolean;\n\n /**\n * Show MFA settings\n */\n showMFASettings?: boolean;\n\n /**\n * Show passkey settings\n */\n showPasskeySettings?: boolean;\n\n /**\n * Custom className\n */\n className?: string;\n\n /**\n * Card variant\n */\n variant?: 'flat' | 'bordered' | 'shadow';\n\n /**\n * Layout orientation\n */\n orientation?: 'horizontal' | 'vertical';\n\n /**\n * Tab placement\n */\n tabPlacement?: 'top' | 'bottom' | 'start' | 'end';\n\n /**\n * Custom header content\n */\n headerContent?: React.ReactNode;\n\n /**\n * Custom footer content\n */\n footerContent?: React.ReactNode;\n\n /**\n * Profile update handler\n */\n onProfileUpdate?: (data: any) => void;\n\n /**\n * Success callback\n */\n onSuccess?: (message: string) => void;\n\n /**\n * Error callback\n */\n onError?: (error: string) => void;\n\n /**\n * Close handler (for modal usage)\n */\n onClose?: () => void;\n\n /**\n * Loading state override\n */\n isLoading?: boolean;\n\n /**\n * Disable all interactions\n */\n isDisabled?: boolean;\n\n /**\n * Size variant\n */\n size?: 'sm' | 'md' | 'lg';\n\n /**\n * Custom tab content\n */\n customTabs?: Record<string, React.ReactNode>;\n}\n\nexport interface UserProfileTab {\n key: string;\n title: string;\n icon?: React.ReactNode;\n content?: React.ReactNode;\n isDisabled?: boolean;\n badge?: string | number;\n}\n\n// ============================================================================\n// Default Icons\n// ============================================================================\n\nconst Icons = {\n user: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z\" />\n </svg>\n ),\n security: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\" />\n </svg>\n ),\n shield: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\" />\n </svg>\n ),\n key: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z\" />\n </svg>\n ),\n organization: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\" />\n </svg>\n ),\n settings: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n ),\n};\n\n// ============================================================================\n// User Profile Component\n// ============================================================================\n\nexport function UserProfile({\n defaultTab = 'profile',\n tabs,\n hideTabs = [],\n showOrganizationSettings = true,\n showSecuritySettings = true,\n showMFASettings = true,\n showPasskeySettings = true,\n className = '',\n variant = 'bordered',\n orientation = 'horizontal',\n tabPlacement = 'top',\n headerContent,\n footerContent,\n onProfileUpdate,\n onSuccess,\n onError,\n onClose,\n isLoading: externalLoading = false,\n isDisabled = false,\n size = 'md',\n customTabs = {},\n }: UserProfileProps) {\n const { user, isLoading: authLoading } = useAuth();\n const { isLoading: userLoading } = useUser();\n const { components, features } = useConfig();\n\n // Custom component override\n const CustomUserProfile = components.UserProfile;\n if (CustomUserProfile) {\n return <CustomUserProfile {...{\n defaultTab, tabs, hideTabs, showOrganizationSettings, showSecuritySettings,\n showMFASettings, showPasskeySettings, className, variant, orientation,\n tabPlacement, headerContent, footerContent, onProfileUpdate, onSuccess,\n onError, onClose, isLoading: externalLoading, isDisabled, size, customTabs\n }} />;\n }\n\n // Loading state\n const isLoading = externalLoading || authLoading || userLoading;\n\n // Selected tab state\n const [selectedTab, setSelectedTab] = React.useState(defaultTab);\n\n // Default tabs configuration\n const defaultTabs = React.useMemo((): UserProfileTab[] => {\n const tabsList: UserProfileTab[] = [\n {\n key: 'profile',\n title: 'Profile',\n icon: Icons.user,\n content: (\n <ProfileForm\n onUpdate={onProfileUpdate}\n onSuccess={onSuccess}\n onError={onError}\n isDisabled={isDisabled}\n />\n ),\n },\n ];\n\n if (showSecuritySettings) {\n tabsList.push({\n key: 'security',\n title: 'Security',\n icon: Icons.security,\n content: (\n <SecurityPanel\n onSuccess={onSuccess}\n onError={onError}\n isDisabled={isDisabled}\n />\n ),\n });\n }\n\n if (showMFASettings && features.mfa) {\n tabsList.push({\n key: 'mfa',\n title: 'Two-Factor Auth',\n icon: Icons.shield,\n content: (\n <MFASetup\n onSuccess={onSuccess}\n onError={onError}\n isDisabled={isDisabled}\n />\n ),\n });\n }\n\n if (showPasskeySettings && features.passkeys) {\n tabsList.push({\n key: 'passkeys',\n title: 'Passkeys',\n icon: Icons.key,\n content: (\n <PasskeySetup\n onSuccess={onSuccess}\n onError={onError}\n isDisabled={isDisabled}\n />\n ),\n });\n }\n\n if (showOrganizationSettings && features.organizationManagement) {\n tabsList.push({\n key: 'organizations',\n title: 'Organizations',\n icon: Icons.organization,\n content: (\n <div className=\"space-y-4\">\n <p>Organization management coming soon...</p>\n </div>\n ),\n });\n }\n\n // Add custom tabs\n Object.entries(customTabs).forEach(([key, content]) => {\n tabsList.push({\n key,\n title: key.charAt(0).toUpperCase() + key.slice(1),\n content,\n });\n });\n\n return tabsList.filter(tab => !hideTabs.includes(tab.key));\n }, [\n showSecuritySettings,\n showMFASettings,\n showPasskeySettings,\n showOrganizationSettings,\n features,\n hideTabs,\n customTabs,\n onProfileUpdate,\n onSuccess,\n onError,\n isDisabled,\n ]);\n\n // Use provided tabs or default tabs\n const effectiveTabs = tabs || defaultTabs;\n\n // Size mapping\n const sizeMapping = {\n sm: 'sm',\n md: 'md',\n lg: 'lg',\n };\n\n // Don't render if no user\n if (!user && !isLoading) {\n return null;\n }\n\n // Loading state\n if (isLoading) {\n return (\n <Card variant={variant} className={className}>\n <CardBody className=\"flex items-center justify-center py-8\">\n <Spinner size=\"lg\" />\n </CardBody>\n </Card>\n );\n }\n\n return (\n <Card variant={variant} className={className}>\n {/* Header */}\n {(headerContent || onClose) && (\n <>\n <CardHeader className=\"flex items-center justify-between\">\n {headerContent || (\n <div>\n <h3 className=\"text-lg font-semibold\">Profile Settings</h3>\n <p className=\"text-sm text-default-500\">\n Manage your account settings and preferences\n </p>\n </div>\n )}\n {onClose && (\n <Button\n isIconOnly\n variant=\"light\"\n onPress={onClose}\n aria-label=\"Close\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </Button>\n )}\n </CardHeader>\n <Divider />\n </>\n )}\n\n {/* Content */}\n <CardBody className=\"p-0\">\n <Tabs\n selectedKey={selectedTab}\n onSelectionChange={(key) => setSelectedTab(key as string)}\n orientation={orientation}\n placement={tabPlacement}\n size={sizeMapping[size] as any}\n classNames={{\n base: 'w-full',\n tabList: orientation === 'vertical' ? 'w-full' : undefined,\n panel: 'w-full',\n }}\n >\n {effectiveTabs.map((tab) => (\n <Tab\n key={tab.key}\n title={\n <div className=\"flex items-center gap-2\">\n {tab.icon}\n <span>{tab.title}</span>\n {tab.badge && (\n <span className=\"bg-danger text-white text-xs rounded-full px-1.5 py-0.5 min-w-[1.25rem] h-5 flex items-center justify-center\">\n {tab.badge}\n </span>\n )}\n </div>\n }\n isDisabled={tab.isDisabled || isDisabled}\n >\n <div className=\"p-6\">\n {tab.content}\n </div>\n </Tab>\n ))}\n </Tabs>\n </CardBody>\n\n {/* Footer */}\n {footerContent && (\n <>\n <Divider />\n <CardBody className=\"pt-4\">\n {footerContent}\n </CardBody>\n </>\n )}\n </Card>\n );\n}\n\n// ============================================================================\n// Export\n// ============================================================================\n\nexport default UserProfile;"],"names":["Icons","jsx","UserProfile","defaultTab","tabs","hideTabs","showOrganizationSettings","showSecuritySettings","showMFASettings","showPasskeySettings","className","variant","orientation","tabPlacement","headerContent","footerContent","onProfileUpdate","onSuccess","onError","onClose","externalLoading","isDisabled","size","customTabs","user","authLoading","useAuth","userLoading","useUser","components","features","useConfig","CustomUserProfile","isLoading","selectedTab","setSelectedTab","React","defaultTabs","tabsList","ProfileForm","SecurityPanel","MFASetup","PasskeySetup","key","content","tab","effectiveTabs","sizeMapping","Card","CardBody","Spinner","jsxs","Fragment","CardHeader","Button","Divider","Tabs","Tab"],"mappings":"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"}