"use client"; import { GraphQLClient, gql } from "graphql-request"; import { useEffect, useState } from "react"; const graphQLClient = new GraphQLClient(process.env.NEXT_PUBLIC_APP_URL + "/api/graphql"); const GET_ORGANIZATIONS_QUERY = gql` query GetOrganizations($page: Int, $pageSize: Int) { organizations(page: $page, pageSize: $pageSize) { success message totalCount organizations { id name description customData isMfaRequired createdAt } } } `; const GET_MY_ORGANIZATIONS_QUERY = gql` query GetMyOrganizations($page: Int, $pageSize: Int) { myOrganizations(page: $page, pageSize: $pageSize) { success message totalCount organizations { id name description customData isMfaRequired createdAt } } } `; const CREATE_ORGANIZATION_MUTATION = gql` mutation CreateOrganization($input: CreateOrganizationInput!) { createOrganization(input: $input) { success message organization { id name description customData isMfaRequired createdAt } } } `; const GET_ORGANIZATION_USERS_QUERY = gql` query GetOrganizationUsers($organizationId: ID!, $page: Int, $pageSize: Int) { organizationUsers(organizationId: $organizationId, page: $page, pageSize: $pageSize) { success message totalCount users { id username primaryEmail name avatar organizationRoles { id name description type } } } } `; const GET_ORGANIZATION_ROLES_QUERY = gql` query GetOrganizationRoles($organizationId: ID!, $page: Int, $pageSize: Int) { organizationRoles(organizationId: $organizationId, page: $page, pageSize: $pageSize) { success message totalCount roles { id name description type } } } `; const TEST_ORGANIZATIONS_API_QUERY = gql` query TestOrganizationsAPI { testOrganizationsAPI { available message } } `; interface Organization { id: string; name: string; description?: string; customData?: Record; isMfaRequired?: boolean; createdAt: string; } interface OrganizationUser { id: string; username?: string; primaryEmail?: string; name?: string; avatar?: string; organizationRoles: Array<{ id: string; name: string; description?: string; type: string; }>; } interface OrganizationRole { id: string; name: string; description?: string; type: string; } interface OrganizationsResponse { organizations: { success: boolean; message?: string; totalCount?: number; organizations?: Organization[]; }; } interface MyOrganizationsResponse { myOrganizations: { success: boolean; message?: string; totalCount?: number; organizations?: Organization[]; }; } interface CreateOrganizationResponse { createOrganization: { success: boolean; message?: string; organization?: Organization; }; } interface OrganizationUsersResponse { organizationUsers: { success: boolean; message?: string; totalCount?: number; users?: OrganizationUser[]; }; } interface OrganizationRolesResponse { organizationRoles: { success: boolean; message?: string; totalCount?: number; roles?: OrganizationRole[]; }; } interface TestOrganizationsAPIResponse { testOrganizationsAPI: { available: boolean; message: string; }; } export default function OrganizationsTestPage() { const [organizations, setOrganizations] = useState([]); const [myOrganizations, setMyOrganizations] = useState([]); const [selectedOrganization, setSelectedOrganization] = useState(null); const [organizationUsers, setOrganizationUsers] = useState([]); const [organizationRoles, setOrganizationRoles] = useState([]); const [loading, setLoading] = useState(false); const [createLoading, setCreateLoading] = useState(false); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState<"all" | "mine" | "details">("all"); // Form data for creating organization const [createFormData, setCreateFormData] = useState({ name: "", description: "", isMfaRequired: false, }); const [accessToken, setAccessToken] = useState(null); const fetchAllOrganizations = async () => { setLoading(true); setError(null); try { const response = await graphQLClient.request(GET_ORGANIZATIONS_QUERY, { page: 1, pageSize: 20, }); if (response.organizations.success) { const orgs = response.organizations.organizations || []; setOrganizations(orgs); } else { setError(response.organizations.message || "Erreur lors du chargement des organisations"); } } catch (err) { setError(err instanceof Error ? err.message : "Erreur inconnue"); } finally { setLoading(false); } }; useEffect(() => { // Récupérer le token depuis localStorage const token = localStorage.getItem("accessToken"); setAccessToken(token); // Auto-loading des organisations au démarrage de la page const loadOrganizations = async () => { setLoading(true); setError(null); try { const response = await graphQLClient.request(GET_ORGANIZATIONS_QUERY, { page: 1, pageSize: 20, }); if (response.organizations.success) { const orgs = response.organizations.organizations || []; setOrganizations(orgs); } else { setError(response.organizations.message || "Erreur lors du chargement des organisations"); } } catch (err) { setError(err instanceof Error ? err.message : "Erreur inconnue"); } finally { setLoading(false); } }; loadOrganizations(); }, []); const testOrganizationsAPI = async () => { setLoading(true); setError(null); try { const response = await graphQLClient.request(TEST_ORGANIZATIONS_API_QUERY); if (response.testOrganizationsAPI.available) { // Si l'API est disponible, on peut essayer de charger les organisations setError(null); // On ne recharge pas automatiquement, on laisse l'utilisateur choisir alert( `✅ ${response.testOrganizationsAPI.message}\n\nVous pouvez maintenant utiliser les fonctionnalités d'organisations.` ); } else { setError(response.testOrganizationsAPI.message); } } catch (err) { setError(err instanceof Error ? err.message : "Erreur inconnue lors du test"); } finally { setLoading(false); } }; const fetchMyOrganizations = async (token: string) => { setLoading(true); setError(null); try { const clientWithAuth = new GraphQLClient(process.env.NEXT_PUBLIC_APP_URL + "/api/graphql", { headers: { Authorization: `Bearer ${token}`, }, }); const response = await clientWithAuth.request(GET_MY_ORGANIZATIONS_QUERY, { page: 1, pageSize: 20, }); if (response.myOrganizations.success) { setMyOrganizations(response.myOrganizations.organizations || []); } else { setError(response.myOrganizations.message || "Erreur lors du chargement de vos organisations"); } } catch (err) { setError(err instanceof Error ? err.message : "Erreur inconnue"); } finally { setLoading(false); } }; const fetchOrganizationDetails = async (organization: Organization) => { setSelectedOrganization(organization); setActiveTab("details"); setLoading(true); setError(null); try { // Récupérer les utilisateurs const usersResponse = await graphQLClient.request(GET_ORGANIZATION_USERS_QUERY, { organizationId: organization.id, page: 1, pageSize: 20, }); if (usersResponse.organizationUsers.success) { setOrganizationUsers(usersResponse.organizationUsers.users || []); } // Récupérer les rôles const rolesResponse = await graphQLClient.request(GET_ORGANIZATION_ROLES_QUERY, { organizationId: organization.id, page: 1, pageSize: 20, }); if (rolesResponse.organizationRoles.success) { setOrganizationRoles(rolesResponse.organizationRoles.roles || []); } } catch (err) { setError(err instanceof Error ? err.message : "Erreur inconnue"); } finally { setLoading(false); } }; const handleCreateOrganization = async (e: React.FormEvent) => { e.preventDefault(); // Validation simple côté client if (!createFormData.name.trim()) { setError("Le nom de l'organisation est requis"); return; } setCreateLoading(true); setError(null); try { const response = await graphQLClient.request(CREATE_ORGANIZATION_MUTATION, { input: { name: createFormData.name, description: createFormData.description || undefined, isMfaRequired: createFormData.isMfaRequired, }, }); if (response.createOrganization.success) { alert("Organisation créée avec succès!"); setCreateFormData({ name: "", description: "", isMfaRequired: false }); // Recharger la liste si on est sur l'onglet "all" if (activeTab === "all") { await fetchAllOrganizations(); } } else { setError(response.createOrganization.message || "Erreur lors de la création"); } } catch (err) { setError(err instanceof Error ? err.message : "Erreur inconnue"); } finally { setCreateLoading(false); } }; return (

🏢 Test des Organisations Logto

{/* Info Section */}

ℹ️ À propos des organisations Logto

Les organisations permettent de gérer des environnements multi-tenant (B2B/SaaS). Cette fonctionnalité peut nécessiter :

  • Un plan Logto qui inclut les organisations
  • L'activation dans votre console Logto
  • La configuration d'un template d'organisation

📚 Documentation Logto sur les organisations

{/* Navigation Tabs */}
{/* API Test Section */}

🔬 Test de l'API des organisations

Cliquez pour vérifier si l'API des organisations est disponible sur votre instance Logto.

{/* Create Organization Form */}

Créer une nouvelle organisation

setCreateFormData(prev => ({ ...prev, name: e.target.value }))} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none" placeholder="Nom de l'organisation" />
setCreateFormData(prev => ({ ...prev, description: e.target.value }))} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none" placeholder="Description de l'organisation" />
{/* Error Display */} {error && (

Erreur

{error}

{error.includes("n'est pas disponible") && (

💡 Comment activer les organisations

Les organisations Logto peuvent nécessiter :

  • Un plan Logto qui inclut cette fonctionnalité
  • L'activation des organisations dans votre console Logto
  • La configuration d'un template d'organisation

Consultez la{" "} documentation Logto sur les organisations {" "} pour plus d'informations.

)}
)} {/* Loading */} {loading && (

Chargement...

)} {/* Content based on active tab */} {activeTab === "all" && (

Toutes les organisations ({organizations.length})

{organizations.length === 0 ? (

Aucune organisation trouvée.

) : (
{organizations.map(org => (
fetchOrganizationDetails(org)} >

{org.name}

{org.description &&

{org.description}

}
ID: {org.id.substring(0, 8)}... {org.isMfaRequired && ( MFA )}

Créée: {new Date(parseInt(org.createdAt)).toLocaleDateString()}

))}
)}
)} {activeTab === "mine" && (

Mes organisations ({myOrganizations.length})

{!accessToken ? (

Veuillez vous connecter pour voir vos organisations.{" "} Se connecter

) : myOrganizations.length === 0 ? (

Vous n'êtes membre d'aucune organisation.

) : (
{myOrganizations.map(org => (
fetchOrganizationDetails(org)} >

{org.name}

{org.description &&

{org.description}

}
ID: {org.id.substring(0, 8)}... {org.isMfaRequired && ( MFA )}
))}
)}
)} {activeTab === "details" && selectedOrganization && (

Détails: {selectedOrganization.name}

{/* Organization Users */}

Utilisateurs ({organizationUsers.length})

{organizationUsers.length === 0 ? (

Aucun utilisateur.

) : (
{organizationUsers.map(user => (

{user.name || user.username || "Utilisateur"}

{user.primaryEmail &&

{user.primaryEmail}

} {user.organizationRoles.length > 0 && (
{user.organizationRoles.map(role => ( {role.name} ))}
)}
))}
)}
{/* Organization Roles */}

Rôles ({organizationRoles.length})

{organizationRoles.length === 0 ? (

Aucun rôle défini.

) : (
{organizationRoles.map(role => (

{role.name}

{role.description &&

{role.description}

} {role.type}
))}
)}
{/* Organization Info */}

Informations

ID:
{selectedOrganization.id}
Nom:
{selectedOrganization.name}
{selectedOrganization.description && (
Description:
{selectedOrganization.description}
)}
MFA requis:
{selectedOrganization.isMfaRequired ? "Oui" : "Non"}
Créée le:
{new Date(parseInt(selectedOrganization.createdAt)).toLocaleString()}
)} {/* Navigation Links */}
); }