import React, { useMemo, useState } from 'react' import { Link, router, usePage } from '@inertiajs/react' import StudioLayout from '../../Layouts/StudioLayout' function formatInviteTimestamp(value) { if (!value) return null try { return new Date(value).toLocaleString() } catch { return value } } export default function StudioGroupInvitations() { const { props } = usePage() const invitations = Array.isArray(props.invitations) ? props.invitations : [] const activeMembers = Array.isArray(props.members) ? props.members.filter((member) => member.status === 'active') : [] const [invite, setInvite] = useState({ username: '', role: 'contributor', note: '', expires_in_days: 7 }) const pendingInvites = useMemo( () => invitations.filter((item) => item.status === 'pending'), [invitations] ) const revokedInvites = useMemo( () => invitations.filter((item) => item.status === 'revoked'), [invitations] ) return (

Group invitations

Invite collaborators into {props.studioGroup?.name}

Pending invites stay separate from active members here, so owners and admins can review who was invited, when the invite expires, and revoke access before acceptance.

Members {pendingInvites.length} pending
setInvite((current) => ({ ...current, username: event.target.value }))} placeholder="Username" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" /> setInvite((current) => ({ ...current, note: event.target.value }))} placeholder="Optional note" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" /> setInvite((current) => ({ ...current, expires_in_days: event.target.value }))} type="number" min="1" max="30" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />

Pending invitations

{pendingInvites.length} outstanding
{pendingInvites.length > 0 ? pendingInvites.map((inviteRow) => (
{inviteRow.user?.avatar_url ? {inviteRow.user.name :
}
{inviteRow.user?.name || inviteRow.user?.username}
{inviteRow.role_label || inviteRow.role}
{inviteRow.invited_by ? Invited by {inviteRow.invited_by.name || inviteRow.invited_by.username} : null} {inviteRow.invited_at ? Sent {formatInviteTimestamp(inviteRow.invited_at)} : null} {inviteRow.expires_at ? Expires {formatInviteTimestamp(inviteRow.expires_at)} : null}
{inviteRow.note ?

{inviteRow.note}

: null}
{inviteRow.can_revoke && inviteRow.revoke_url ? : null}
)) :
No pending invites for this group.
}

Recent invite history

{revokedInvites.length} revoked or expired
{revokedInvites.length > 0 ? revokedInvites.map((inviteRow) => (
{inviteRow.user?.name || inviteRow.user?.username}
{inviteRow.is_expired ? 'Expired' : 'Revoked'} • {inviteRow.role_label || inviteRow.role}
{inviteRow.invited_at ?

Originally sent {formatInviteTimestamp(inviteRow.invited_at)}

: null}
)) :
No recent invite history yet.
}

Active members

{activeMembers.length} active
{activeMembers.slice(0, 6).map((member) => (
{member.user?.avatar_url ? {member.user.name :
}
{member.user?.name || member.user?.username}
{member.role_label || member.role}
))}
) }