@burdenoff/microfe-auth 2026.527.2 → 2026.528.1
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/components/CaptchaV2.js.map +1 -1
- package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
- package/dist/auth/components/workspace/WorkspaceSelector.js +103 -93
- package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
- package/dist/auth/hooks/useContextSwitcherData.js +1 -1
- package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
- package/dist/auth/hooks/useMfaOperations.js +22 -18
- package/dist/auth/hooks/useMfaOperations.js.map +1 -1
- package/dist/auth/hooks/useMyWorkspaces.js +1 -1
- package/dist/auth/hooks/useMyWorkspaces.js.map +1 -1
- package/dist/auth/hooks/useWorkspaceToken.js +24 -24
- package/dist/auth/hooks/useWorkspaceToken.js.map +1 -1
- package/dist/auth/pages/AuditLogPage.js +3 -5
- package/dist/auth/pages/AuditLogPage.js.map +1 -1
- package/dist/auth/pages/EmailVerifyPage.js +21 -21
- package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
- package/dist/auth/pages/LoginPage.js +195 -191
- package/dist/auth/pages/LoginPage.js.map +1 -1
- package/dist/auth/pages/MagicLinkPage.js +92 -95
- package/dist/auth/pages/MagicLinkPage.js.map +1 -1
- package/dist/auth/pages/MfaSetupPage.js.map +1 -1
- package/dist/auth/pages/MfaVerifyPage.js +3 -1
- package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
- package/dist/auth/pages/OAuthCallbackPage.js +6 -1
- package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
- package/dist/auth/pages/PasswordResetPage.js +3 -0
- package/dist/auth/pages/PasswordResetPage.js.map +1 -1
- package/dist/auth/pages/ReactivatePage.js +2 -0
- package/dist/auth/pages/ReactivatePage.js.map +1 -1
- package/dist/auth/pages/RegisterPage.js +5 -0
- package/dist/auth/pages/RegisterPage.js.map +1 -1
- package/dist/auth/pages/SSORelayPage.js +39 -33
- package/dist/auth/pages/SSORelayPage.js.map +1 -1
- package/dist/auth/pages/SecurityPage.js +7 -6
- package/dist/auth/pages/SecurityPage.js.map +1 -1
- package/dist/auth/pages/SessionsPage.js +5 -8
- package/dist/auth/pages/SessionsPage.js.map +1 -1
- package/dist/auth/pages/SsoCallbackPage.js +11 -8
- package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
- package/dist/auth/pages/SsoConfigPage.js +10 -1
- package/dist/auth/pages/SsoConfigPage.js.map +1 -1
- package/dist/auth/pages/SsoLoginPage.js +1 -0
- package/dist/auth/pages/SsoLoginPage.js.map +1 -1
- package/dist/auth/pages/UserDetailsPage.js.map +1 -1
- package/dist/auth/pages/UserProfilePage.js +7 -1
- package/dist/auth/pages/UserProfilePage.js.map +1 -1
- package/dist/auth/pages/UsersPage.js +10 -3
- package/dist/auth/pages/UsersPage.js.map +1 -1
- package/dist/auth/providers/AuthProvider.js +8 -7
- package/dist/auth/providers/AuthProvider.js.map +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={`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":";;;;;;;;AAcA,SAAgB,IAAe;CAC7B,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAGnC,IAAW,GAAa,EACxB,CAAC,KAAgB,GAAiB,EAClC,IAAgB,EAAa,IAAI,SAAS,EAC1C,IAAgB,EAAa,IAAI,SAAS,EAC1C,EACJ,qBACA,8BACA,wBACA,2BACE,EAAiB,KAAiB,KAAA,EAAU,EAE1C,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAW,KAAgB,EAAoB,OAAO,EACvD,CAAC,GAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAoB,KAAyB,EAAS,GAAM;AAGnE,SAAgB;AACd,GAAI,MAAkB,UAAU,MAAkB,WAChD,EAAa,EAAc;IAE5B,CAAC,EAAc,CAAC;CAEnB,IAAM,IAAe,OAAO,MAAuB;AAIjD,EAHA,EAAE,gBAAgB,EAClB,EAAS,KAAK,EACd,EAAsB,GAAM,EAC5B,EAAgB,GAAK;AAErB,MAAI;GAEF,IAAM,IAAS,EAAM,MAAM,IAAI,CAAC;AAChC,OAAI,CAAC,GAAQ;AAOX,IANA,EACE,EACE,oCACA,8DACD,CACF,EACD,EAAgB,GAAM;AACtB;;GAKF,IAAM,IAAW,KAAiB;AAKlC,GAFA,eAAe,QAAQ,aAAa,EAAM,EAEtC,MAAc,SAEhB,MAAM,EAA0B,GAAU,aAAa,GAEvD,MAAM,EAAiB,GAAU,aAAa;WAEzC,GAAK;AACZ,WAAQ,MAAM,qBAAqB,EAAI;GACvC,IAAM,IACJ,aAAe,QACX,EAAI,UACJ;AAmBN,GAfE,EAAa,aAAa,CAAC,SAAS,MAAM,KACzC,EAAa,aAAa,CAAC,SAAS,iBAAiB,IACpD,EAAa,aAAa,CAAC,SAAS,aAAa,KAEnD,EAAsB,GAAK,EAC3B,EACE,EACE,kCACA,oIACD,CACF,IAED,EAAS,EAAa,EAGxB,EAAgB,GAAM;;IAIpB,IACJ,KAAuB,KAAuB;AAEhD,QACE,kBAAC,GAAD;EACE,OAAO,EAAG,uBAAuB,iBAAiB;EAClD,UAAU,EACR,0BACA,gDACD;YALH;GAOE,kBAAC,QAAD;IAAM,UAAU;IAAc,WAAU;cAAxC;KAEG,KACC,kBAAC,OAAD;MACE,WAAW,gDACT,IACI,6DACA;gBAJR,CAOE,kBAAC,GAAD,EACE,WAAW,+BACT,IACI,6BACA,4BAEN,CAAA,EACF,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QACE,WAAW,WACT,IACI,6BACA;kBAGL;QACC,CAAA,EACH,KACC,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAS,eAAe,EAAE,OAAO,EAAE,UAAO,EAAE,CAAC;QAC5D,WAAU;kBAHZ,CAKE,kBAAC,OAAD;SACE,WAAU;SACV,MAAK;SACL,SAAQ;SACR,QAAO;mBAEP,kBAAC,QAAD;UACE,eAAc;UACd,gBAAe;UACf,aAAa;UACb,GAAE;UACF,CAAA;SACE,CAAA,EACL,EACC,+BACA,mCACD,CACM;UAEP;SACF;;KAIR,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,SAAD;OAAO,WAAU;iBACd,EAAG,4BAA4B,wBAAwB;OAClD,CAAA,EACR,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAa,OAAO;QACnC,WAAW,0CACT,MAAc,SACV,sDACA;kBAGN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UACE,WAAW,uDACT,MAAc,SACV,8BACA;oBAGN,kBAAC,OAAD;WACE,WAAW,UAAU,MAAc,SAAS,eAAe;WAC3D,MAAK;WACL,SAAQ;WACR,QAAO;qBAEP,kBAAC,QAAD;YACE,eAAc;YACd,gBAAe;YACf,aAAa;YACb,GAAE;YACF,CAAA;WACE,CAAA;UACF,CAAA,EACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WACE,WAAW,eAAe,MAAc,SAAS,wBAAwB;qBAC1E;WAEK,CAAA,EACN,kBAAC,OAAD;WAAK,WAAU;qBACZ,EACC,mCACA,uBACD;WACG,CAAA,CACF;YACF;;QACC,CAAA,EAET,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAa,OAAO;QACnC,WAAW,0CACT,MAAc,SACV,iDACA;kBAGN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UACE,WAAW,uDACT,MAAc,SACV,6BACA;oBAGN,kBAAC,OAAD;WACE,WAAW,UAAU,MAAc,SAAS,eAAe;WAC3D,MAAK;WACL,SAAQ;WACR,QAAO;qBAEP,kBAAC,QAAD;YACE,eAAc;YACd,gBAAe;YACf,aAAa;YACb,GAAE;YACF,CAAA;WACE,CAAA;UACF,CAAA,EACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WACE,WAAW,eAAe,MAAc,SAAS,uBAAuB;qBACzE;WAEK,CAAA,EACN,kBAAC,OAAD;WAAK,WAAU;qBACZ,EAAG,kCAAkC,oBAAoB;WACtD,CAAA,CACF;YACF;;QACC,CAAA,CACL;SACF;;KAGN,kBAAC,OAAD;MACE,WAAW,yBACT,MAAc,SACV,uDACA;gBAGN,kBAAC,KAAD;OACE,WAAW,WACT,MAAc,SACV,0BACA;iBAGL,MAAc,SACX,EACE,+BACA,oGACD,GACD,EACE,2BACA,wFACD;OACH,CAAA;MACA,CAAA;KAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,SAAD;QACE,SAAQ;QACR,WAAU;kBAET,EAAG,2BAA2B,qBAAqB;QAC9C,CAAA;OACR,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAW,WAAU,4CAA6C,CAAA;SAC9D,CAAA,EACN,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,UAAA;SACA,iBAAc;SACd,OAAO;SACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;SACzC,cAAa;SACb,WAAU;SACV,aAAY;SACZ,CAAA,CACE;;OACN,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,iCACA,kDACD;QACC,CAAA;OACA;;KAGN,kBAAC,UAAD;MACE,MAAK;MACL,UAAU,KAAgB,CAAC;MAC3B,WAAW,+LACT,MAAc,SACV,wBACA;gBAGL,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAa,WAAU,uBAAwB,CAAA,EAC/C,kBAAC,QAAD,EAAA,UAAO,EAAG,wBAAwB,iBAAiB,EAAQ,CAAA,CAC1D,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,SAAQ;OACR,QAAO;OACP,eAAY;iBAEZ,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;QACF,CAAA;OACE,CAAA,EACN,kBAAC,QAAD,EAAA,UACG,MAAc,SACX,EAAG,6BAA6B,qBAAqB,GACrD,EAAG,6BAA6B,qBAAqB,EACpD,CAAA,CACN,EAAA,CAAA;MAEE,CAAA;KACJ;;GAGP,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,UAAD;KAAQ,MAAK;KACX,eAAe,EAAS,cAAc;KACtC,WAAU;eAEV,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAG,6BAA6B,wBAAwB;MACpD,CAAA;KACA,CAAA;IACL,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,UAAD;KAAQ,MAAK;KACX,eAAe,EAAS,mBAAmB;KAC3C,WAAU;eAFZ;MAIG,EACC,oCACA,8BACD;MAAE;MACH,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,8BAA8B,gBAAgB;OAC7C,CAAA;MACA;;IACL,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,uBAAuB,gCAAgC;KACxD,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,2BACA,wIACD;KACC,CAAA,CACA;;GACK"}
|
|
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 aria-label={tr(\"auth.ssoLogin.workEmail\", \"Work Email Address\")}\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\n 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\n 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 +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 } 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
|
+
{"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 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\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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"}
|
|
@@ -193,7 +193,8 @@ function x() {
|
|
|
193
193
|
accept: "image/*",
|
|
194
194
|
className: "hidden",
|
|
195
195
|
onChange: (e) => void H(e),
|
|
196
|
-
|
|
196
|
+
tabIndex: -1,
|
|
197
|
+
"aria-label": S("auth.profile.avatarUpload", "Upload avatar image")
|
|
197
198
|
})
|
|
198
199
|
]
|
|
199
200
|
}), /* @__PURE__ */ v("div", {
|
|
@@ -256,6 +257,7 @@ function x() {
|
|
|
256
257
|
}), /* @__PURE__ */ _("input", {
|
|
257
258
|
id: A,
|
|
258
259
|
type: "text",
|
|
260
|
+
"aria-label": S("auth.profile.username", "Username"),
|
|
259
261
|
value: G.username,
|
|
260
262
|
onChange: (e) => K({
|
|
261
263
|
...G,
|
|
@@ -272,6 +274,7 @@ function x() {
|
|
|
272
274
|
}), /* @__PURE__ */ _("input", {
|
|
273
275
|
id: j,
|
|
274
276
|
type: "text",
|
|
277
|
+
"aria-label": S("auth.profile.displayName", "Display Name"),
|
|
275
278
|
value: G.name,
|
|
276
279
|
onChange: (e) => K({
|
|
277
280
|
...G,
|
|
@@ -287,6 +290,7 @@ function x() {
|
|
|
287
290
|
}), /* @__PURE__ */ _("input", {
|
|
288
291
|
id: M,
|
|
289
292
|
type: "text",
|
|
293
|
+
"aria-label": S("auth.profile.firstName", "First Name"),
|
|
290
294
|
value: G.firstName,
|
|
291
295
|
onChange: (e) => K({
|
|
292
296
|
...G,
|
|
@@ -302,6 +306,7 @@ function x() {
|
|
|
302
306
|
}), /* @__PURE__ */ _("input", {
|
|
303
307
|
id: N,
|
|
304
308
|
type: "text",
|
|
309
|
+
"aria-label": S("auth.profile.lastName", "Last Name"),
|
|
305
310
|
value: G.lastName,
|
|
306
311
|
onChange: (e) => K({
|
|
307
312
|
...G,
|
|
@@ -319,6 +324,7 @@ function x() {
|
|
|
319
324
|
}), /* @__PURE__ */ _("input", {
|
|
320
325
|
id: P,
|
|
321
326
|
type: "tel",
|
|
327
|
+
"aria-label": S("auth.profile.phoneNumber", "Phone Number"),
|
|
322
328
|
value: G.phoneNumber,
|
|
323
329
|
onChange: (e) => K({
|
|
324
330
|
...G,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserProfilePage.js","names":[],"sources":["../../../src/auth/pages/UserProfilePage.tsx"],"sourcesContent":["import { useState, useEffect, useRef, useId } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useAuth as useShellAuth } from \"@burdenoff/fe-libs/shared/providers/shell/AuthProvider\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport {\n SpinnerIcon,\n AlertIcon,\n UserIcon,\n EmailIcon,\n CheckIcon,\n} from \"../components/AuthIcons\";\nimport {\n useInitiateAvatarUploadMutation,\n useCompleteAvatarUploadMutation,\n} from \"../../generated/wspace-operations\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\n\ninterface ProfileFormData {\n username: string;\n firstName: string;\n lastName: string;\n name: string;\n phoneNumber: string;\n}\n\n/**\n * User Profile page for viewing and editing own profile\n */\nexport function UserProfilePage() {\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 shellAuth = useShellAuth();\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const setStoreUser = useAuthStore((state) => state.setUser);\n const { updateCurrentUserProfile, isLoading } = useUserOperations();\n\n const usernameId = useId();\n const displayNameId = useId();\n const firstNameId = useId();\n const lastNameId = useId();\n const phoneNumberId = useId();\n\n const currentWorkspaceId = useAuthStore((state) => state.currentWorkspaceId);\n const wspaceClient = useGatewayClient(\"workspace\");\n const [initiateUpload] = useInitiateAvatarUploadMutation({\n client: wspaceClient,\n });\n const [completeUpload] = useCompleteAvatarUploadMutation({\n client: wspaceClient,\n });\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);\n\n const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file) return;\n if (!file.type.startsWith(\"image/\")) {\n setError(\n tr(\"auth.profile.invalidImageType\", \"Please select an image file\"),\n );\n return;\n }\n if (!currentWorkspaceId) {\n setError(\n tr(\n \"auth.profile.noWorkspace\",\n \"Please select a workspace before uploading a profile picture\",\n ),\n );\n return;\n }\n setIsUploadingAvatar(true);\n setError(null);\n const wsHeaders = { \"x-workspace-id\": currentWorkspaceId };\n try {\n const extension = file.name.split(\".\").pop()?.toLowerCase() ?? \"\";\n const initiateResult = await initiateUpload({\n variables: {\n input: {\n fileName: file.name,\n fileSize: file.size.toString(),\n mimeType: file.type,\n extension,\n },\n },\n context: { headers: wsHeaders },\n });\n const session = initiateResult.data?.initiateUpload.session;\n const uploadUrl = initiateResult.data?.initiateUpload.uploadUrl;\n if (!session || !uploadUrl) throw new Error(\"Failed to initiate upload\");\n\n const uploadResp = await fetch(uploadUrl, {\n method: \"PUT\",\n headers: { \"Content-Type\": file.type },\n body: file,\n });\n if (!uploadResp.ok) throw new Error(\"Failed to upload file\");\n\n const completeResult = await completeUpload({\n variables: { input: { sessionId: session.id } },\n context: { headers: wsHeaders },\n });\n const avatarUrl = completeResult.data?.completeUpload.file?.downloadUrl;\n if (!avatarUrl) throw new Error(\"Upload succeeded but no URL returned\");\n\n await updateCurrentUserProfile({ avatar: avatarUrl });\n // Update both the local authStore and the shell AuthProvider so the nav\n // bar avatar reflects the change immediately without a page reload.\n if (currentUser) {\n setStoreUser({ ...currentUser, avatar: avatarUrl });\n }\n shellAuth.updateUser({ avatar: avatarUrl });\n setSuccessMessage(\n tr(\"auth.profile.avatarUpdated\", \"Profile picture updated!\"),\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.avatarFailed\", \"Failed to upload picture\"),\n );\n } finally {\n setIsUploadingAvatar(false);\n if (fileInputRef.current) fileInputRef.current.value = \"\";\n }\n };\n\n const [isEditing, setIsEditing] = useState(false);\n const [formData, setFormData] = useState<ProfileFormData>({\n username: \"\",\n firstName: \"\",\n lastName: \"\",\n name: \"\",\n phoneNumber: \"\",\n });\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // Populate form with current user data\n useEffect(() => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n }, [currentUser]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccessMessage(null);\n\n try {\n await updateCurrentUserProfile({\n username: formData.username || undefined,\n firstName: formData.firstName || undefined,\n lastName: formData.lastName || undefined,\n name: formData.name || undefined,\n phoneNumber: formData.phoneNumber || undefined,\n });\n setSuccessMessage(\n tr(\"auth.profile.updateSuccess\", \"Profile updated successfully!\"),\n );\n setIsEditing(false);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.updateFailed\", \"Failed to update profile\"),\n );\n }\n };\n\n const cancelEdit = () => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n setIsEditing(false);\n setError(null);\n };\n\n // Not authenticated\n if (!isAuthenticated || !currentUser) {\n return (\n <AuthLayout\n title={tr(\"auth.profile.title\", \"Profile\")}\n subtitle={tr(\"auth.profile.subtitle\", \"View and manage your profile\")}\n >\n <div className=\"text-center py-12\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-[var(--color-status-warning-bg-subtle)] flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-[var(--color-status-warning-text)]\" />\n </div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\"auth.profile.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.profile.signInToView\",\n \"Please sign in to view your profile\",\n )}\n </p>\n <button type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"auth-primary-button px-6 py-3 font-semibold\"\n >\n {tr(\"auth.profile.signIn\", \"Sign In\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"mb-8\">\n <h1 className=\"text-3xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.myProfile\", \"My Profile\")}\n </h1>\n <p className=\"text-[var(--color-text-secondary)] mt-2\">\n {tr(\n \"auth.profile.viewAndManage\",\n \"View and manage your account information\",\n )}\n </p>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div 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)]\">\n <CheckIcon className=\"size-5 text-[var(--color-status-success-text)] flex-shrink-0\" />\n <p className=\"text-[var(--color-status-success-text)]\">\n {successMessage}\n </p>\n <button type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"ml-auto text-sm text-[var(--color-status-success-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div 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)]\">\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n <button type=\"button\"\n onClick={() => setError(null)}\n className=\"text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Profile Card */}\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm overflow-hidden\">\n {/* Profile Header */}\n <div className=\"auth-hero-card p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"relative group\">\n {currentUser.avatar ? (\n <img\n src={currentUser.avatar}\n alt={tr(\"auth.profile.avatarAlt\", \"Profile picture\")}\n className=\"size-20 rounded-full object-cover shadow-lg\"\n />\n ) : (\n <div 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\">\n {(\n currentUser.name ||\n currentUser.username ||\n currentUser.email\n )\n .charAt(0)\n .toUpperCase()}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n disabled={isUploadingAvatar}\n aria-label={tr(\n \"auth.profile.changeAvatar\",\n \"Change profile picture\",\n )}\n 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\"\n >\n {isUploadingAvatar ? (\n <SpinnerIcon className=\"size-6 text-white animate-spin\" />\n ) : (\n <svg\n className=\"size-6 text-white\"\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=\"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 13a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n )}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n className=\"hidden\"\n onChange={(e) => void handleAvatarUpload(e)}\n aria-hidden=\"true\"\n />\n </div>\n <div className=\"text-white\">\n <h2 className=\"text-2xl font-semibold\">\n {currentUser.name ||\n `${currentUser.firstName || \"\"} ${currentUser.lastName || \"\"}`.trim() ||\n currentUser.username}\n </h2>\n <p className=\"text-white/80 flex items-center gap-2\">\n <EmailIcon className=\"size-4\" />\n {currentUser.email}\n </p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-bg-surface/20 backdrop-blur-sm\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </span>\n {currentUser.emailVerified && (\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-status-success-text/30 flex items-center gap-1\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </div>\n </div>\n </div>\n </div>\n\n {/* Profile Body */}\n <div className=\"p-6\">\n {isEditing ? (\n /* Edit Form */\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label\n htmlFor={usernameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.username\", \"Username\")}\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 <UserIcon\n className=\"size-5 text-[var(--color-text-tertiary)]\"\n aria-hidden=\"true\"\n />\n </div>\n <input\n id={usernameId}\n type=\"text\"\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.usernamePlaceholder\",\n \"Enter username\",\n )}\n />\n </div>\n </div>\n\n <div>\n <label\n htmlFor={displayNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <input\n id={displayNameId}\n type=\"text\"\n value={formData.name}\n onChange={(e) =>\n setFormData({ ...formData, name: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.displayNamePlaceholder\",\n \"Enter display name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={firstNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <input\n id={firstNameId}\n type=\"text\"\n value={formData.firstName}\n onChange={(e) =>\n setFormData({ ...formData, firstName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.firstNamePlaceholder\",\n \"Enter first name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={lastNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <input\n id={lastNameId}\n type=\"text\"\n value={formData.lastName}\n onChange={(e) =>\n setFormData({ ...formData, lastName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.lastNamePlaceholder\",\n \"Enter last name\",\n )}\n />\n </div>\n\n <div className=\"md:col-span-2\">\n <label\n htmlFor={phoneNumberId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <input\n id={phoneNumberId}\n type=\"tel\"\n value={formData.phoneNumber}\n onChange={(e) =>\n setFormData({ ...formData, phoneNumber: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.phonePlaceholder\",\n \"Enter phone number\",\n )}\n />\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-4 border-t border-[var(--color-border-default)]\">\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"auth-primary-button flex items-center gap-2 px-6 py-3 font-medium\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.profile.saving\", \"Saving...\")}\n </>\n ) : (\n tr(\"auth.profile.saveChanges\", \"Save Changes\")\n )}\n </button>\n <button\n type=\"button\"\n onClick={cancelEdit}\n 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\"\n >\n {tr(\"auth.profile.cancel\", \"Cancel\")}\n </button>\n </div>\n </form>\n ) : (\n /* View Mode */\n <>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.username\", \"Username\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.username || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.name || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.firstName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.email\", \"Email\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium flex items-center gap-2\">\n {currentUser.email}\n {currentUser.emailVerified && (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-success-text)] text-xs\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.phoneNumber || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.role\", \"Role\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.mfaStatus\", \"MFA Status\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.mfaEnabled ? (\n <span className=\"text-[var(--color-status-success-text)] flex items-center gap-1\">\n <CheckIcon className=\"size-4\" />\n {tr(\"auth.profile.enabled\", \"Enabled\")}\n </span>\n ) : (\n <span className=\"text-[var(--color-status-warning-text)]\">\n {tr(\"auth.profile.notEnabled\", \"Not Enabled\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastLogin\", \"Last Login\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastLoginAt\n ? new Date(currentUser.lastLoginAt).toLocaleString()\n : tr(\"auth.profile.never\", \"Never\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.accountCreated\", \"Account Created\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.createdAt\n ? new Date(currentUser.createdAt).toLocaleString()\n : \"-\"}\n </p>\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-6 mt-6 border-t border-[var(--color-border-default)]\">\n <button type=\"button\"\n onClick={() => setIsEditing(true)}\n className=\"auth-primary-button px-6 py-3 font-medium\"\n >\n {tr(\"auth.profile.editProfile\", \"Edit Profile\")}\n </button>\n <button type=\"button\"\n onClick={() => navigate(\"/auth/security\")}\n 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\"\n >\n {tr(\"auth.profile.securitySettings\", \"Security Settings\")}\n </button>\n </div>\n </>\n )}\n </div>\n </div>\n\n {/* Quick Links */}\n <div className=\"mt-8 grid grid-cols-1 md:grid-cols-3 gap-4\">\n <button type=\"button\"\n onClick={() => navigate(\"/auth/mfa\")}\n 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\"\n >\n <div 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\">\n <svg\n className=\"size-5 text-[var(--color-text-link)]\"\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=\"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 />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.twoFactorAuth\", \"Two-Factor Auth\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {currentUser.mfaEnabled\n ? tr(\"auth.profile.manageMfa\", \"Manage MFA settings\")\n : tr(\"auth.profile.enableMfa\", \"Enable MFA for extra security\")}\n </p>\n </button>\n\n <button type=\"button\"\n onClick={() => navigate(\"/auth/api-keys\")}\n 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\"\n >\n <div 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\">\n <svg\n className=\"size-5 text-[var(--color-action-secondary-text)]\"\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=\"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 />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.apiKeys\", \"API Keys\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {tr(\n \"auth.profile.manageApiKeys\",\n \"Manage your API access credentials\",\n )}\n </p>\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"UserProfilePage.js","names":[],"sources":["../../../src/auth/pages/UserProfilePage.tsx"],"sourcesContent":["import { useState, useEffect, useRef, useId } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useAuth as useShellAuth } from \"@burdenoff/fe-libs/shared/providers/shell/AuthProvider\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport {\n SpinnerIcon,\n AlertIcon,\n UserIcon,\n EmailIcon,\n CheckIcon,\n} from \"../components/AuthIcons\";\nimport {\n useInitiateAvatarUploadMutation,\n useCompleteAvatarUploadMutation,\n} from \"../../generated/wspace-operations\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\n\ninterface ProfileFormData {\n username: string;\n firstName: string;\n lastName: string;\n name: string;\n phoneNumber: string;\n}\n\n/**\n * User Profile page for viewing and editing own profile\n */\nexport function UserProfilePage() {\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 shellAuth = useShellAuth();\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const setStoreUser = useAuthStore((state) => state.setUser);\n const { updateCurrentUserProfile, isLoading } = useUserOperations();\n\n const usernameId = useId();\n const displayNameId = useId();\n const firstNameId = useId();\n const lastNameId = useId();\n const phoneNumberId = useId();\n\n const currentWorkspaceId = useAuthStore((state) => state.currentWorkspaceId);\n const wspaceClient = useGatewayClient(\"workspace\");\n const [initiateUpload] = useInitiateAvatarUploadMutation({\n client: wspaceClient,\n });\n const [completeUpload] = useCompleteAvatarUploadMutation({\n client: wspaceClient,\n });\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);\n\n const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file) return;\n if (!file.type.startsWith(\"image/\")) {\n setError(\n tr(\"auth.profile.invalidImageType\", \"Please select an image file\"),\n );\n return;\n }\n if (!currentWorkspaceId) {\n setError(\n tr(\n \"auth.profile.noWorkspace\",\n \"Please select a workspace before uploading a profile picture\",\n ),\n );\n return;\n }\n setIsUploadingAvatar(true);\n setError(null);\n const wsHeaders = { \"x-workspace-id\": currentWorkspaceId };\n try {\n const extension = file.name.split(\".\").pop()?.toLowerCase() ?? \"\";\n const initiateResult = await initiateUpload({\n variables: {\n input: {\n fileName: file.name,\n fileSize: file.size.toString(),\n mimeType: file.type,\n extension,\n },\n },\n context: { headers: wsHeaders },\n });\n const session = initiateResult.data?.initiateUpload.session;\n const uploadUrl = initiateResult.data?.initiateUpload.uploadUrl;\n if (!session || !uploadUrl) throw new Error(\"Failed to initiate upload\");\n\n const uploadResp = await fetch(uploadUrl, {\n method: \"PUT\",\n headers: { \"Content-Type\": file.type },\n body: file,\n });\n if (!uploadResp.ok) throw new Error(\"Failed to upload file\");\n\n const completeResult = await completeUpload({\n variables: { input: { sessionId: session.id } },\n context: { headers: wsHeaders },\n });\n const avatarUrl = completeResult.data?.completeUpload.file?.downloadUrl;\n if (!avatarUrl) throw new Error(\"Upload succeeded but no URL returned\");\n\n await updateCurrentUserProfile({ avatar: avatarUrl });\n // Update both the local authStore and the shell AuthProvider so the nav\n // bar avatar reflects the change immediately without a page reload.\n if (currentUser) {\n setStoreUser({ ...currentUser, avatar: avatarUrl });\n }\n shellAuth.updateUser({ avatar: avatarUrl });\n setSuccessMessage(\n tr(\"auth.profile.avatarUpdated\", \"Profile picture updated!\"),\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.avatarFailed\", \"Failed to upload picture\"),\n );\n } finally {\n setIsUploadingAvatar(false);\n if (fileInputRef.current) fileInputRef.current.value = \"\";\n }\n };\n\n const [isEditing, setIsEditing] = useState(false);\n const [formData, setFormData] = useState<ProfileFormData>({\n username: \"\",\n firstName: \"\",\n lastName: \"\",\n name: \"\",\n phoneNumber: \"\",\n });\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // Populate form with current user data\n useEffect(() => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n }, [currentUser]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccessMessage(null);\n\n try {\n await updateCurrentUserProfile({\n username: formData.username || undefined,\n firstName: formData.firstName || undefined,\n lastName: formData.lastName || undefined,\n name: formData.name || undefined,\n phoneNumber: formData.phoneNumber || undefined,\n });\n setSuccessMessage(\n tr(\"auth.profile.updateSuccess\", \"Profile updated successfully!\"),\n );\n setIsEditing(false);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.updateFailed\", \"Failed to update profile\"),\n );\n }\n };\n\n const cancelEdit = () => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n setIsEditing(false);\n setError(null);\n };\n\n // Not authenticated\n if (!isAuthenticated || !currentUser) {\n return (\n <AuthLayout\n title={tr(\"auth.profile.title\", \"Profile\")}\n subtitle={tr(\"auth.profile.subtitle\", \"View and manage your profile\")}\n >\n <div className=\"text-center py-12\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-[var(--color-status-warning-bg-subtle)] flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-[var(--color-status-warning-text)]\" />\n </div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\"auth.profile.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.profile.signInToView\",\n \"Please sign in to view your profile\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"auth-primary-button px-6 py-3 font-semibold\"\n >\n {tr(\"auth.profile.signIn\", \"Sign In\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"mb-8\">\n <h1 className=\"text-3xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.myProfile\", \"My Profile\")}\n </h1>\n <p className=\"text-[var(--color-text-secondary)] mt-2\">\n {tr(\n \"auth.profile.viewAndManage\",\n \"View and manage your account information\",\n )}\n </p>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div 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)]\">\n <CheckIcon className=\"size-5 text-[var(--color-status-success-text)] flex-shrink-0\" />\n <p className=\"text-[var(--color-status-success-text)]\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"ml-auto text-sm text-[var(--color-status-success-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div 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)]\">\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Profile Card */}\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm overflow-hidden\">\n {/* Profile Header */}\n <div className=\"auth-hero-card p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"relative group\">\n {currentUser.avatar ? (\n <img\n src={currentUser.avatar}\n alt={tr(\"auth.profile.avatarAlt\", \"Profile picture\")}\n className=\"size-20 rounded-full object-cover shadow-lg\"\n />\n ) : (\n <div 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\">\n {(\n currentUser.name ||\n currentUser.username ||\n currentUser.email\n )\n .charAt(0)\n .toUpperCase()}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n disabled={isUploadingAvatar}\n aria-label={tr(\n \"auth.profile.changeAvatar\",\n \"Change profile picture\",\n )}\n 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\"\n >\n {isUploadingAvatar ? (\n <SpinnerIcon className=\"size-6 text-white animate-spin\" />\n ) : (\n <svg\n className=\"size-6 text-white\"\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=\"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 13a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n )}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n className=\"hidden\"\n onChange={(e) => void handleAvatarUpload(e)}\n tabIndex={-1}\n aria-label={tr(\n \"auth.profile.avatarUpload\",\n \"Upload avatar image\",\n )}\n />\n </div>\n <div className=\"text-white\">\n <h2 className=\"text-2xl font-semibold\">\n {currentUser.name ||\n `${currentUser.firstName || \"\"} ${currentUser.lastName || \"\"}`.trim() ||\n currentUser.username}\n </h2>\n <p className=\"text-white/80 flex items-center gap-2\">\n <EmailIcon className=\"size-4\" />\n {currentUser.email}\n </p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-bg-surface/20 backdrop-blur-sm\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </span>\n {currentUser.emailVerified && (\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-status-success-text/30 flex items-center gap-1\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </div>\n </div>\n </div>\n </div>\n\n {/* Profile Body */}\n <div className=\"p-6\">\n {isEditing ? (\n /* Edit Form */\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label\n htmlFor={usernameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.username\", \"Username\")}\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 <UserIcon\n className=\"size-5 text-[var(--color-text-tertiary)]\"\n aria-hidden=\"true\"\n />\n </div>\n <input\n id={usernameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.username\", \"Username\")}\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.usernamePlaceholder\",\n \"Enter username\",\n )}\n />\n </div>\n </div>\n\n <div>\n <label\n htmlFor={displayNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <input\n id={displayNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.displayName\", \"Display Name\")}\n value={formData.name}\n onChange={(e) =>\n setFormData({ ...formData, name: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.displayNamePlaceholder\",\n \"Enter display name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={firstNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <input\n id={firstNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.firstName\", \"First Name\")}\n value={formData.firstName}\n onChange={(e) =>\n setFormData({ ...formData, firstName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.firstNamePlaceholder\",\n \"Enter first name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={lastNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <input\n id={lastNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.lastName\", \"Last Name\")}\n value={formData.lastName}\n onChange={(e) =>\n setFormData({ ...formData, lastName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.lastNamePlaceholder\",\n \"Enter last name\",\n )}\n />\n </div>\n\n <div className=\"md:col-span-2\">\n <label\n htmlFor={phoneNumberId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <input\n id={phoneNumberId}\n type=\"tel\"\n aria-label={tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n value={formData.phoneNumber}\n onChange={(e) =>\n setFormData({ ...formData, phoneNumber: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.phonePlaceholder\",\n \"Enter phone number\",\n )}\n />\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-4 border-t border-[var(--color-border-default)]\">\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"auth-primary-button flex items-center gap-2 px-6 py-3 font-medium\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.profile.saving\", \"Saving...\")}\n </>\n ) : (\n tr(\"auth.profile.saveChanges\", \"Save Changes\")\n )}\n </button>\n <button\n type=\"button\"\n onClick={cancelEdit}\n 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\"\n >\n {tr(\"auth.profile.cancel\", \"Cancel\")}\n </button>\n </div>\n </form>\n ) : (\n /* View Mode */\n <>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.username\", \"Username\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.username || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.name || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.firstName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.email\", \"Email\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium flex items-center gap-2\">\n {currentUser.email}\n {currentUser.emailVerified && (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-success-text)] text-xs\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.phoneNumber || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.role\", \"Role\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.mfaStatus\", \"MFA Status\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.mfaEnabled ? (\n <span className=\"text-[var(--color-status-success-text)] flex items-center gap-1\">\n <CheckIcon className=\"size-4\" />\n {tr(\"auth.profile.enabled\", \"Enabled\")}\n </span>\n ) : (\n <span className=\"text-[var(--color-status-warning-text)]\">\n {tr(\"auth.profile.notEnabled\", \"Not Enabled\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastLogin\", \"Last Login\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastLoginAt\n ? new Date(currentUser.lastLoginAt).toLocaleString()\n : tr(\"auth.profile.never\", \"Never\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.accountCreated\", \"Account Created\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.createdAt\n ? new Date(currentUser.createdAt).toLocaleString()\n : \"-\"}\n </p>\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-6 mt-6 border-t border-[var(--color-border-default)]\">\n <button\n type=\"button\"\n onClick={() => setIsEditing(true)}\n className=\"auth-primary-button px-6 py-3 font-medium\"\n >\n {tr(\"auth.profile.editProfile\", \"Edit Profile\")}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/security\")}\n 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\"\n >\n {tr(\"auth.profile.securitySettings\", \"Security Settings\")}\n </button>\n </div>\n </>\n )}\n </div>\n </div>\n\n {/* Quick Links */}\n <div className=\"mt-8 grid grid-cols-1 md:grid-cols-3 gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa\")}\n 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\"\n >\n <div 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\">\n <svg\n className=\"size-5 text-[var(--color-text-link)]\"\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=\"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 />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.twoFactorAuth\", \"Two-Factor Auth\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {currentUser.mfaEnabled\n ? tr(\"auth.profile.manageMfa\", \"Manage MFA settings\")\n : tr(\"auth.profile.enableMfa\", \"Enable MFA for extra security\")}\n </p>\n </button>\n\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/api-keys\")}\n 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\"\n >\n <div 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\">\n <svg\n className=\"size-5 text-[var(--color-action-secondary-text)]\"\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=\"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 />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.apiKeys\", \"API Keys\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {tr(\n \"auth.profile.manageApiKeys\",\n \"Manage your API access credentials\",\n )}\n </p>\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|