-
Notifications
You must be signed in to change notification settings - Fork 5
feat(core, react): add service types and invitation components #160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rax7389
wants to merge
11
commits into
main
Choose a base branch
from
feat/mm-invitations-components
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
6c8ea1a
feat(core, react): add service, react types, and invitation components
rax7389 53b1407
chore: update core package.json for myorganization-js
rax7389 2d01b35
Merge branch 'feat/mm-invitations-core-types' into feat/mm-invitation…
rax7389 e44e20a
refactor(react): addressed review comments
rax7389 bf676e1
Merge branch 'feat/mm-invitations-core-types' into feat/mm-invitation…
rax7389 b194e31
chore: review commends addressed
rax7389 c4447f7
chore: review comments addressed
rax7389 91d66ba
chore: addressed review comments
rax7389 be5a762
Merge branch 'feat/mm-invitations-core-types' into feat/mm-invitation…
rax7389 15fc629
Merge branch 'main' into feat/mm-invitations-components
rax7389 900f8c7
chore: addressed review comments
rax7389 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
packages/core/src/services/my-organization/member-management/member-management-types.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
|
|
||
| /** | ||
| * 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; | ||
236 changes: 236 additions & 0 deletions
236
...ember-management/invitations/invitation-details/organization-invitation-details-modal.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() : '-' | ||
|
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}> | ||
|
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> | ||
| ); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.