@burdenoff/microfe-auth 2026.527.1 → 2026.527.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/AuthRoutes.js +1 -1
- package/dist/auth/AuthRoutes.js.map +1 -1
- package/dist/auth/components/AuthLayout.js +14 -14
- package/dist/auth/components/AuthLayout.js.map +1 -1
- package/dist/auth/components/CaptchaV2.js +1 -4
- package/dist/auth/components/CaptchaV2.js.map +1 -1
- package/dist/auth/components/ConfirmActionDialog.js +3 -3
- package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
- package/dist/auth/components/profile/UserProfileMenu.js +10 -6
- package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
- package/dist/auth/components/workspace/WorkspaceSelector.js +5 -5
- package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
- package/dist/auth/hooks/index.js +2 -2
- package/dist/auth/index.js +4 -4
- package/dist/auth/pages/AuditLogPage.js +7 -7
- package/dist/auth/pages/AuditLogPage.js.map +1 -1
- package/dist/auth/pages/EmailVerifyPage.js +10 -8
- package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
- package/dist/auth/pages/LoginPage.js +20 -19
- package/dist/auth/pages/LoginPage.js.map +1 -1
- package/dist/auth/pages/MagicLinkPage.js +10 -8
- package/dist/auth/pages/MagicLinkPage.js.map +1 -1
- package/dist/auth/pages/MfaSetupPage.js +26 -21
- package/dist/auth/pages/MfaSetupPage.js.map +1 -1
- package/dist/auth/pages/MfaVerifyPage.js +21 -21
- package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
- package/dist/auth/pages/OAuthCallbackPage.js +7 -6
- package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
- package/dist/auth/pages/PasswordResetPage.js +25 -22
- package/dist/auth/pages/PasswordResetPage.js.map +1 -1
- package/dist/auth/pages/ReactivatePage.js +17 -15
- package/dist/auth/pages/ReactivatePage.js.map +1 -1
- package/dist/auth/pages/RegisterPage.js +198 -194
- package/dist/auth/pages/RegisterPage.js.map +1 -1
- package/dist/auth/pages/SSOClearPage.js +1 -1
- package/dist/auth/pages/SSOClearPage.js.map +1 -1
- package/dist/auth/pages/SSODepositPage.js +1 -1
- package/dist/auth/pages/SSODepositPage.js.map +1 -1
- package/dist/auth/pages/SSORelayCallbackPage.js +1 -1
- package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
- package/dist/auth/pages/SSORelayPage.js +1 -1
- package/dist/auth/pages/SSORelayPage.js.map +1 -1
- package/dist/auth/pages/SecurityPage.js +15 -16
- package/dist/auth/pages/SecurityPage.js.map +1 -1
- package/dist/auth/pages/SessionsPage.js +19 -20
- package/dist/auth/pages/SessionsPage.js.map +1 -1
- package/dist/auth/pages/SsoCallbackPage.js +19 -15
- package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
- package/dist/auth/pages/SsoConfigPage.js +41 -27
- package/dist/auth/pages/SsoConfigPage.js.map +1 -1
- package/dist/auth/pages/SsoLoginPage.js +16 -14
- package/dist/auth/pages/SsoLoginPage.js.map +1 -1
- package/dist/auth/pages/UserDetailsPage.js +16 -17
- package/dist/auth/pages/UserDetailsPage.js.map +1 -1
- package/dist/auth/pages/UserProfilePage.js +31 -25
- package/dist/auth/pages/UserProfilePage.js.map +1 -1
- package/dist/auth/pages/UsersPage.js +46 -42
- package/dist/auth/pages/UsersPage.js.map +1 -1
- package/dist/index.js +11 -11
- package/dist/microfe-auth.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SsoLoginPage.js","names":[],"sources":["../../../src/auth/pages/SsoLoginPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useNavigate, useSearchParams } from \"react-router-dom\";\nimport { useSsoOperations } from \"../hooks/useSsoOperations\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { EmailIcon, SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\n\ntype SsoMethod = \"saml\" | \"oidc\";\n\n/**\n * SSO Login Page\n * Allows users to enter their email/company domain to initiate SSO login\n * Supports both SAML 2.0 and OIDC authentication methods\n */\nexport function SsoLoginPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const tenantFromUrl = searchParams.get(\"tenant\");\n const methodFromUrl = searchParams.get(\"method\") as SsoMethod | null;\n const {\n initiateSsoLogin,\n initiateExternalOidcLogin,\n isGeneratingRequest,\n isGeneratingOidcUrl,\n } = useSsoOperations(tenantFromUrl ?? undefined);\n\n const [email, setEmail] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [ssoMethod, setSsoMethod] = useState<SsoMethod>(\"oidc\");\n const [isInitiating, setIsInitiating] = useState(false);\n const [isSsoNotConfigured, setIsSsoNotConfigured] = useState(false);\n\n // Set method from URL if provided\n useEffect(() => {\n if (methodFromUrl === \"saml\" || methodFromUrl === \"oidc\") {\n setSsoMethod(methodFromUrl);\n }\n }, [methodFromUrl]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setIsSsoNotConfigured(false);\n setIsInitiating(true);\n\n try {\n // Extract domain from email\n const domain = email.split(\"@\")[1];\n if (!domain) {\n setError(\n tr(\n \"auth.ssoLogin.invalidEmailDomain\",\n \"Please enter a valid email address with your company domain\",\n ),\n );\n setIsInitiating(false);\n return;\n }\n\n // In a real app, you would look up the tenant by domain\n // For now, we'll use the tenant from URL or the domain itself\n const tenantId = tenantFromUrl || domain;\n\n // Store email for post-login\n sessionStorage.setItem(\"sso_email\", email);\n\n if (ssoMethod === \"oidc\") {\n // External OIDC flow (Azure AD, Google, etc.)\n await initiateExternalOidcLogin(tenantId, \"/dashboard\");\n } else {\n await initiateSsoLogin(tenantId, \"/dashboard\");\n }\n } catch (err) {\n console.error(\"SSO login failed:\", err);\n const errorMessage =\n err instanceof Error\n ? err.message\n : \"Failed to initiate SSO login. Please check your configuration.\";\n\n // Check if error is about SSO not being configured\n if (\n errorMessage.toLowerCase().includes(\"sso\") &&\n (errorMessage.toLowerCase().includes(\"not configured\") ||\n errorMessage.toLowerCase().includes(\"not set up\"))\n ) {\n setIsSsoNotConfigured(true);\n setError(\n tr(\n \"auth.ssoLogin.ssoNotConfigured\",\n \"SSO is not configured for your organization. Please use regular email/password login or contact your administrator to set up SSO.\",\n ),\n );\n } else {\n setError(errorMessage);\n }\n\n setIsInitiating(false);\n }\n };\n\n const isSubmitting =\n isGeneratingRequest || isGeneratingOidcUrl || isInitiating;\n\n return (\n <AuthLayout\n title={tr(\"auth.ssoLogin.title\", \"Enterprise SSO\")}\n subtitle={tr(\n \"auth.ssoLogin.subtitle\",\n \"Sign in with your company's identity provider\",\n )}\n >\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div\n className={`flex items-start gap-3 p-4 rounded-xl border ${\n isSsoNotConfigured\n ? \"bg-status-warning-bg-subtle border-status-warning-border\"\n : \"bg-status-error-bg-subtle border-status-error-border\"\n }`}\n >\n <AlertIcon\n className={`w-5 h-5 flex-shrink-0 mt-0.5 ${\n isSsoNotConfigured\n ? \"text-status-warning-text\"\n : \"text-status-error-text\"\n }`}\n />\n <div className=\"flex-1\">\n <p\n className={`text-sm ${\n isSsoNotConfigured\n ? \"text-status-warning-text\"\n : \"text-status-error-text\"\n }`}\n >\n {error}\n </p>\n {isSsoNotConfigured && (\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\", { state: { email } })}\n className=\"mt-3 inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-status-warning-bg-subtle hover:bg-status-warning-bg-subtle text-status-warning-text text-sm font-semibold transition-all\"\n >\n <svg\n className=\"w-4 h-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1\"\n />\n </svg>\n {tr(\n \"auth.ssoLogin.useEmailLogin\",\n \"Use Email/Password Login Instead\",\n )}\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* SSO Method Toggle */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.ssoLogin.authMethod\", \"Authentication Method\")}\n </label>\n <div className=\"grid grid-cols-2 gap-3\">\n <button\n type=\"button\"\n onClick={() => setSsoMethod(\"oidc\")}\n className={`p-4 rounded-xl border-2 transition-all ${\n ssoMethod === \"oidc\"\n ? \"border-action-primary-bg bg-status-info-bg-subtle\"\n : \"border-border-subtle hover:border-border-default\"\n }`}\n >\n <div className=\"flex items-center gap-3\">\n <div\n className={`w-10 h-10 rounded-lg flex items-center justify-center ${\n ssoMethod === \"oidc\"\n ? \"bg-status-info-bg-subtle0\"\n : \"bg-bg-sunken\"\n }`}\n >\n <svg\n className={`w-5 h-5 ${ssoMethod === \"oidc\" ? \"text-white\" : \"text-text-secondary\"}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9\"\n />\n </svg>\n </div>\n <div className=\"text-left\">\n <div\n className={`font-medium ${ssoMethod === \"oidc\" ? \"text-text-linkHover\" : \"text-text-primary\"}`}\n >\n OpenID Connect\n </div>\n <div className=\"text-xs text-text-secondary\">\n {tr(\n \"auth.ssoLogin.modernRecommended\",\n \"Modern & Recommended\",\n )}\n </div>\n </div>\n </div>\n </button>\n\n <button\n type=\"button\"\n onClick={() => setSsoMethod(\"saml\")}\n className={`p-4 rounded-xl border-2 transition-all ${\n ssoMethod === \"saml\"\n ? \"border-accent-purple bg-accent-purple-subtle\"\n : \"border-border-subtle hover:border-border-default\"\n }`}\n >\n <div className=\"flex items-center gap-3\">\n <div\n className={`w-10 h-10 rounded-lg flex items-center justify-center ${\n ssoMethod === \"saml\"\n ? \"bg-accent-purple-subtle0\"\n : \"bg-bg-sunken\"\n }`}\n >\n <svg\n className={`w-5 h-5 ${ssoMethod === \"saml\" ? \"text-white\" : \"text-text-secondary\"}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n 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 />\n </svg>\n </div>\n <div className=\"text-left\">\n <div\n className={`font-medium ${ssoMethod === \"saml\" ? \"text-accent-purple\" : \"text-text-primary\"}`}\n >\n SAML 2.0\n </div>\n <div className=\"text-xs text-text-secondary\">\n {tr(\"auth.ssoLogin.enterpriseLegacy\", \"Enterprise Legacy\")}\n </div>\n </div>\n </div>\n </button>\n </div>\n </div>\n\n {/* Info Banner */}\n <div\n className={`p-4 rounded-xl border ${\n ssoMethod === \"oidc\"\n ? \"bg-status-info-bg-subtle border-status-info-border\"\n : \"bg-accent-purple-subtle border-accent-purple-subtle\"\n }`}\n >\n <p\n className={`text-sm ${\n ssoMethod === \"oidc\"\n ? \"text-status-info-text\"\n : \"text-accent-purple\"\n }`}\n >\n {ssoMethod === \"oidc\"\n ? tr(\n \"auth.sso.oidcInfoBannerPkce\",\n \"Sign in using OpenID Connect with your identity provider. Supports modern SSO with PKCE security.\",\n )\n : tr(\n \"auth.sso.samlInfoBanner\",\n \"Sign in using SAML 2.0 with your enterprise identity provider (Okta, Azure AD, etc.).\",\n )}\n </p>\n </div>\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.ssoLogin.workEmail\", \"Work Email Address\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"w-5 h-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n required\n aria-required=\"true\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n autoComplete=\"email\"\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder=\"you@company.com\"\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.ssoLogin.useCompanyEmail\",\n \"Use your company email (e.g., @yourcompany.com)\",\n )}\n </p>\n </div>\n\n {/* Submit Button */}\n <button\n type=\"submit\"\n disabled={isSubmitting || !email}\n className={`w-full py-3 px-4 rounded-xl font-semibold shadow-lg transition-all duration-200 flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:shadow-none ${\n ssoMethod === \"oidc\"\n ? \"auth-primary-button\"\n : \"bg-accent-purple text-white hover:opacity-90\"\n }`}\n >\n {isSubmitting ? (\n <>\n <SpinnerIcon className=\"w-5 h-5 animate-spin\" />\n <span>{tr(\"auth.sso.redirecting\", \"Redirecting...\")}</span>\n </>\n ) : (\n <>\n <svg\n className=\"w-5 h-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1\"\n />\n </svg>\n <span>\n {ssoMethod === \"oidc\"\n ? tr(\"auth.sso.continueWithOidc\", \"Continue with OIDC\")\n : tr(\"auth.sso.continueWithSaml\", \"Continue with SAML\")}\n </span>\n </>\n )}\n </button>\n </form>\n\n {/* Back to Regular Login */}\n <div className=\"mt-6 text-center\">\n <button\n onClick={() => navigate(\"/auth/login\")}\n className=\"text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n <span className=\"font-semibold text-text-link hover:text-text-linkHover\">\n {tr(\"auth.ssoLogin.backToLogin\", \"Back to regular login\")}\n </span>\n </button>\n </div>\n\n {/* Reactivate Account Link */}\n <div className=\"mt-4 text-center\">\n <button\n onClick={() => navigate(\"/auth/reactivate\")}\n className=\"text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\n \"auth.ssoLogin.deactivatedAccount\",\n \"Have a deactivated account?\",\n )}{\" \"}\n <span className=\"font-semibold text-status-success-text hover:text-status-success-text\">\n {tr(\"auth.ssoLogin.reactivateIt\", \"Reactivate it\")}\n </span>\n </button>\n </div>\n\n {/* Help Text */}\n <div className=\"mt-6 p-4 rounded-xl bg-bg-canvas/50 border border-border-subtle\">\n <h4 className=\"text-sm font-medium text-[var(--color-text-primary)] mb-2\">\n {tr(\"auth.ssoLogin.noSso\", \"Don't see your company's SSO?\")}\n </h4>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.ssoLogin.contactIT\",\n \"Contact your IT administrator to set up SSO for your organization. They'll need to configure your identity provider with our service.\",\n )}\n </p>\n </div>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SsoLoginPage.js","names":[],"sources":["../../../src/auth/pages/SsoLoginPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useNavigate, useSearchParams } from \"react-router-dom\";\nimport { useSsoOperations } from \"../hooks/useSsoOperations\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { EmailIcon, SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\n\ntype SsoMethod = \"saml\" | \"oidc\";\n\n/**\n * SSO Login Page\n * Allows users to enter their email/company domain to initiate SSO login\n * Supports both SAML 2.0 and OIDC authentication methods\n */\nexport function SsoLoginPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const tenantFromUrl = searchParams.get(\"tenant\");\n const methodFromUrl = searchParams.get(\"method\") as SsoMethod | null;\n const {\n initiateSsoLogin,\n initiateExternalOidcLogin,\n isGeneratingRequest,\n isGeneratingOidcUrl,\n } = useSsoOperations(tenantFromUrl ?? undefined);\n\n const [email, setEmail] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [ssoMethod, setSsoMethod] = useState<SsoMethod>(\"oidc\");\n const [isInitiating, setIsInitiating] = useState(false);\n const [isSsoNotConfigured, setIsSsoNotConfigured] = useState(false);\n\n // Set method from URL if provided\n useEffect(() => {\n if (methodFromUrl === \"saml\" || methodFromUrl === \"oidc\") {\n setSsoMethod(methodFromUrl);\n }\n }, [methodFromUrl]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setIsSsoNotConfigured(false);\n setIsInitiating(true);\n\n try {\n // Extract domain from email\n const domain = email.split(\"@\")[1];\n if (!domain) {\n setError(\n tr(\n \"auth.ssoLogin.invalidEmailDomain\",\n \"Please enter a valid email address with your company domain\",\n ),\n );\n setIsInitiating(false);\n return;\n }\n\n // In a real app, you would look up the tenant by domain\n // For now, we'll use the tenant from URL or the domain itself\n const tenantId = tenantFromUrl || domain;\n\n // Store email for post-login\n sessionStorage.setItem(\"sso_email\", email);\n\n if (ssoMethod === \"oidc\") {\n // External OIDC flow (Azure AD, Google, etc.)\n await initiateExternalOidcLogin(tenantId, \"/dashboard\");\n } else {\n await initiateSsoLogin(tenantId, \"/dashboard\");\n }\n } catch (err) {\n console.error(\"SSO login failed:\", err);\n const errorMessage =\n err instanceof Error\n ? err.message\n : \"Failed to initiate SSO login. Please check your configuration.\";\n\n // Check if error is about SSO not being configured\n if (\n errorMessage.toLowerCase().includes(\"sso\") &&\n (errorMessage.toLowerCase().includes(\"not configured\") ||\n errorMessage.toLowerCase().includes(\"not set up\"))\n ) {\n setIsSsoNotConfigured(true);\n setError(\n tr(\n \"auth.ssoLogin.ssoNotConfigured\",\n \"SSO is not configured for your organization. Please use regular email/password login or contact your administrator to set up SSO.\",\n ),\n );\n } else {\n setError(errorMessage);\n }\n\n setIsInitiating(false);\n }\n };\n\n const isSubmitting =\n isGeneratingRequest || isGeneratingOidcUrl || isInitiating;\n\n return (\n <AuthLayout\n title={tr(\"auth.ssoLogin.title\", \"Enterprise SSO\")}\n subtitle={tr(\n \"auth.ssoLogin.subtitle\",\n \"Sign in with your company's identity provider\",\n )}\n >\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div\n className={`flex items-start gap-3 p-4 rounded-xl border ${\n isSsoNotConfigured\n ? \"bg-status-warning-bg-subtle border-status-warning-border\"\n : \"bg-status-error-bg-subtle border-status-error-border\"\n }`}\n >\n <AlertIcon\n className={`size-5 flex-shrink-0 mt-0.5 ${\n isSsoNotConfigured\n ? \"text-status-warning-text\"\n : \"text-status-error-text\"\n }`}\n />\n <div className=\"flex-1\">\n <p\n className={`text-sm ${\n isSsoNotConfigured\n ? \"text-status-warning-text\"\n : \"text-status-error-text\"\n }`}\n >\n {error}\n </p>\n {isSsoNotConfigured && (\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\", { state: { email } })}\n className=\"mt-3 inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-status-warning-bg-subtle hover:bg-status-warning-bg-subtle text-status-warning-text text-sm font-semibold transition-all\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1\"\n />\n </svg>\n {tr(\n \"auth.ssoLogin.useEmailLogin\",\n \"Use Email/Password Login Instead\",\n )}\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* SSO Method Toggle */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.ssoLogin.authMethod\", \"Authentication Method\")}\n </label>\n <div className=\"grid grid-cols-2 gap-3\">\n <button\n type=\"button\"\n onClick={() => setSsoMethod(\"oidc\")}\n className={`p-4 rounded-xl border-2 transition-all ${\n ssoMethod === \"oidc\"\n ? \"border-action-primary-bg bg-status-info-bg-subtle\"\n : \"border-border-subtle hover:border-border-default\"\n }`}\n >\n <div className=\"flex items-center gap-3\">\n <div\n className={`size-10 rounded-lg flex items-center justify-center ${\n ssoMethod === \"oidc\"\n ? \"bg-status-info-bg-subtle0\"\n : \"bg-bg-sunken\"\n }`}\n >\n <svg\n className={`size-5 ${ssoMethod === \"oidc\" ? \"text-white\" : \"text-text-secondary\"}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9\"\n />\n </svg>\n </div>\n <div className=\"text-left\">\n <div\n className={`font-medium ${ssoMethod === \"oidc\" ? \"text-text-linkHover\" : \"text-text-primary\"}`}\n >\n OpenID Connect\n </div>\n <div className=\"text-xs text-text-secondary\">\n {tr(\n \"auth.ssoLogin.modernRecommended\",\n \"Modern & Recommended\",\n )}\n </div>\n </div>\n </div>\n </button>\n\n <button\n type=\"button\"\n onClick={() => setSsoMethod(\"saml\")}\n className={`p-4 rounded-xl border-2 transition-all ${\n ssoMethod === \"saml\"\n ? \"border-accent-purple bg-accent-purple-subtle\"\n : \"border-border-subtle hover:border-border-default\"\n }`}\n >\n <div className=\"flex items-center gap-3\">\n <div\n className={`size-10 rounded-lg flex items-center justify-center ${\n ssoMethod === \"saml\"\n ? \"bg-accent-purple-subtle0\"\n : \"bg-bg-sunken\"\n }`}\n >\n <svg\n className={`size-5 ${ssoMethod === \"saml\" ? \"text-white\" : \"text-text-secondary\"}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n 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 />\n </svg>\n </div>\n <div className=\"text-left\">\n <div\n className={`font-medium ${ssoMethod === \"saml\" ? \"text-accent-purple\" : \"text-text-primary\"}`}\n >\n SAML 2.0\n </div>\n <div className=\"text-xs text-text-secondary\">\n {tr(\"auth.ssoLogin.enterpriseLegacy\", \"Enterprise Legacy\")}\n </div>\n </div>\n </div>\n </button>\n </div>\n </div>\n\n {/* Info Banner */}\n <div\n className={`p-4 rounded-xl border ${\n ssoMethod === \"oidc\"\n ? \"bg-status-info-bg-subtle border-status-info-border\"\n : \"bg-accent-purple-subtle border-accent-purple-subtle\"\n }`}\n >\n <p\n className={`text-sm ${\n ssoMethod === \"oidc\"\n ? \"text-status-info-text\"\n : \"text-accent-purple\"\n }`}\n >\n {ssoMethod === \"oidc\"\n ? tr(\n \"auth.sso.oidcInfoBannerPkce\",\n \"Sign in using OpenID Connect with your identity provider. Supports modern SSO with PKCE security.\",\n )\n : tr(\n \"auth.sso.samlInfoBanner\",\n \"Sign in using SAML 2.0 with your enterprise identity provider (Okta, Azure AD, etc.).\",\n )}\n </p>\n </div>\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.ssoLogin.workEmail\", \"Work Email Address\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n required\n aria-required=\"true\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n autoComplete=\"email\"\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder=\"you@company.com\"\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.ssoLogin.useCompanyEmail\",\n \"Use your company email (e.g., @yourcompany.com)\",\n )}\n </p>\n </div>\n\n {/* Submit Button */}\n <button\n type=\"submit\"\n disabled={isSubmitting || !email}\n className={`w-full py-3 px-4 rounded-xl font-semibold shadow-lg transition-all duration-200 flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:shadow-none ${\n ssoMethod === \"oidc\"\n ? \"auth-primary-button\"\n : \"bg-accent-purple text-white hover:opacity-90\"\n }`}\n >\n {isSubmitting ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.sso.redirecting\", \"Redirecting...\")}</span>\n </>\n ) : (\n <>\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1\"\n />\n </svg>\n <span>\n {ssoMethod === \"oidc\"\n ? tr(\"auth.sso.continueWithOidc\", \"Continue with OIDC\")\n : tr(\"auth.sso.continueWithSaml\", \"Continue with SAML\")}\n </span>\n </>\n )}\n </button>\n </form>\n\n {/* Back to Regular Login */}\n <div className=\"mt-6 text-center\">\n <button type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n <span className=\"font-semibold text-text-link hover:text-text-linkHover\">\n {tr(\"auth.ssoLogin.backToLogin\", \"Back to regular login\")}\n </span>\n </button>\n </div>\n\n {/* Reactivate Account Link */}\n <div className=\"mt-4 text-center\">\n <button type=\"button\"\n onClick={() => navigate(\"/auth/reactivate\")}\n className=\"text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\n \"auth.ssoLogin.deactivatedAccount\",\n \"Have a deactivated account?\",\n )}{\" \"}\n <span className=\"font-semibold text-status-success-text hover:text-status-success-text\">\n {tr(\"auth.ssoLogin.reactivateIt\", \"Reactivate it\")}\n </span>\n </button>\n </div>\n\n {/* Help Text */}\n <div className=\"mt-6 p-4 rounded-xl bg-bg-canvas/50 border border-border-subtle\">\n <h4 className=\"text-sm font-medium text-[var(--color-text-primary)] mb-2\">\n {tr(\"auth.ssoLogin.noSso\", \"Don't see your company's SSO?\")}\n </h4>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.ssoLogin.contactIT\",\n \"Contact your IT administrator to set up SSO for your organization. They'll need to configure your identity provider with our service.\",\n )}\n </p>\n </div>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { useAuthStore as e } from "../store/authStore.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import "../hooks/
|
|
5
|
-
import { AlertIcon as r, SpinnerIcon as i } from "../components/AuthIcons.js";
|
|
2
|
+
import { AlertIcon as t, SpinnerIcon as n } from "../components/AuthIcons.js";
|
|
3
|
+
import { useUserOperations as r } from "../hooks/useUserOperations.js";
|
|
4
|
+
import { useAuthPermissions as i } from "../hooks/useAuthPermissions.js";
|
|
6
5
|
import { useCreateAuthExportJobMutation as a } from "../../generated/wspace-operations.js";
|
|
7
6
|
import { ConfirmActionDialog as o } from "../components/ConfirmActionDialog.js";
|
|
8
7
|
import { useEffect as s, useState as c } from "react";
|
|
@@ -37,7 +36,7 @@ function v() {
|
|
|
37
36
|
let { id: v } = u(), y = l(), { t: b } = d(), x = (e, t) => {
|
|
38
37
|
let n = b(e);
|
|
39
38
|
return n === e ? t : n;
|
|
40
|
-
}, S = e((e) => e.isAuthenticated), C = e((e) => e.user), { canViewUsers: w, canDeleteUsers: T, isLoading: E } =
|
|
39
|
+
}, S = e((e) => e.isAuthenticated), C = e((e) => e.user), { canViewUsers: w, canDeleteUsers: T, isLoading: E } = i(), { getUser: D, deleteUser: O, hardDeleteUser: k } = r(), [A, j] = c(null), [M, N] = c(!0), [P, F] = c(null), [I, L] = c(null), [R, z] = c(null), [B, { loading: V }] = a(), [H, U] = c(null);
|
|
41
40
|
s(() => {
|
|
42
41
|
let e = !1;
|
|
43
42
|
if (!v) {
|
|
@@ -118,8 +117,8 @@ function v() {
|
|
|
118
117
|
className: "text-center py-16",
|
|
119
118
|
"aria-live": "polite",
|
|
120
119
|
"aria-busy": "true",
|
|
121
|
-
children: [/* @__PURE__ */ p(
|
|
122
|
-
className: "
|
|
120
|
+
children: [/* @__PURE__ */ p(n, {
|
|
121
|
+
className: "size-8 mx-auto text-text-secondary animate-spin",
|
|
123
122
|
"aria-hidden": "true"
|
|
124
123
|
}), /* @__PURE__ */ p("p", {
|
|
125
124
|
className: "mt-4 text-text-secondary",
|
|
@@ -133,7 +132,7 @@ function v() {
|
|
|
133
132
|
onClick: () => y("/auth/users"),
|
|
134
133
|
className: "mb-6 flex items-center gap-2 text-text-link hover:underline font-medium",
|
|
135
134
|
children: [/* @__PURE__ */ p("svg", {
|
|
136
|
-
className: "
|
|
135
|
+
className: "size-4",
|
|
137
136
|
fill: "none",
|
|
138
137
|
viewBox: "0 0 24 24",
|
|
139
138
|
stroke: "currentColor",
|
|
@@ -148,8 +147,8 @@ function v() {
|
|
|
148
147
|
}), /* @__PURE__ */ m("div", {
|
|
149
148
|
role: "alert",
|
|
150
149
|
className: "flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border",
|
|
151
|
-
children: [/* @__PURE__ */ p(
|
|
152
|
-
className: "
|
|
150
|
+
children: [/* @__PURE__ */ p(t, {
|
|
151
|
+
className: "size-5 text-status-error-text flex-shrink-0 mt-0.5",
|
|
153
152
|
"aria-hidden": "true"
|
|
154
153
|
}), /* @__PURE__ */ p("p", {
|
|
155
154
|
className: "text-sm text-status-error-text",
|
|
@@ -179,7 +178,7 @@ function v() {
|
|
|
179
178
|
onClick: () => y("/auth/users"),
|
|
180
179
|
className: "flex items-center gap-2 text-text-link hover:underline font-medium",
|
|
181
180
|
children: [/* @__PURE__ */ p("svg", {
|
|
182
|
-
className: "
|
|
181
|
+
className: "size-4",
|
|
183
182
|
fill: "none",
|
|
184
183
|
viewBox: "0 0 24 24",
|
|
185
184
|
stroke: "currentColor",
|
|
@@ -197,11 +196,11 @@ function v() {
|
|
|
197
196
|
disabled: V,
|
|
198
197
|
"aria-label": x("auth.userDetails.exportAriaLabel", "Export user data"),
|
|
199
198
|
className: "flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium",
|
|
200
|
-
children: V ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(
|
|
201
|
-
className: "
|
|
199
|
+
children: V ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(n, {
|
|
200
|
+
className: "size-4 animate-spin",
|
|
202
201
|
"aria-hidden": "true"
|
|
203
202
|
}), x("auth.userDetails.exporting", "Exporting...")] }) : /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("svg", {
|
|
204
|
-
className: "
|
|
203
|
+
className: "size-4",
|
|
205
204
|
fill: "none",
|
|
206
205
|
viewBox: "0 0 24 24",
|
|
207
206
|
stroke: "currentColor",
|
|
@@ -218,8 +217,8 @@ function v() {
|
|
|
218
217
|
H && /* @__PURE__ */ m("div", {
|
|
219
218
|
role: "alert",
|
|
220
219
|
className: "mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border",
|
|
221
|
-
children: [/* @__PURE__ */ p(
|
|
222
|
-
className: "
|
|
220
|
+
children: [/* @__PURE__ */ p(t, {
|
|
221
|
+
className: "size-5 text-status-error-text flex-shrink-0 mt-0.5",
|
|
223
222
|
"aria-hidden": "true"
|
|
224
223
|
}), /* @__PURE__ */ p("p", {
|
|
225
224
|
className: "text-sm text-status-error-text",
|
|
@@ -244,7 +243,7 @@ function v() {
|
|
|
244
243
|
/* @__PURE__ */ m("div", {
|
|
245
244
|
className: "flex items-center gap-4 mb-8",
|
|
246
245
|
children: [/* @__PURE__ */ p("div", {
|
|
247
|
-
className: "auth-brand-badge flex
|
|
246
|
+
className: "auth-brand-badge flex size-20 items-center justify-center rounded-full text-3xl font-semibold",
|
|
248
247
|
"aria-hidden": "true",
|
|
249
248
|
children: (A.name || A.username || A.email).charAt(0).toUpperCase()
|
|
250
249
|
}), /* @__PURE__ */ m("div", { children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserDetailsPage.js","names":[],"sources":["../../../src/auth/pages/UserDetailsPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useNavigate, useParams } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useAuthPermissions, useUserOperations } from \"../hooks\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\nimport type { User, UserRole, UserStatus } from \"../types\";\n\nconst ALLOWED_ROLES: UserRole[] = [\"SUPER_ADMIN\", \"TENANT_ADMIN\", \"ORG_ADMIN\"];\n\nconst getRoleColor = (role: UserRole) => {\n switch (role) {\n case \"SUPER_ADMIN\":\n return \"bg-accent-purple-subtle text-accent-purple\";\n case \"TENANT_ADMIN\":\n return \"bg-status-info-bg-subtle text-status-info-text\";\n case \"ORG_ADMIN\":\n return \"bg-accent-indigo-subtle text-accent-indigo\";\n case \"WORKSPACE_ADMIN\":\n return \"bg-accent-cyan-subtle text-accent-cyan\";\n case \"MEMBER\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"GUEST\":\n return \"bg-bg-sunken text-text-primary\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n};\n\nconst getStatusColor = (status: UserStatus) => {\n switch (status) {\n case \"ACTIVE\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"INACTIVE\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"SUSPENDED\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"DELETED\":\n return \"bg-status-error-bg-subtle text-status-error-text\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n};\n\n/**\n * User details page (admin only).\n *\n * Route: /auth/users/:id\n *\n * Renders the full profile of a single user, with admin actions\n * (edit, deactivate, hard-delete, export). Reuses the same getUser query\n * that UsersPage relies on.\n */\nexport function UserDetailsPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const {\n canViewUsers,\n canDeleteUsers,\n isLoading: permissionsLoading,\n } = useAuthPermissions();\n const { getUser, deleteUser, hardDeleteUser } = useUserOperations();\n\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingAction, setPendingAction] = useState<\n \"deactivate\" | \"hard-delete\" | null\n >(null);\n\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation();\n const [exportError, setExportError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n if (!id) {\n setError(\"Missing user id\");\n setLoading(false);\n return;\n }\n if (!isAuthenticated) {\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n getUser(id)\n .then((fetched) => {\n if (cancelled) return;\n if (!fetched) {\n setError(tr(\"auth.userDetails.userNotFound\", \"User not found\"));\n } else {\n setUser(fetched);\n }\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setError(\n err instanceof Error ? err.message : \"Failed to fetch user details\",\n );\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n // tr is stable within the render, id/getUser/isAuthenticated drive the effect\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [id, getUser, isAuthenticated]);\n\n const handleDeactivate = async () => {\n if (!user) return;\n try {\n const result = await deleteUser(user.id);\n if (result.success) {\n setSuccessMessage(\n tr(\n \"auth.userDetails.userDeactivated\",\n 'User \"{email}\" has been deactivated.',\n ).replace(\"{email}\", user.email),\n );\n setPendingAction(null);\n // Refresh\n const fresh = await getUser(user.id);\n if (fresh) setUser(fresh);\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to delete user\");\n }\n };\n\n const handleHardDelete = async () => {\n if (!user) return;\n try {\n const result = await hardDeleteUser(user.id);\n if (result.success) {\n setPendingAction(null);\n navigate(\"/auth/users\");\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(\n err instanceof Error ? err.message : \"Failed to hard delete user\",\n );\n }\n };\n\n const handleExport = async () => {\n if (!user) return;\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: `User Data Export - ${user.email}`,\n entityTypes: [\n \"user_profile\",\n \"user_sessions\",\n \"permission_audit_logs\",\n ],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n // Auth + role guards\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.userDetails.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.userDetails.signInToView\",\n \"Please sign in to view user details\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (currentUser && !ALLOWED_ROLES.includes(currentUser.role)) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\n \"auth.userDetails.notAllowed\",\n \"You are not allowed to access this page\",\n )}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.userDetails.notAllowedDesc\",\n \"This page is restricted to administrators only.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (permissionsLoading || loading) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16\" aria-live=\"polite\" aria-busy=\"true\">\n <SpinnerIcon\n className=\"w-8 h-8 mx-auto text-text-secondary animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-4 text-text-secondary\">\n {tr(\"auth.userDetails.loading\", \"Loading user...\")}\n </p>\n </div>\n </div>\n );\n }\n\n if (!canViewUsers) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.userDetails.adminRequired\", \"Admin Access Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.userDetails.adminRequiredDesc\",\n \"You need administrator privileges to view user details.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (error || !user) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\n className=\"mb-6 flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"w-4 h-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n <div\n role=\"alert\"\n className=\"flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"w-5 h-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">\n {error ?? tr(\"auth.userDetails.userNotFound\", \"User not found\")}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <ConfirmActionDialog\n open={pendingAction !== null}\n onOpenChange={(open) => {\n if (!open) setPendingAction(null);\n }}\n title={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteTitle\", \"Permanently delete user?\")\n : tr(\"auth.userDetails.deactivateTitle\", \"Deactivate user?\")\n }\n description={\n pendingAction === \"hard-delete\"\n ? tr(\n \"auth.userDetails.hardDeleteDesc\",\n 'This permanently deletes \"{email}\" and cannot be undone.',\n ).replace(\"{email}\", user.email)\n : tr(\n \"auth.userDetails.deactivateDesc\",\n '\"{email}\" will be prevented from signing in until reactivated.',\n ).replace(\"{email}\", user.email)\n }\n confirmText={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteConfirm\", \"Delete permanently\")\n : tr(\"auth.userDetails.deactivateConfirm\", \"Deactivate user\")\n }\n variant={pendingAction === \"hard-delete\" ? \"danger\" : \"warning\"}\n onConfirm={async () => {\n if (pendingAction === \"hard-delete\") await handleHardDelete();\n else if (pendingAction === \"deactivate\") await handleDeactivate();\n }}\n />\n\n {/* Header row */}\n <div className=\"flex items-center justify-between mb-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\n className=\"flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"w-4 h-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n\n {/* Export button */}\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\n \"auth.userDetails.exportAriaLabel\",\n \"Export user data\",\n )}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon\n className=\"w-4 h-4 animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\"auth.userDetails.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"w-4 h-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.userDetails.exportButton\", \"Export User Data\")}\n </>\n )}\n </button>\n </div>\n\n {/* Export error */}\n {exportError && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"w-5 h-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n {successMessage && (\n <div className=\"mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\">\n <p className=\"text-status-success-text\">{successMessage}</p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"mt-2 text-sm text-status-success-text hover:underline\"\n >\n {tr(\"auth.userDetails.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n <div className=\"flex items-center gap-4 mb-8\">\n <div\n className=\"auth-brand-badge flex h-20 w-20 items-center justify-center rounded-full text-3xl font-semibold\"\n aria-hidden=\"true\"\n >\n {(user.name || user.username || user.email).charAt(0).toUpperCase()}\n </div>\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {user.name || user.username}\n </h1>\n <p className=\"text-text-secondary\" data-testid=\"user-email\">\n {user.email}\n </p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getRoleColor(user.role)}`}\n >\n {user.role.replace(\"_\", \" \")}\n </span>\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getStatusColor(user.status)}`}\n >\n {user.status}\n </span>\n </div>\n </div>\n </div>\n\n <dl className=\"grid grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.username\", \"Username\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.username}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.email\", \"Email\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.email}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.emailVerified\", \"Email Verified\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.emailVerified ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-status-warning-text\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.mfaEnabled\", \"MFA Enabled\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.mfaEnabled ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n {user.phoneNumber && (\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.phoneNumber\", \"Phone Number\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.phoneNumber}\n </dd>\n </div>\n )}\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastLogin\", \"Last Login\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.lastLoginAt\n ? new Date(user.lastLoginAt).toLocaleString()\n : tr(\"auth.userDetails.never\", \"Never\")}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.created\", \"Created\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.createdAt).toLocaleString()}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastUpdated\", \"Last Updated\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.updatedAt).toLocaleString()}\n </dd>\n </div>\n </dl>\n\n <div className=\"mt-8 pt-6 border-t border-border-subtle flex gap-3\">\n {user.status !== \"DELETED\" && canDeleteUsers && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"deactivate\")}\n className=\"px-4 py-2.5 rounded-xl border border-status-error-border text-status-error-text font-medium hover:bg-status-error-bg-subtle transition-colors\"\n >\n {tr(\"auth.userDetails.deactivateUser\", \"Deactivate User\")}\n </button>\n )}\n {canDeleteUsers &&\n user.status === \"DELETED\" &&\n currentUser?.role === \"SUPER_ADMIN\" && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"hard-delete\")}\n className=\"px-4 py-2.5 rounded-xl bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover transition-colors\"\n >\n {tr(\"auth.userDetails.permanentlyDelete\", \"Permanently Delete\")}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"UserDetailsPage.js","names":[],"sources":["../../../src/auth/pages/UserDetailsPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useNavigate, useParams } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useAuthPermissions } from \"../hooks/useAuthPermissions\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\nimport type { User, UserRole, UserStatus } from \"../types\";\n\nconst ALLOWED_ROLES: UserRole[] = [\"SUPER_ADMIN\", \"TENANT_ADMIN\", \"ORG_ADMIN\"];\n\nconst getRoleColor = (role: UserRole) => {\n switch (role) {\n case \"SUPER_ADMIN\":\n return \"bg-accent-purple-subtle text-accent-purple\";\n case \"TENANT_ADMIN\":\n return \"bg-status-info-bg-subtle text-status-info-text\";\n case \"ORG_ADMIN\":\n return \"bg-accent-indigo-subtle text-accent-indigo\";\n case \"WORKSPACE_ADMIN\":\n return \"bg-accent-cyan-subtle text-accent-cyan\";\n case \"MEMBER\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"GUEST\":\n return \"bg-bg-sunken text-text-primary\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n};\n\nconst getStatusColor = (status: UserStatus) => {\n switch (status) {\n case \"ACTIVE\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"INACTIVE\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"SUSPENDED\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"DELETED\":\n return \"bg-status-error-bg-subtle text-status-error-text\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n};\n\n/**\n * User details page (admin only).\n *\n * Route: /auth/users/:id\n *\n * Renders the full profile of a single user, with admin actions\n * (edit, deactivate, hard-delete, export). Reuses the same getUser query\n * that UsersPage relies on.\n */\nexport function UserDetailsPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const {\n canViewUsers,\n canDeleteUsers,\n isLoading: permissionsLoading,\n } = useAuthPermissions();\n const { getUser, deleteUser, hardDeleteUser } = useUserOperations();\n\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingAction, setPendingAction] = useState<\n \"deactivate\" | \"hard-delete\" | null\n >(null);\n\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation();\n const [exportError, setExportError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n if (!id) {\n setError(\"Missing user id\");\n setLoading(false);\n return;\n }\n if (!isAuthenticated) {\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n getUser(id)\n .then((fetched) => {\n if (cancelled) return;\n if (!fetched) {\n setError(tr(\"auth.userDetails.userNotFound\", \"User not found\"));\n } else {\n setUser(fetched);\n }\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setError(\n err instanceof Error ? err.message : \"Failed to fetch user details\",\n );\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n // tr is stable within the render, id/getUser/isAuthenticated drive the effect\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [id, getUser, isAuthenticated]);\n\n const handleDeactivate = async () => {\n if (!user) return;\n try {\n const result = await deleteUser(user.id);\n if (result.success) {\n setSuccessMessage(\n tr(\n \"auth.userDetails.userDeactivated\",\n 'User \"{email}\" has been deactivated.',\n ).replace(\"{email}\", user.email),\n );\n setPendingAction(null);\n // Refresh\n const fresh = await getUser(user.id);\n if (fresh) setUser(fresh);\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to delete user\");\n }\n };\n\n const handleHardDelete = async () => {\n if (!user) return;\n try {\n const result = await hardDeleteUser(user.id);\n if (result.success) {\n setPendingAction(null);\n navigate(\"/auth/users\");\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(\n err instanceof Error ? err.message : \"Failed to hard delete user\",\n );\n }\n };\n\n const handleExport = async () => {\n if (!user) return;\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: `User Data Export - ${user.email}`,\n entityTypes: [\n \"user_profile\",\n \"user_sessions\",\n \"permission_audit_logs\",\n ],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n // Auth + role guards\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.userDetails.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.userDetails.signInToView\",\n \"Please sign in to view user details\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (currentUser && !ALLOWED_ROLES.includes(currentUser.role)) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\n \"auth.userDetails.notAllowed\",\n \"You are not allowed to access this page\",\n )}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.userDetails.notAllowedDesc\",\n \"This page is restricted to administrators only.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (permissionsLoading || loading) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16\" aria-live=\"polite\" aria-busy=\"true\">\n <SpinnerIcon\n className=\"size-8 mx-auto text-text-secondary animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-4 text-text-secondary\">\n {tr(\"auth.userDetails.loading\", \"Loading user...\")}\n </p>\n </div>\n </div>\n );\n }\n\n if (!canViewUsers) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.userDetails.adminRequired\", \"Admin Access Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.userDetails.adminRequiredDesc\",\n \"You need administrator privileges to view user details.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (error || !user) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\n className=\"mb-6 flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n <div\n role=\"alert\"\n className=\"flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">\n {error ?? tr(\"auth.userDetails.userNotFound\", \"User not found\")}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <ConfirmActionDialog\n open={pendingAction !== null}\n onOpenChange={(open) => {\n if (!open) setPendingAction(null);\n }}\n title={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteTitle\", \"Permanently delete user?\")\n : tr(\"auth.userDetails.deactivateTitle\", \"Deactivate user?\")\n }\n description={\n pendingAction === \"hard-delete\"\n ? tr(\n \"auth.userDetails.hardDeleteDesc\",\n 'This permanently deletes \"{email}\" and cannot be undone.',\n ).replace(\"{email}\", user.email)\n : tr(\n \"auth.userDetails.deactivateDesc\",\n '\"{email}\" will be prevented from signing in until reactivated.',\n ).replace(\"{email}\", user.email)\n }\n confirmText={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteConfirm\", \"Delete permanently\")\n : tr(\"auth.userDetails.deactivateConfirm\", \"Deactivate user\")\n }\n variant={pendingAction === \"hard-delete\" ? \"danger\" : \"warning\"}\n onConfirm={async () => {\n if (pendingAction === \"hard-delete\") await handleHardDelete();\n else if (pendingAction === \"deactivate\") await handleDeactivate();\n }}\n />\n\n {/* Header row */}\n <div className=\"flex items-center justify-between mb-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\n className=\"flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n\n {/* Export button */}\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\n \"auth.userDetails.exportAriaLabel\",\n \"Export user data\",\n )}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon\n className=\"size-4 animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\"auth.userDetails.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.userDetails.exportButton\", \"Export User Data\")}\n </>\n )}\n </button>\n </div>\n\n {/* Export error */}\n {exportError && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n {successMessage && (\n <div className=\"mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\">\n <p className=\"text-status-success-text\">{successMessage}</p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"mt-2 text-sm text-status-success-text hover:underline\"\n >\n {tr(\"auth.userDetails.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n <div className=\"flex items-center gap-4 mb-8\">\n <div\n className=\"auth-brand-badge flex size-20 items-center justify-center rounded-full text-3xl font-semibold\"\n aria-hidden=\"true\"\n >\n {(user.name || user.username || user.email).charAt(0).toUpperCase()}\n </div>\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {user.name || user.username}\n </h1>\n <p className=\"text-text-secondary\" data-testid=\"user-email\">\n {user.email}\n </p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getRoleColor(user.role)}`}\n >\n {user.role.replace(\"_\", \" \")}\n </span>\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getStatusColor(user.status)}`}\n >\n {user.status}\n </span>\n </div>\n </div>\n </div>\n\n <dl className=\"grid grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.username\", \"Username\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.username}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.email\", \"Email\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.email}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.emailVerified\", \"Email Verified\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.emailVerified ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-status-warning-text\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.mfaEnabled\", \"MFA Enabled\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.mfaEnabled ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n {user.phoneNumber && (\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.phoneNumber\", \"Phone Number\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.phoneNumber}\n </dd>\n </div>\n )}\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastLogin\", \"Last Login\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.lastLoginAt\n ? new Date(user.lastLoginAt).toLocaleString()\n : tr(\"auth.userDetails.never\", \"Never\")}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.created\", \"Created\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.createdAt).toLocaleString()}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastUpdated\", \"Last Updated\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.updatedAt).toLocaleString()}\n </dd>\n </div>\n </dl>\n\n <div className=\"mt-8 pt-6 border-t border-border-subtle flex gap-3\">\n {user.status !== \"DELETED\" && canDeleteUsers && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"deactivate\")}\n className=\"px-4 py-2.5 rounded-xl border border-status-error-border text-status-error-text font-medium hover:bg-status-error-bg-subtle transition-colors\"\n >\n {tr(\"auth.userDetails.deactivateUser\", \"Deactivate User\")}\n </button>\n )}\n {canDeleteUsers &&\n user.status === \"DELETED\" &&\n currentUser?.role === \"SUPER_ADMIN\" && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"hard-delete\")}\n className=\"px-4 py-2.5 rounded-xl bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover transition-colors\"\n >\n {tr(\"auth.userDetails.permanentlyDelete\", \"Permanently Delete\")}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { useAuthStore as e } from "../store/authStore.js";
|
|
2
|
-
import {
|
|
3
|
-
import "../
|
|
4
|
-
import {
|
|
5
|
-
import { AlertIcon as r, CheckIcon as i, EmailIcon as a, SpinnerIcon as o, UserIcon as s } from "../components/AuthIcons.js";
|
|
2
|
+
import { AuthLayout as t } from "../components/AuthLayout.js";
|
|
3
|
+
import { AlertIcon as n, CheckIcon as r, EmailIcon as i, SpinnerIcon as a, UserIcon as o } from "../components/AuthIcons.js";
|
|
4
|
+
import { useUserOperations as s } from "../hooks/useUserOperations.js";
|
|
6
5
|
import { useCompleteAvatarUploadMutation as c, useInitiateAvatarUploadMutation as l } from "../../generated/wspace-operations.js";
|
|
7
6
|
import { useEffect as u, useId as d, useRef as f, useState as p } from "react";
|
|
8
7
|
import { useNavigate as m } from "react-router-dom";
|
|
@@ -15,7 +14,7 @@ function x() {
|
|
|
15
14
|
let { t: x } = h(), S = (e, t) => {
|
|
16
15
|
let n = x(e);
|
|
17
16
|
return n === e ? t : n;
|
|
18
|
-
}, C = m(), w = b(), T = e((e) => e.isAuthenticated), E = e((e) => e.user), D = e((e) => e.setUser), { updateCurrentUserProfile: O, isLoading: k } =
|
|
17
|
+
}, C = m(), w = b(), T = e((e) => e.isAuthenticated), E = e((e) => e.user), D = e((e) => e.setUser), { updateCurrentUserProfile: O, isLoading: k } = s(), A = d(), j = d(), M = d(), N = d(), P = d(), F = e((e) => e.currentWorkspaceId), I = y("workspace"), [L] = l({ client: I }), [R] = c({ client: I }), z = f(null), [B, V] = p(!1), H = async (e) => {
|
|
19
18
|
let t = e.target.files?.[0];
|
|
20
19
|
if (!t) return;
|
|
21
20
|
if (!t.type.startsWith("image/")) {
|
|
@@ -73,15 +72,15 @@ function x() {
|
|
|
73
72
|
name: E.name || "",
|
|
74
73
|
phoneNumber: E.phoneNumber || ""
|
|
75
74
|
});
|
|
76
|
-
}, [E]), !T || !E ? /* @__PURE__ */ _(
|
|
75
|
+
}, [E]), !T || !E ? /* @__PURE__ */ _(t, {
|
|
77
76
|
title: S("auth.profile.title", "Profile"),
|
|
78
77
|
subtitle: S("auth.profile.subtitle", "View and manage your profile"),
|
|
79
78
|
children: /* @__PURE__ */ v("div", {
|
|
80
79
|
className: "text-center py-12",
|
|
81
80
|
children: [
|
|
82
81
|
/* @__PURE__ */ _("div", {
|
|
83
|
-
className: "
|
|
84
|
-
children: /* @__PURE__ */ _(
|
|
82
|
+
className: "size-16 mx-auto mb-4 rounded-full bg-[var(--color-status-warning-bg-subtle)] flex items-center justify-center",
|
|
83
|
+
children: /* @__PURE__ */ _(n, { className: "size-8 text-[var(--color-status-warning-text)]" })
|
|
85
84
|
}),
|
|
86
85
|
/* @__PURE__ */ _("h2", {
|
|
87
86
|
className: "text-xl font-semibold text-[var(--color-text-primary)] mb-2",
|
|
@@ -92,6 +91,7 @@ function x() {
|
|
|
92
91
|
children: S("auth.profile.signInToView", "Please sign in to view your profile")
|
|
93
92
|
}),
|
|
94
93
|
/* @__PURE__ */ _("button", {
|
|
94
|
+
type: "button",
|
|
95
95
|
onClick: () => C("/auth/login"),
|
|
96
96
|
className: "auth-primary-button px-6 py-3 font-semibold",
|
|
97
97
|
children: S("auth.profile.signIn", "Sign In")
|
|
@@ -114,12 +114,13 @@ function x() {
|
|
|
114
114
|
Y && /* @__PURE__ */ v("div", {
|
|
115
115
|
className: "mb-6 flex items-center gap-3 p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]",
|
|
116
116
|
children: [
|
|
117
|
-
/* @__PURE__ */ _(
|
|
117
|
+
/* @__PURE__ */ _(r, { className: "size-5 text-[var(--color-status-success-text)] flex-shrink-0" }),
|
|
118
118
|
/* @__PURE__ */ _("p", {
|
|
119
119
|
className: "text-[var(--color-status-success-text)]",
|
|
120
120
|
children: Y
|
|
121
121
|
}),
|
|
122
122
|
/* @__PURE__ */ _("button", {
|
|
123
|
+
type: "button",
|
|
123
124
|
onClick: () => X(null),
|
|
124
125
|
className: "ml-auto text-sm text-[var(--color-status-success-text)] hover:underline transition-colors duration-200",
|
|
125
126
|
children: S("auth.profile.dismiss", "Dismiss")
|
|
@@ -129,7 +130,7 @@ function x() {
|
|
|
129
130
|
q && /* @__PURE__ */ v("div", {
|
|
130
131
|
className: "mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]",
|
|
131
132
|
children: [
|
|
132
|
-
/* @__PURE__ */ _(
|
|
133
|
+
/* @__PURE__ */ _(n, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }),
|
|
133
134
|
/* @__PURE__ */ _("div", {
|
|
134
135
|
className: "flex-1",
|
|
135
136
|
children: /* @__PURE__ */ _("p", {
|
|
@@ -138,6 +139,7 @@ function x() {
|
|
|
138
139
|
})
|
|
139
140
|
}),
|
|
140
141
|
/* @__PURE__ */ _("button", {
|
|
142
|
+
type: "button",
|
|
141
143
|
onClick: () => J(null),
|
|
142
144
|
className: "text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200",
|
|
143
145
|
children: S("auth.profile.dismiss", "Dismiss")
|
|
@@ -156,9 +158,9 @@ function x() {
|
|
|
156
158
|
E.avatar ? /* @__PURE__ */ _("img", {
|
|
157
159
|
src: E.avatar,
|
|
158
160
|
alt: S("auth.profile.avatarAlt", "Profile picture"),
|
|
159
|
-
className: "
|
|
161
|
+
className: "size-20 rounded-full object-cover shadow-lg"
|
|
160
162
|
}) : /* @__PURE__ */ _("div", {
|
|
161
|
-
className: "
|
|
163
|
+
className: "size-20 rounded-full bg-bg-surface/20 backdrop-blur-sm flex items-center justify-center text-white text-3xl font-semibold shadow-lg",
|
|
162
164
|
children: (E.name || E.username || E.email).charAt(0).toUpperCase()
|
|
163
165
|
}),
|
|
164
166
|
/* @__PURE__ */ _("button", {
|
|
@@ -167,8 +169,8 @@ function x() {
|
|
|
167
169
|
disabled: B,
|
|
168
170
|
"aria-label": S("auth.profile.changeAvatar", "Change profile picture"),
|
|
169
171
|
className: "absolute inset-0 rounded-full flex items-center justify-center bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 cursor-pointer",
|
|
170
|
-
children: B ? /* @__PURE__ */ _(
|
|
171
|
-
className: "
|
|
172
|
+
children: B ? /* @__PURE__ */ _(a, { className: "size-6 text-white animate-spin" }) : /* @__PURE__ */ v("svg", {
|
|
173
|
+
className: "size-6 text-white",
|
|
172
174
|
fill: "none",
|
|
173
175
|
viewBox: "0 0 24 24",
|
|
174
176
|
stroke: "currentColor",
|
|
@@ -203,7 +205,7 @@ function x() {
|
|
|
203
205
|
}),
|
|
204
206
|
/* @__PURE__ */ v("p", {
|
|
205
207
|
className: "text-white/80 flex items-center gap-2",
|
|
206
|
-
children: [/* @__PURE__ */ _(
|
|
208
|
+
children: [/* @__PURE__ */ _(i, { className: "size-4" }), E.email]
|
|
207
209
|
}),
|
|
208
210
|
/* @__PURE__ */ v("div", {
|
|
209
211
|
className: "flex items-center gap-2 mt-2",
|
|
@@ -212,7 +214,7 @@ function x() {
|
|
|
212
214
|
children: E.role?.replace("_", " ") || S("auth.profile.member", "Member")
|
|
213
215
|
}), E.emailVerified && /* @__PURE__ */ v("span", {
|
|
214
216
|
className: "px-2.5 py-1 rounded-full text-xs font-medium bg-status-success-text/30 flex items-center gap-1",
|
|
215
|
-
children: [/* @__PURE__ */ _(
|
|
217
|
+
children: [/* @__PURE__ */ _(r, { className: "size-3" }), S("auth.profile.verified", "Verified")]
|
|
216
218
|
})]
|
|
217
219
|
})
|
|
218
220
|
]
|
|
@@ -247,8 +249,8 @@ function x() {
|
|
|
247
249
|
className: "relative",
|
|
248
250
|
children: [/* @__PURE__ */ _("div", {
|
|
249
251
|
className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
|
|
250
|
-
children: /* @__PURE__ */ _(
|
|
251
|
-
className: "
|
|
252
|
+
children: /* @__PURE__ */ _(o, {
|
|
253
|
+
className: "size-5 text-[var(--color-text-tertiary)]",
|
|
252
254
|
"aria-hidden": "true"
|
|
253
255
|
})
|
|
254
256
|
}), /* @__PURE__ */ _("input", {
|
|
@@ -333,7 +335,7 @@ function x() {
|
|
|
333
335
|
type: "submit",
|
|
334
336
|
disabled: k,
|
|
335
337
|
className: "auth-primary-button flex items-center gap-2 px-6 py-3 font-medium",
|
|
336
|
-
children: k ? /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _(
|
|
338
|
+
children: k ? /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _(a, { className: "size-4 animate-spin" }), S("auth.profile.saving", "Saving...")] }) : S("auth.profile.saveChanges", "Save Changes")
|
|
337
339
|
}), /* @__PURE__ */ _("button", {
|
|
338
340
|
type: "button",
|
|
339
341
|
onClick: () => {
|
|
@@ -401,7 +403,7 @@ function x() {
|
|
|
401
403
|
className: "text-[var(--color-text-primary)] font-medium flex items-center gap-2",
|
|
402
404
|
children: [E.email, E.emailVerified && /* @__PURE__ */ v("span", {
|
|
403
405
|
className: "inline-flex items-center gap-1 text-[var(--color-status-success-text)] text-xs",
|
|
404
|
-
children: [/* @__PURE__ */ _(
|
|
406
|
+
children: [/* @__PURE__ */ _(r, { className: "size-3" }), S("auth.profile.verified", "Verified")]
|
|
405
407
|
})]
|
|
406
408
|
})]
|
|
407
409
|
}),
|
|
@@ -434,7 +436,7 @@ function x() {
|
|
|
434
436
|
className: "text-[var(--color-text-primary)] font-medium",
|
|
435
437
|
children: E.mfaEnabled ? /* @__PURE__ */ v("span", {
|
|
436
438
|
className: "text-[var(--color-status-success-text)] flex items-center gap-1",
|
|
437
|
-
children: [/* @__PURE__ */ _(
|
|
439
|
+
children: [/* @__PURE__ */ _(r, { className: "size-4" }), S("auth.profile.enabled", "Enabled")]
|
|
438
440
|
}) : /* @__PURE__ */ _("span", {
|
|
439
441
|
className: "text-[var(--color-status-warning-text)]",
|
|
440
442
|
children: S("auth.profile.notEnabled", "Not Enabled")
|
|
@@ -465,10 +467,12 @@ function x() {
|
|
|
465
467
|
}), /* @__PURE__ */ v("div", {
|
|
466
468
|
className: "flex gap-3 pt-6 mt-6 border-t border-[var(--color-border-default)]",
|
|
467
469
|
children: [/* @__PURE__ */ _("button", {
|
|
470
|
+
type: "button",
|
|
468
471
|
onClick: () => W(!0),
|
|
469
472
|
className: "auth-primary-button px-6 py-3 font-medium",
|
|
470
473
|
children: S("auth.profile.editProfile", "Edit Profile")
|
|
471
474
|
}), /* @__PURE__ */ _("button", {
|
|
475
|
+
type: "button",
|
|
472
476
|
onClick: () => C("/auth/security"),
|
|
473
477
|
className: "px-6 py-3 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200",
|
|
474
478
|
children: S("auth.profile.securitySettings", "Security Settings")
|
|
@@ -479,13 +483,14 @@ function x() {
|
|
|
479
483
|
/* @__PURE__ */ v("div", {
|
|
480
484
|
className: "mt-8 grid grid-cols-1 md:grid-cols-3 gap-4",
|
|
481
485
|
children: [/* @__PURE__ */ v("button", {
|
|
486
|
+
type: "button",
|
|
482
487
|
onClick: () => C("/auth/mfa"),
|
|
483
488
|
className: "p-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:border-[var(--color-action-primary-border)] transition-colors duration-200 text-left group",
|
|
484
489
|
children: [
|
|
485
490
|
/* @__PURE__ */ _("div", {
|
|
486
|
-
className: "
|
|
491
|
+
className: "size-10 rounded-lg bg-[var(--color-bg-accent)] flex items-center justify-center mb-3 group-hover:bg-[var(--color-action-primary-bg-subtle)] transition-colors duration-200",
|
|
487
492
|
children: /* @__PURE__ */ _("svg", {
|
|
488
|
-
className: "
|
|
493
|
+
className: "size-5 text-[var(--color-text-link)]",
|
|
489
494
|
fill: "none",
|
|
490
495
|
viewBox: "0 0 24 24",
|
|
491
496
|
stroke: "currentColor",
|
|
@@ -507,13 +512,14 @@ function x() {
|
|
|
507
512
|
})
|
|
508
513
|
]
|
|
509
514
|
}), /* @__PURE__ */ v("button", {
|
|
515
|
+
type: "button",
|
|
510
516
|
onClick: () => C("/auth/api-keys"),
|
|
511
517
|
className: "p-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:border-[var(--color-action-secondary-border)] transition-colors duration-200 text-left group",
|
|
512
518
|
children: [
|
|
513
519
|
/* @__PURE__ */ _("div", {
|
|
514
|
-
className: "
|
|
520
|
+
className: "size-10 rounded-lg bg-[var(--color-action-secondary-bg-subtle)] flex items-center justify-center mb-3 group-hover:bg-[var(--color-action-secondary-bg-subtle)] transition-colors duration-200",
|
|
515
521
|
children: /* @__PURE__ */ _("svg", {
|
|
516
|
-
className: "
|
|
522
|
+
className: "size-5 text-[var(--color-action-secondary-text)]",
|
|
517
523
|
fill: "none",
|
|
518
524
|
viewBox: "0 0 24 24",
|
|
519
525
|
stroke: "currentColor",
|