feat: Oracle Canvas, Revision History and Canvas Sharing
This commit is contained in:
@@ -10,6 +10,7 @@ interface ShareModalProps {
|
||||
page: CanvasPage | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
currentUserId?: string | null;
|
||||
onShare: (params: {
|
||||
recipientUserId: string;
|
||||
visibility: 'private' | 'team';
|
||||
@@ -40,7 +41,7 @@ function getInitials(member: VelocityActiveUser): string {
|
||||
.join('') || 'U';
|
||||
}
|
||||
|
||||
export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps) {
|
||||
export function ShareModal({ page, isOpen, onClose, currentUserId, onShare }: ShareModalProps) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [teamMembers, setTeamMembers] = useState<VelocityActiveUser[]>([]);
|
||||
const [loadingMembers, setLoadingMembers] = useState(false);
|
||||
@@ -50,6 +51,7 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
const [message, setMessage] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||
const [memberDropOpen, setMemberDropOpen] = useState(false);
|
||||
|
||||
useEffect(() => setMounted(true), []);
|
||||
@@ -57,6 +59,7 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setMemberDropOpen(false);
|
||||
setSubmitError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,6 +86,17 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const availableMembers = useMemo(
|
||||
() => teamMembers.filter((member) => member.user_id !== currentUserId),
|
||||
[teamMembers, currentUserId],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (recipient && recipient.user_id === currentUserId) {
|
||||
setRecipient(null);
|
||||
}
|
||||
}, [recipient, currentUserId]);
|
||||
|
||||
const selectedRecipientLabel = useMemo(
|
||||
() => (recipient ? getDisplayName(recipient) : 'Select verified teammate...'),
|
||||
[recipient],
|
||||
@@ -91,6 +105,7 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
const handleShare = async () => {
|
||||
if (!recipient || !page) return;
|
||||
setSubmitting(true);
|
||||
setSubmitError(null);
|
||||
try {
|
||||
await onShare({
|
||||
recipientUserId: recipient.user_id,
|
||||
@@ -105,8 +120,8 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
setRecipient(null);
|
||||
setMessage('');
|
||||
}, 1800);
|
||||
} catch {
|
||||
// keep modal open and let caller surface the error upstream
|
||||
} catch (error) {
|
||||
setSubmitError(error instanceof Error ? error.message : 'Share failed.');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
@@ -180,6 +195,17 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{submitError && (
|
||||
<div
|
||||
className="rounded-xl px-3 py-2 text-xs text-red-300"
|
||||
style={{
|
||||
background: 'rgba(239,68,68,0.08)',
|
||||
border: '1px solid rgba(239,68,68,0.2)',
|
||||
}}
|
||||
>
|
||||
{submitError}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="text-xs font-medium text-zinc-400 mb-1.5 block">Recipient</label>
|
||||
<div className="relative">
|
||||
@@ -217,10 +243,10 @@ export function ShareModal({ page, isOpen, onClose, onShare }: ShareModalProps)
|
||||
{!loadingMembers && membersError && (
|
||||
<div className="px-3 py-3 text-xs text-red-400">{membersError}</div>
|
||||
)}
|
||||
{!loadingMembers && !membersError && teamMembers.length === 0 && (
|
||||
{!loadingMembers && !membersError && availableMembers.length === 0 && (
|
||||
<div className="px-3 py-3 text-xs text-zinc-500">No verified users available.</div>
|
||||
)}
|
||||
{!loadingMembers && !membersError && teamMembers.map((member) => (
|
||||
{!loadingMembers && !membersError && availableMembers.map((member) => (
|
||||
<button
|
||||
key={member.user_id}
|
||||
className="w-full flex items-center gap-3 px-3 py-2.5 hover:bg-white/5 transition-colors text-left"
|
||||
|
||||
Reference in New Issue
Block a user