Skip to content

feat(core, react): add service types and invitation components#160

Open
rax7389 wants to merge 9 commits intofeat/mm-invitations-core-typesfrom
feat/mm-invitations-components
Open

feat(core, react): add service types and invitation components#160
rax7389 wants to merge 9 commits intofeat/mm-invitations-core-typesfrom
feat/mm-invitations-components

Conversation

@rax7389
Copy link
Copy Markdown
Contributor

@rax7389 rax7389 commented Mar 14, 2026

Changes

Summary: Adds MyOrg-dependent service types (@auth0/myorganization-js type aliases), React types/utilities that depend on MemberInvitation, and all presentational invitation components — details modal, revoke modal, table with actions, create modal with multi-email input, and search filter. All components are prop-driven and fully translatable.

Why: This PR bridges types to the actual @auth0/myorganization-js SDK and provides the complete set of presentational UI components needed for the invitation management flow. React types that depend on MemberInvitation are co-located here with the core type definitions. Separating components (this PR) from the orchestrating hook (PR 4) keeps the review focused.

What:

Core Service Types (member-management-types.ts)

Type aliases mapping internal types to @auth0/myorganization-js SDK types for members and invitations.

export type OrgMember = MyOrganization.OrgMember;
export type MemberInvitation = MyOrganization.MemberInvitation;
export type ListMemberInvitationsRequestParameters = MyOrganization.ListMemberInvitationsRequestParameters;
export type CreateMemberInvitationRequestContent = MyOrganization.CreateMemberInvitationRequestContent;

React Types (moved from PR 2)

  • organization-invitation-table-types.ts — Core entity and component prop types:
interface Invitation { id: string; invitee: { email: string }; status?: InvitationStatus; ... }
interface InvitationPaginationState { pageSize: number; currentPage: number; hasNextPage: boolean; ... }
interface InvitationSortConfig { key: string | null; direction: 'asc' | 'desc'; }
interface OrganizationInvitationTableProps { invitations: Invitation[]; pagination: ...; sortConfig?: ...; }
  • organization-member-management-types.ts — Top-level management types including service layer types:
interface UseMemberManagementServiceOptions { activeTab: ActiveTab; invitationParams: TableQueryParams<...>; ... }
interface MemberManagementServiceResult { invitationsQuery: UseQueryResult<...>; createInvitationMutation: ...; }
interface OrganizationMemberManagementProps extends SharedComponentProps<...> { ... }

Utilities (react)

  • member-management-utils.tsgetInvitationStatus() helper that computes status from expires_at when no explicit status is set.
getInvitationStatus({ expires_at: "2024-01-01T00:00:00Z" }); // "expired"
getInvitationStatus({ status: "pending" });                    // "pending"

Presentational Components

  1. InvitationDetailsModal — Read-only modal displaying all invitation fields (email, status, dates, roles, invitation URL with copy, identity provider) with inline revoke/resend action buttons.
image
  1. InvitationRevokeModal — Reusable confirmation dialog that handles both revoke and revoke-and-resend flows via the isRevokeAndResend prop.
image
  1. InvitationTableActionsColumn — Dropdown menu with context-aware actions: "View Details" (always), "Copy URL" (pending only), "Revoke and Resend", and "Revoke" (hidden in read-only mode).
image
  1. InvitationTable — Data table with sortable columns (email, status, created_at, expires_at, inviter), checkpoint-based pagination via DataPagination, role filtering via SearchFilter, and the actions column above.
image
  1. InvitationCreateModal — Form modal with multi-email input (TextFieldGroup with chips), multi-role selection (Combobox), identity provider dropdown (Select), and Zod-powered validation.
image
  1. SearchFilter — Role filter dropdown with reset button, shown above the invitation table when roles are available.
<SearchFilter
  filters={{ roleId: "role_admin" }}
  availableRoles={roles}
  onRoleFilterChange={handleFilterChange}
/>

Other

  • package.json: Updated @auth0/myorganization-js dependency
  • pnpm-lock.yaml: Lock file updates

References

Part of the member management invitations feature.

Testing

  • This change adds unit test coverage

Component tests are in PR 5. These are presentational components receiving all data via props.

  • This change has been tested on the latest version of the platform/language

Checklist

@rax7389 rax7389 force-pushed the feat/mm-invitations-core-types branch from d32ea46 to a3edf1e Compare March 14, 2026 12:06
@rax7389 rax7389 force-pushed the feat/mm-invitations-components branch from 7b93170 to bf603bf Compare March 14, 2026 12:07
@rax7389 rax7389 self-assigned this Mar 15, 2026
@rax7389 rax7389 added the enhancement New feature or request label Mar 15, 2026
@rax7389 rax7389 force-pushed the feat/mm-invitations-core-types branch from a3edf1e to 038ed92 Compare March 29, 2026 17:43
@rax7389 rax7389 force-pushed the feat/mm-invitations-components branch from bf603bf to e29c388 Compare March 29, 2026 17:44
@rax7389 rax7389 force-pushed the feat/mm-invitations-core-types branch from 038ed92 to 27b3080 Compare April 8, 2026 09:03
@rax7389 rax7389 force-pushed the feat/mm-invitations-components branch 3 times, most recently from 1397fc9 to 3fef17b Compare April 8, 2026 10:59
@rax7389 rax7389 force-pushed the feat/mm-invitations-core-types branch from a6cfe0d to 2b6461e Compare April 8, 2026 11:07
@rax7389 rax7389 closed this Apr 8, 2026
@rax7389 rax7389 force-pushed the feat/mm-invitations-components branch from 3fef17b to 2b6461e Compare April 8, 2026 11:07
@rax7389 rax7389 reopened this Apr 8, 2026
@rax7389 rax7389 closed this Apr 8, 2026
@rax7389 rax7389 force-pushed the feat/mm-invitations-components branch from 9f53be5 to e9c796d Compare April 8, 2026 11:17
@rax7389 rax7389 reopened this Apr 8, 2026
return (
<div className={className ?? 'mb-4 flex items-center justify-end gap-2'}>
<Select value={filters?.roleId ?? 'all'} onValueChange={handleRoleFilterChange}>
<SelectTrigger className="w-auto min-w-[180px]">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aria-label here would improve accessibility

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

/** OrganizationInvitationTab translation messages. */
export interface OrganizationInvitationTabMessages {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldnt this be defined in core ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

/**
* Organization member ID type.
*/
export type OrgMemberId = MyOrganization.OrgMemberId;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we previously did replace all org to organization, should we apply the same here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good observation let me raise this with API/SDK

invitationPagination: InvitationPaginationState;
invitationFilters: InvitationFilterState;
invitationSortConfig: InvitationSortConfig;
showCreateModal: boolean;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we simplify this

// 2. UI State is now a single source of truth
  activeModal: 'create' | 'details' | 'revoke' | 'resend' | null;
  selectedInvitation: MemberInvitation | null;

  // 3. Consolidated Handlers
  setActiveTab: (tab: ActiveTab) => void;

  /** Opens any modal and optionally sets the target invitation */
  openModal: (type: 'create' | 'details' | 'revoke' | 'resend', invitation?: MemberInvitation) => void;
  
  /** Clears activeModal and selectedInvitation in one go */
  closeModal: () => void;

  /** Unified submission for create/revoke/resend based on activeModal */
  onConfirm: (data?: CreateInvitationInput) => void;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

invitationData.inviter = { name: inviterName };
}

onCreate(invitationData);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bulk invite is not supported ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is there but API is still in development, but let me check sdk signature once

e.preventDefault();

// Add any remaining input to emails
const finalEmails = emailChips.map((chip) => chip.value);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do

const finalEmails = [...new Set([
      ...emailChips.map((chip) => chip.value),
      ...(trimmedInput ? [trimmedInput] : []),
    ])];

    // 3. Early Exit
    if (finalEmails.length === 0) {
      setEmailError(t('invitation.create.email_required_error'));
      return;
    }

    // 4. Construct Base Data once 
    // Using conditional spread keeps the object definition atomic and clean.
    const baseInvitation = {
      roles: selectedRoles.length > 0 ? selectedRoles : undefined,
      identity_provider_id: selectedProvider,
      ...(inviterName && { inviter: { name: inviterName } }),
    };

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did some updates


if (!trimmedEmail) return;

if (emailChips.length >= validationConfig.maxEmails) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i feel this function can be simplified further

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

@rax7389 rax7389 requested a review from NaveenChand755 April 17, 2026 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants