Skip to content

Commit

Permalink
refactor: cleanup share button UI logic, make more intuitive
Browse files Browse the repository at this point in the history
  • Loading branch information
danny-avila committed Jan 21, 2025
1 parent e61715d commit 3c1d80b
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 119 deletions.
40 changes: 26 additions & 14 deletions client/src/components/Conversations/ConvoOptions/ShareButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useState, useEffect } from 'react';
import { QRCodeSVG } from 'qrcode.react';
import { Copy, CopyCheck } from 'lucide-react';
import { useGetSharedLinkQuery } from 'librechat-data-provider/react-query';
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
import { useLocalize, useCopyToClipboard } from '~/hooks';
import { Button, Spinner, OGDialog } from '~/components';
import SharedLinkButton from './SharedLinkButton';
import { Spinner, OGDialog } from '~/components';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';

export default function ShareButton({
conversationId,
Expand All @@ -20,9 +22,11 @@ export default function ShareButton({
children?: React.ReactNode;
}) {
const localize = useLocalize();
const { data: share, isLoading } = useGetSharedLinkQuery(conversationId);
const [sharedLink, setSharedLink] = useState('');
const [showQR, setShowQR] = useState(false);
const [sharedLink, setSharedLink] = useState('');
const [isCopying, setIsCopying] = useState(false);
const { data: share, isLoading } = useGetSharedLinkQuery(conversationId);
const copyLink = useCopyToClipboard({ text: sharedLink });

useEffect(() => {
if (share?.shareId !== undefined) {
Expand All @@ -39,11 +43,12 @@ export default function ShareButton({
setShareDialogOpen={onOpenChange}
showQR={showQR}
setShowQR={setShowQR}
sharedLink={sharedLink}
setSharedLink={setSharedLink}
/>
);

const shareId = share?.shareId ?? '';

return (
<OGDialog open={open} onOpenChange={onOpenChange} triggerRef={triggerRef}>
{children}
Expand All @@ -61,12 +66,6 @@ export default function ShareButton({
return <Spinner className="m-auto h-14 animate-spin" />;
}

// if (isUpdated) {
// return isNewSharedLink
// ? localize('com_ui_share_created_message')
// : localize('com_ui_share_updated_message');
// }

return share?.success === true
? localize('com_ui_share_update_message')
: localize('com_ui_share_create_message');
Expand All @@ -79,9 +78,22 @@ export default function ShareButton({
</div>
)}

{share?.shareId !== null && (
<div className="cursor-text break-all text-center text-sm text-text-secondary">
{sharedLink}
{shareId && (
<div className="flex items-center gap-2 rounded-md bg-surface-secondary p-2">
<div className="flex-1 break-all text-sm text-text-secondary">{sharedLink}</div>
<Button
size="sm"
variant="outline"
onClick={() => {
if (isCopying) {
return;
}
copyLink(setIsCopying);
}}
className={cn('shrink-0', isCopying ? 'cursor-default' : '')}
>
{isCopying ? <CopyCheck className="size-4" /> : <Copy className="size-4" />}
</Button>
</div>
)}
</div>
Expand Down
115 changes: 12 additions & 103 deletions client/src/components/Conversations/ConvoOptions/SharedLinkButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import copy from 'copy-to-clipboard';
import { useEffect, useState, useCallback, useRef } from 'react';
import { Copy, Link, QrCode, RotateCw, CopyCheck, Trash2 } from 'lucide-react';
import { useState, useCallback } from 'react';
import { QrCode, RotateCw, Trash2 } from 'lucide-react';
import type { TSharedLinkGetResponse } from 'librechat-data-provider';
import {
useCreateSharedLinkMutation,
Expand All @@ -19,23 +18,19 @@ export default function SharedLinkButton({
setShareDialogOpen,
showQR,
setShowQR,
sharedLink,
setSharedLink,
}: {
share: TSharedLinkGetResponse | undefined;
conversationId: string;
setShareDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
showQR: boolean;
setShowQR: (showQR: boolean) => void;
sharedLink: string;
setSharedLink: (sharedLink: string) => void;
}) {
const localize = useLocalize();
const { showToast } = useToastContext();
const [isCopying, setIsCopying] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const copyTimeoutRef = useRef<number | null>(null);
const shareId = share?.shareId || undefined;
const shareId = share?.shareId ?? '';

const { mutateAsync: mutate, isLoading: isCreateLoading } = useCreateSharedLinkMutation({
onError: () => {
Expand Down Expand Up @@ -75,67 +70,23 @@ export default function SharedLinkButton({
return `${window.location.protocol}//${window.location.host}/share/${shareId}`;
}, []);

const copyLink = () => {
if (shareId === undefined) {
return;
}

if (typeof copyTimeoutRef.current === 'number') {
clearTimeout(copyTimeoutRef.current);
}

setIsCopying(true);
copy(sharedLink);
copyTimeoutRef.current = window.setTimeout(() => {
setIsCopying(false);
}, 1500);
};

useEffect(() => {
return () => {
if (typeof copyTimeoutRef.current === 'number') {
clearTimeout(copyTimeoutRef.current);
}
};
}, []);

const updateSharedLink = async () => {
if (shareId === undefined) {
if (!shareId) {
return;
}
const updateShare = await mutateAsync({ shareId });
const newLink = generateShareLink(updateShare.shareId);
setSharedLink(newLink);

if (typeof copyTimeoutRef.current === 'number') {
clearTimeout(copyTimeoutRef.current);
}

setIsCopying(true);
copy(newLink);
copyTimeoutRef.current = window.setTimeout(() => {
setIsCopying(false);
}, 1500);
};

const createShareLink = async () => {
const share = await mutate({ conversationId });
const newLink = generateShareLink(share.shareId);
setSharedLink(newLink);

if (typeof copyTimeoutRef.current === 'number') {
clearTimeout(copyTimeoutRef.current);
}

setIsCopying(true);
copy(newLink);
copyTimeoutRef.current = window.setTimeout(() => {
setIsCopying(false);
}, 1500);
};

const handleDelete = async () => {
if (shareId === undefined) {
if (!shareId) {
return;
}

Expand All @@ -154,57 +105,16 @@ export default function SharedLinkButton({
}
};

const getHandler = (shareId?: string) => {
if (shareId === undefined) {
return {
handler: async () => {
createShareLink();
},
label: (
<>
<Link className="mr-2 size-4" />
{localize('com_ui_create_link')}
</>
),
};
}

return {
handler: async () => {
copyLink();
},
label: (
<>
<Copy className="mr-2 size-4" />
{localize('com_ui_copy_link')}
</>
),
};
};

const handlers = getHandler(shareId);

return (
<>
<div className="flex gap-2">
<Button
disabled={isCreateLoading || isCopying}
variant="submit"
onClick={() => {
handlers.handler();
}}
>
{!isCopying && !isCreateLoading && handlers.label}
{isCreateLoading && <Spinner className="size-4" />}
{isCopying && (
<>
<CopyCheck className="size-4" />
{localize('com_ui_copied')}
</>
)}
</Button>

{shareId !== undefined && (
{!shareId && (
<Button disabled={isCreateLoading} variant="submit" onClick={createShareLink}>
{!isCreateLoading && localize('com_ui_create_link')}
{isCreateLoading && <Spinner className="size-4" />}
</Button>
)}
{shareId && (
<div className="flex items-center gap-2">
<TooltipAnchor
description={localize('com_ui_refresh_link')}
Expand Down Expand Up @@ -243,7 +153,6 @@ export default function SharedLinkButton({
/>
</div>
)}

<OGDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<OGDialogTemplate
showCloseButton={false}
Expand Down
16 changes: 14 additions & 2 deletions client/src/hooks/Messages/useCopyToClipboard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useEffect, useRef } from 'react';
import copy from 'copy-to-clipboard';
import { ContentTypes } from 'librechat-data-provider';
import type { TMessage } from 'librechat-data-provider';
Expand All @@ -7,8 +7,20 @@ export default function useCopyToClipboard({
text,
content,
}: Partial<Pick<TMessage, 'text' | 'content'>>) {
const copyTimeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
return () => {
if (copyTimeoutRef.current) {
clearTimeout(copyTimeoutRef.current);
}
};
}, []);

const copyToClipboard = useCallback(
(setIsCopied: React.Dispatch<React.SetStateAction<boolean>>) => {
if (copyTimeoutRef.current) {
clearTimeout(copyTimeoutRef.current);
}
setIsCopied(true);
let messageText = text ?? '';
if (content) {
Expand All @@ -22,7 +34,7 @@ export default function useCopyToClipboard({
}
copy(messageText, { format: 'text/plain' });

setTimeout(() => {
copyTimeoutRef.current = setTimeout(() => {
setIsCopied(false);
}, 3000);
},
Expand Down

0 comments on commit 3c1d80b

Please sign in to comment.