+ {/* Header - similar to TopBar but without sidebar */}
+
+
+
+ Gatehouse
+
+
+
+ {user?.email}
+
+
+
+
+ {/* Main content */}
+
+
+
+
+
+
+ MFA Enrollment Required
+
+ Your account is restricted until you configure multi-factor authentication.
+ Please set up at least one of the following methods to continue.
+
+
+
+ {/* Deadline info */}
+ {mfaCompliance?.deadline_at && (
+
+
+
+
+ Unable to load organization policy. Please try again later.
+
+
+
+ );
+ }
+
return (
@@ -18,31 +217,161 @@ export default function PoliciesPage() {
- {/* Registration Mode */}
+ {/* Compliance Overview */}
-
- Registration Mode
+
+ Compliance Overview
- Control how new members can join your organization
+ Current MFA compliance status for organization members
-
-
- Invite only: Members can only join via admin invitation
-
+
+
+
{complianceStats.compliant}
+
Compliant
+
+
+
{complianceStats.inGrace}
+
In Grace
+
+
+
{complianceStats.pastDue}
+
Past Due
+
+
+
{complianceStats.suspended}
+
Suspended
+
+
+
{complianceStats.total}
+
Total Members
+
+
+
+
+
+
+ {/* MFA Policy */}
+
+
+
+
+ Multi-Factor Authentication
+
+
+ Require additional authentication methods for all members
+
+
+
+ {hasUnsavedChanges && (
+
+
+
+ You have unsaved changes. Click "Save Changes" to apply them or "Discard" to revert.
+
+
+ )}
+
-
-
-
-
- Enabling this will require all existing members to set up TOTP on their next login.
-
-
-
-
-
{/* Passkey Requirements */}
@@ -160,4 +455,4 @@ export default function PoliciesPage() {
);
-}
+}
\ No newline at end of file
diff --git a/src/pages/user/ProfilePage.tsx b/src/pages/user/ProfilePage.tsx
index 9860777..cea7865 100644
--- a/src/pages/user/ProfilePage.tsx
+++ b/src/pages/user/ProfilePage.tsx
@@ -9,6 +9,7 @@ import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
import { useAuth } from "@/contexts/AuthContext";
import { api, Organization, ApiError } from "@/lib/api";
+import { useOrganizations } from "@/hooks/useOrganizations";
import { toast } from "@/hooks/use-toast";
function ProfileSkeleton() {
@@ -73,8 +74,16 @@ export default function ProfilePage() {
const [name, setName] = useState("");
const [isEditing, setIsEditing] = useState(false);
const [isSaving, setIsSaving] = useState(false);
- const [organizations, setOrganizations] = useState([]);
- const [orgsLoading, setOrgsLoading] = useState(true);
+
+ // Use React Query hook for organizations with automatic caching and deduplication
+ const { data: organizations = [], isLoading: orgsLoading, error: orgsError } = useOrganizations();
+
+ // Debug logging
+ console.log('[ProfilePage] organizations data:', organizations);
+ console.log('[ProfilePage] organizations is array:', Array.isArray(organizations));
+
+ // Ensure organizations is always an array (defensive check)
+ const organizationsArray = Array.isArray(organizations) ? organizations : [];
// Sync local name state with user data
useEffect(() => {
@@ -83,36 +92,16 @@ export default function ProfilePage() {
}
}, [user?.full_name]);
- // Fetch organizations only when user is available
+ // Handle 403 errors for organizations
useEffect(() => {
- console.log('[ProfilePage] useEffect triggered, user:', user?.id);
- if (!user) {
- console.log('[ProfilePage] No user, skipping organizations fetch');
- setOrgsLoading(false);
- return;
+ if (orgsError instanceof ApiError && orgsError.code === 403) {
+ toast({
+ title: "Access Denied",
+ description: "You don't have permission to view organizations. Please contact your organization administrator.",
+ variant: "destructive",
+ });
}
-
- const fetchOrgs = async () => {
- console.log('[ProfilePage] Making api.users.organizations() request');
- try {
- const response = await api.users.organizations();
- console.log('[ProfilePage] Organizations fetched successfully:', response.organizations.length);
- setOrganizations(response.organizations);
- } catch (error) {
- if (error instanceof ApiError) {
- toast({
- title: "Error loading organizations",
- description: error.message,
- variant: "destructive",
- });
- }
- } finally {
- setOrgsLoading(false);
- }
- };
-
- fetchOrgs();
- }, [user]);
+ }, [orgsError]);
const getInitials = (fullName: string | null) => {
if (!fullName) return "?";
@@ -271,13 +260,13 @@ export default function ProfilePage() {