Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added auth0-myorganization-js-1.0.0-beta.4.tgz
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
},
"dependencies": {
"@auth0/myaccount-js": "1.0.0-beta.0",
"@auth0/myorganization-js": "1.0.0",
"@auth0/myorganization-js": "file:../../auth0-myorganization-js-1.0.0-beta.4.tgz",
"zod": "^3.22.4"
}
}
1 change: 1 addition & 0 deletions packages/core/src/services/my-organization/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
export * from './organization-management';
export * from './idp-management';
export * from './domain-management';
export * from './member-management/member-management-types';
export * from './config';
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Member management type definitions for organization member and invitation operations.
* @module member-management-types
* @internal
*/
import type { MyOrganization } from '@auth0/myorganization-js';

/**
* Organization member ID type.
*/
export type OrgMemberId = MyOrganization.OrgMemberId;
Comment thread
NaveenChand755 marked this conversation as resolved.

/**
* Organization member entity.
*/
export type OrgMember = MyOrganization.OrgMember;

/**
* Organization member role.
*/
export type OrgMemberRole = MyOrganization.OrgMemberRole;

/**
* Organization member role ID.
*/
export type OrgMemberRoleId = MyOrganization.OrgMemberRoleId;

/**
* Response content for listing organization members.
*/
export type ListOrganizationMembersResponseContent =
MyOrganization.ListOrganizationMembersResponseContent;

/**
* Response content for getting a single organization member.
*/
export type GetOrganizationMemberResponseContent =
MyOrganization.GetOrganizationMemberResponseContent;

/**
* Request parameters for listing organization members.
*/
export type ListOrganizationMembersRequestParameters =
MyOrganization.ListOrganizationMembersRequestParameters;

/**
* Response content for getting organization member roles.
*/
export type GetOrganizationMemberRolesResponseContent =
MyOrganization.GetOrganizationMemberRolesResponseContent;

/**
* Request content for assigning a role to an organization member.
*/
export type AssignOrganizationMemberRoleRequestContent =
MyOrganization.AssignOrganizationMemberRoleRequestContent;

/**
* Response content for assigning a role to an organization member.
*/
export type AssignOrganizationMemberRoleResponseContent =
MyOrganization.AssignOrganizationMemberRoleResponseContent;

/**
* Invitation ID type.
*/
export type InvitationId = MyOrganization.InvitationId;

/**
* Member invitation entity.
*/
export type MemberInvitation = MyOrganization.MemberInvitation;

/**
* Member invitation invitee details.
*/
export type MemberInvitationInvitee = MyOrganization.MemberInvitationInvitee;

/**
* Member invitation inviter details.
*/
export type MemberInvitationInviter = MyOrganization.MemberInvitationInviter;

/**
* Response content for listing member invitations.
*/
export type ListMembersInvitationsResponseContent =
MyOrganization.ListMembersInvitationsResponseContent;

/**
* Request parameters for listing member invitations.
*/
export type ListMemberInvitationsRequestParameters =
MyOrganization.ListMemberInvitationsRequestParameters;

/**
* Request content for creating a member invitation.
*/
export type CreateMemberInvitationRequestContent =
MyOrganization.CreateMemberInvitationRequestContent;

/**
* Response content for creating a member invitation.
*/
export type CreateMemberInvitationResponseContent =
MyOrganization.CreateMemberInvitationResponseContent;

/**
* Response content for getting a member invitation.
*/
export type GetMemberInvitationResponseContent = MyOrganization.GetMemberInvitationResponseContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/**
* Organization invitation details modal component.
* @module organization-invitation-details-modal
*/

import { Link } from 'lucide-react';
import * as React from 'react';

import { CopyableTextField } from '@/components/auth0/shared/copyable-text-field';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Spinner } from '@/components/ui/spinner';
import { TextField } from '@/components/ui/text-field';
import { TextFieldGroup } from '@/components/ui/text-field-group';
import { useTranslator } from '@/hooks/shared/use-translator';
import { getInvitationStatus } from '@/lib/utils/my-organization/member-management/member-management-utils';
import type {
InvitationStatus,
OrganizationInvitationDetailsModalProps,
} from '@/types/my-organization/member-management/organization-invitation-table-types';

export type { OrganizationInvitationDetailsModalProps };

/**
* Returns the badge variant for a given invitation status.
* @param status - The invitation status.
* @returns The badge variant string.
*/
function getStatusBadgeVariant(status: InvitationStatus): 'warning' | 'destructive' {
return status === 'pending' ? 'warning' : 'destructive';
}

/**
* Modal for viewing invitation details with revoke and resend actions.
* @param props - The component props.
* @param props.invitation - The invitation to display.
* @param props.isOpen - Whether the modal is open.
* @param props.isRevoking - Whether a revoke action is in progress.
* @param props.isResending - Whether a resend action is in progress.
* @param props.customMessages - Custom translation messages.
* @param props.availableRoles - Available roles for display.
* @param props.availableProviders - Available providers for display.
* @param props.readOnly - Whether in read-only mode.
* @param props.onClose - Callback when modal is closed.
* @param props.onCopyUrl - Callback when copy URL is clicked.
* @param props.onRevoke - Callback when revoke is clicked.
* @param props.onResend - Callback when revoke and resend is clicked.
* @param props.className - Optional CSS class name.
* @returns The modal component.
*/
export function OrganizationInvitationDetailsModal({
invitation,
isOpen,
isRevoking = false,
isResending = false,
customMessages = {},
availableRoles = [],
availableProviders = [],
readOnly = false,
onClose,
onCopyUrl,
onRevoke,
onResend,
className,
}: OrganizationInvitationDetailsModalProps): React.JSX.Element {
const { t } = useTranslator('member_management', customMessages);

const status = invitation ? getInvitationStatus(invitation) : 'pending';
const isPending = status === 'pending';
const isActionInProgress = isRevoking || isResending;

const roleNames = React.useMemo(() => {
if (!invitation?.roles || invitation.roles.length === 0) return [];
return invitation.roles
.map((roleId) => {
const role = availableRoles.find((r) => r.id === roleId);
return role?.name ?? roleId;
})
.filter(Boolean);
}, [invitation?.roles, availableRoles]);

const providerName = React.useMemo(() => {
if (!invitation?.identity_provider_id) return null;
const provider = availableProviders.find((p) => p.id === invitation.identity_provider_id);
return provider?.name ?? invitation.identity_provider_id;
}, [invitation?.identity_provider_id, availableProviders]);

const handleCopyUrl = React.useCallback(() => {
if (invitation) {
onCopyUrl?.(invitation);
}
}, [invitation, onCopyUrl]);

const handleRevoke = React.useCallback(() => {
if (invitation) {
onRevoke?.(invitation);
}
}, [invitation, onRevoke]);

const handleResend = React.useCallback(() => {
if (invitation) {
onResend?.(invitation);
}
}, [invitation, onResend]);

return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className={className}>
<DialogHeader>
<div className="flex items-center gap-2">
<DialogTitle>{t('invitation.details.title')}</DialogTitle>
<Badge variant={getStatusBadgeVariant(status)} size="sm">
{isPending
? t('invitation.table.status_pending')
: t('invitation.table.status_expired')}
</Badge>
</div>
<DialogDescription className="sr-only">{t('invitation.details.title')}</DialogDescription>
</DialogHeader>

<div className="space-y-4 py-4">
{/* Email */}
<div className="space-y-2">
<Label className="text-sm font-medium text-muted-foreground">
{t('invitation.details.email_label')}
</Label>
<TextField value={invitation?.invitee?.email ?? '-'} readOnly />
</div>

{/* Created At */}
<div className="space-y-2">
<Label className="text-sm font-medium text-muted-foreground">
{t('invitation.details.created_at_label')}
</Label>
<TextField
value={
invitation?.created_at ? new Date(invitation.created_at).toLocaleString() : '-'
Comment thread
NaveenChand755 marked this conversation as resolved.
}
readOnly
/>
</div>

{/* Expires At */}
<div className="space-y-2">
<Label className="text-sm font-medium text-muted-foreground">
{t('invitation.details.expires_at_label')}
</Label>
<TextField
value={
invitation?.expires_at ? new Date(invitation.expires_at).toLocaleString() : '-'
}
readOnly
/>
</div>

{/* Roles */}
<div className="space-y-2">
<Label className="text-sm font-medium text-muted-foreground">
{t('invitation.details.roles_label')}
</Label>
{roleNames.length > 0 ? (
<TextFieldGroup
chips={roleNames.map((name) => ({ label: name, value: name }))}
summarizeChips={false}
disabled
readOnly
/>
) : (
<TextField value="-" readOnly />
)}
</div>

{/* Invitation URL */}
{invitation?.invitation_url && (
<div className="space-y-2">
<Label className="text-sm font-medium text-muted-foreground">
{t('invitation.details.invitation_url_label')}
</Label>
<CopyableTextField
value={invitation.invitation_url}
readOnly
onCopy={handleCopyUrl}
startAdornment={<Link className="h-4 w-4 text-muted-foreground" />}
/>
</div>
)}

{/* Revoke / Resend Actions (inline, below invitation URL) */}
{!readOnly && (
<div className="flex flex-wrap gap-2">
<Button variant="outline" onClick={handleResend} disabled={isActionInProgress}>
{isResending ? <Spinner size="sm" /> : null}
{t('invitation.details.resend_button')}
</Button>
<Button variant="destructive" onClick={handleRevoke} disabled={isActionInProgress}>
Comment thread
NaveenChand755 marked this conversation as resolved.
{isRevoking ? <Spinner size="sm" /> : null}
{t('invitation.details.revoke_button')}
</Button>
</div>
)}

{/* Invited By */}
<div className="space-y-2">
<Label className="text-sm font-medium text-muted-foreground">
{t('invitation.details.invited_by_label')}
</Label>
<TextField value={invitation?.inviter?.name ?? '-'} readOnly />
</div>

{/* Identity Provider */}
{providerName && (
<div className="space-y-2">
<Label className="text-sm font-medium text-muted-foreground">
{t('invitation.details.provider_label')}
</Label>
<TextField value={providerName} readOnly />
</div>
)}
</div>

<DialogFooter>
<Button onClick={onClose}>{t('invitation.details.close_button')}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Loading
Loading