Skip to content

Commit 2d3d1df

Browse files
committed
feat(react): add invitation hook, container, and example app
1 parent 7b93170 commit 2d3d1df

File tree

5 files changed

+719
-1
lines changed

5 files changed

+719
-1
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use client';
2+
3+
import { OrganizationMemberManagement } from '@auth0/universal-components-react/rwa';
4+
5+
export default function MemberManagementPage() {
6+
return (
7+
<div className="p-6 pt-8 space-y-6">
8+
<OrganizationMemberManagement defaultTab="members" />
9+
</div>
10+
);
11+
}

examples/next-rwa/src/components/navigation/side-bar.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { useUser } from '@auth0/nextjs-auth0';
4-
import { Building, Settings, Shield, User } from 'lucide-react';
4+
import { Building, Settings, Shield, User, Users } from 'lucide-react';
55
import Link from 'next/link';
66
import React from 'react';
77
import { useTranslation } from 'react-i18next';
@@ -74,6 +74,15 @@ export const Sidebar: React.FC = () => {
7474
<span className="truncate">{t('sidebar.identity-providers')}</span>
7575
</Link>
7676
</li>
77+
<li>
78+
<Link
79+
href="/member-management"
80+
className="flex items-center gap-3 px-3 py-2 text-sm text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded-md dark:text-gray-300 dark:hover:text-white dark:hover:bg-gray-800 transition-colors"
81+
>
82+
<Users className="h-4 w-4 flex-shrink-0" />
83+
<span className="truncate">{t('sidebar.members')}</span>
84+
</Link>
85+
</li>
7786
</ul>
7887
</div>
7988
</div>

examples/next-rwa/src/providers/i18n-provider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ i18n.use(initReactI18next).init({
2626
'sidebar.organization-settings': 'Organization Settings',
2727
'sidebar.domains': 'Domains',
2828
'sidebar.identity-providers': 'Identity Providers',
29+
'sidebar.members': 'Members & Invitations',
2930
},
3031
},
3132
},
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/**
2+
* Organization member management component.
3+
* @module organization-member-management
4+
*/
5+
6+
import { getComponentStyles } from '@auth0/universal-components-core';
7+
import { Plus } from 'lucide-react';
8+
import * as React from 'react';
9+
10+
import { OrganizationInvitationDetailsModal } from '@/components/auth0/my-organization/shared/member-management/invitations/invitation-details/organization-invitation-details-modal';
11+
import { OrganizationInvitationRevokeModal } from '@/components/auth0/my-organization/shared/member-management/invitations/invitation-revoke/organization-invitation-revoke-modal';
12+
import { OrganizationInvitationTable } from '@/components/auth0/my-organization/shared/member-management/invitations/invitation-table/organization-invitation-table';
13+
import { OrganizationInvitationCreateModal } from '@/components/auth0/my-organization/shared/member-management/shared/invitation-create/organization-invitation-create-modal';
14+
import { Header } from '@/components/auth0/shared/header';
15+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
16+
import { useOrganizationMemberManagement } from '@/hooks/my-organization/use-organization-member-management';
17+
import { useTheme } from '@/hooks/shared/use-theme';
18+
import { useTranslator } from '@/hooks/shared/use-translator';
19+
import type {
20+
MemberManagementState,
21+
MemberManagementHandlers,
22+
OrganizationMemberManagementProps,
23+
} from '@/types/my-organization/member-management/organization-member-management-types';
24+
25+
/**
26+
* Props for the OrganizationMemberManagementView component.
27+
*/
28+
export interface OrganizationMemberManagementViewProps {
29+
state: MemberManagementState & {
30+
styling: OrganizationMemberManagementProps['styling'];
31+
customMessages: OrganizationMemberManagementProps['customMessages'];
32+
hideHeader: boolean;
33+
readOnly: boolean;
34+
};
35+
handlers: MemberManagementHandlers;
36+
}
37+
38+
/**
39+
* View component for organization member management.
40+
* @param props - The component props.
41+
* @returns The component.
42+
*/
43+
export function OrganizationMemberManagementView({
44+
state,
45+
handlers,
46+
}: OrganizationMemberManagementViewProps) {
47+
const { isDarkMode } = useTheme();
48+
const { t } = useTranslator('member_management', state.customMessages as Record<string, unknown>);
49+
50+
const currentStyles = React.useMemo(
51+
() => getComponentStyles(state.styling, isDarkMode),
52+
[state.styling, isDarkMode],
53+
);
54+
55+
return (
56+
<div
57+
style={currentStyles.variables}
58+
className={currentStyles.classes?.['OrganizationMemberManagement-root']}
59+
>
60+
{!state.hideHeader && (
61+
<div className={currentStyles.classes?.['OrganizationMemberManagement-header']}>
62+
<Header
63+
title={t('header.title')}
64+
description={t('header.description')}
65+
actions={
66+
!state.readOnly
67+
? [
68+
{
69+
type: 'button',
70+
label: t('invite_button'),
71+
onClick: handlers.handleCreateClick,
72+
icon: Plus,
73+
disabled: state.readOnly,
74+
},
75+
]
76+
: []
77+
}
78+
/>
79+
</div>
80+
)}
81+
82+
<Tabs
83+
value={state.activeTab}
84+
onValueChange={(value: string) => handlers.setActiveTab(value as 'members' | 'invitations')}
85+
className={currentStyles.classes?.['OrganizationMemberManagement-tabs']}
86+
>
87+
<TabsList>
88+
<TabsTrigger value="members">{t('tabs.members')}</TabsTrigger>
89+
<TabsTrigger value="invitations">{t('tabs.invitations')}</TabsTrigger>
90+
</TabsList>
91+
92+
<TabsContent value="members">
93+
{/* <OrganizationMemberTable
94+
/> */}
95+
</TabsContent>
96+
97+
<TabsContent value="invitations">
98+
<OrganizationInvitationTable
99+
invitations={state.invitations}
100+
loading={state.isFetchingInvitations}
101+
customMessages={state.customMessages?.invitation}
102+
pagination={state.invitationPagination}
103+
filters={state.invitationFilters}
104+
availableRoles={state.availableRoles}
105+
readOnly={state.readOnly}
106+
sortConfig={state.invitationSortConfig}
107+
onSortChange={handlers.handleSortChange}
108+
onView={handlers.handleDetailsClick}
109+
onCopyUrl={handlers.handleCopyUrl}
110+
onRevokeAndResend={state.readOnly ? undefined : handlers.handleRevokeResendClick}
111+
onRevoke={state.readOnly ? undefined : handlers.handleRevokeClick}
112+
onNextPage={handlers.handleNextPage}
113+
onPreviousPage={handlers.handlePreviousPage}
114+
onPageSizeChange={handlers.handlePageSizeChange}
115+
onRoleFilterChange={handlers.handleRoleFilterChange}
116+
className={currentStyles.classes?.['OrganizationInvitationTab-table']}
117+
/>
118+
</TabsContent>
119+
</Tabs>
120+
121+
<OrganizationInvitationCreateModal
122+
isOpen={state.showCreateModal}
123+
isLoading={state.isCreatingInvitation}
124+
customMessages={state.customMessages?.invitation}
125+
availableRoles={state.availableRoles}
126+
availableProviders={state.availableProviders}
127+
onClose={handlers.handleCreateCancel}
128+
onCreate={handlers.handleCreateSubmit}
129+
className={currentStyles.classes?.['OrganizationInvitationTab-createModal']}
130+
/>
131+
132+
<OrganizationInvitationDetailsModal
133+
invitation={state.selectedInvitation}
134+
isOpen={state.showDetailsModal}
135+
isRevoking={state.isRevokingInvitation}
136+
isResending={state.isResendingInvitation}
137+
customMessages={state.customMessages?.invitation}
138+
availableRoles={state.availableRoles}
139+
availableProviders={state.availableProviders}
140+
readOnly={state.readOnly}
141+
onClose={handlers.handleDetailsClose}
142+
onCopyUrl={handlers.handleCopyUrl}
143+
onRevoke={(invitation) => invitation && handlers.handleRevokeClick(invitation)}
144+
onResend={(invitation) => invitation && handlers.handleRevokeResendClick(invitation)}
145+
className={currentStyles.classes?.['OrganizationInvitationTab-detailsModal']}
146+
/>
147+
148+
<OrganizationInvitationRevokeModal
149+
invitation={state.selectedInvitation}
150+
isOpen={state.showRevokeModal}
151+
isLoading={state.isRevokingInvitation}
152+
customMessages={state.customMessages?.invitation}
153+
onClose={handlers.handleRevokeCancel}
154+
onConfirm={() => handlers.handleRevokeConfirm()}
155+
className={currentStyles.classes?.['OrganizationInvitationTab-revokeModal']}
156+
/>
157+
158+
<OrganizationInvitationRevokeModal
159+
invitation={state.selectedInvitation}
160+
isOpen={state.showRevokeResendModal}
161+
isLoading={state.isResendingInvitation}
162+
isRevokeAndResend
163+
customMessages={state.customMessages?.invitation}
164+
onClose={handlers.handleRevokeResendCancel}
165+
onConfirm={() => handlers.handleRevokeResendConfirm()}
166+
className={currentStyles.classes?.['OrganizationInvitationTab-revokeResendModal']}
167+
/>
168+
</div>
169+
);
170+
}
171+
172+
/**
173+
* Container component for organization member management.
174+
* @param props - The component props.
175+
* @returns The component.
176+
*/
177+
export function OrganizationMemberManagement(props: OrganizationMemberManagementProps) {
178+
const {
179+
hideHeader = false,
180+
defaultTab = 'members',
181+
customMessages = {},
182+
styling = { variables: { common: {}, light: {}, dark: {} }, classes: {} },
183+
readOnly = false,
184+
createInvitationAction,
185+
revokeInvitationAction,
186+
resendInvitationAction,
187+
} = props;
188+
189+
const { state, handlers } = useOrganizationMemberManagement({
190+
customMessages,
191+
defaultTab,
192+
readOnly,
193+
createInvitationAction,
194+
revokeInvitationAction,
195+
resendInvitationAction,
196+
});
197+
198+
const extendedState = {
199+
...state,
200+
styling,
201+
customMessages,
202+
hideHeader,
203+
readOnly,
204+
};
205+
206+
return <OrganizationMemberManagementView state={extendedState} handlers={handlers} />;
207+
}

0 commit comments

Comments
 (0)