mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-08 15:22:39 +02:00
feat: update UI for snapshot-based public sharing
This commit is contained in:
parent
6aff69f4ec
commit
98991d2ed4
6 changed files with 39 additions and 155 deletions
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { Globe, Link2, User, Users } from "lucide-react";
|
||||
import { Globe, User, Users } from "lucide-react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { togglePublicShareMutationAtom } from "@/atoms/chat/chat-thread-mutation.atoms";
|
||||
import { createSnapshotMutationAtom } from "@/atoms/chat/chat-thread-mutation.atoms";
|
||||
import { currentThreadAtom, setThreadVisibilityAtom } from "@/atoms/chat/current-thread.atom";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
|
|
@ -49,19 +49,15 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS
|
|||
|
||||
// Use Jotai atom for visibility (single source of truth)
|
||||
const currentThreadState = useAtomValue(currentThreadAtom);
|
||||
const setCurrentThreadState = useSetAtom(currentThreadAtom);
|
||||
const setThreadVisibility = useSetAtom(setThreadVisibilityAtom);
|
||||
|
||||
// Public share mutation
|
||||
const { mutateAsync: togglePublicShare, isPending: isTogglingPublic } = useAtomValue(
|
||||
togglePublicShareMutationAtom
|
||||
// Snapshot creation mutation
|
||||
const { mutateAsync: createSnapshot, isPending: isCreatingSnapshot } = useAtomValue(
|
||||
createSnapshotMutationAtom
|
||||
);
|
||||
|
||||
// Use Jotai visibility if available (synced from chat page), otherwise fall back to thread prop
|
||||
const currentVisibility = currentThreadState.visibility ?? thread?.visibility ?? "PRIVATE";
|
||||
const isPublicEnabled =
|
||||
currentThreadState.publicShareEnabled ?? thread?.public_share_enabled ?? false;
|
||||
const publicShareToken = currentThreadState.publicShareToken ?? null;
|
||||
|
||||
const handleVisibilityChange = useCallback(
|
||||
async (newVisibility: ChatVisibility) => {
|
||||
|
|
@ -96,45 +92,24 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS
|
|||
[thread, currentVisibility, onVisibilityChange, queryClient, setThreadVisibility]
|
||||
);
|
||||
|
||||
const handlePublicShareToggle = useCallback(async () => {
|
||||
const handleCreatePublicLink = useCallback(async () => {
|
||||
if (!thread) return;
|
||||
|
||||
try {
|
||||
const response = await togglePublicShare({
|
||||
thread_id: thread.id,
|
||||
enabled: !isPublicEnabled,
|
||||
});
|
||||
|
||||
// Update atom state with response
|
||||
setCurrentThreadState((prev) => ({
|
||||
...prev,
|
||||
publicShareEnabled: response.enabled,
|
||||
publicShareToken: response.share_token,
|
||||
}));
|
||||
await createSnapshot({ thread_id: thread.id });
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to toggle public share:", error);
|
||||
console.error("Failed to create public link:", error);
|
||||
}
|
||||
}, [thread, isPublicEnabled, togglePublicShare, setCurrentThreadState]);
|
||||
|
||||
const handleCopyPublicLink = useCallback(async () => {
|
||||
if (!publicShareToken) return;
|
||||
|
||||
const publicUrl = `${window.location.origin}/public/${publicShareToken}`;
|
||||
await navigator.clipboard.writeText(publicUrl);
|
||||
toast.success("Public link copied to clipboard");
|
||||
}, [publicShareToken]);
|
||||
}, [thread, createSnapshot]);
|
||||
|
||||
// Don't show if no thread (new chat that hasn't been created yet)
|
||||
if (!thread) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const CurrentIcon = isPublicEnabled ? Globe : currentVisibility === "PRIVATE" ? User : Users;
|
||||
const buttonLabel = isPublicEnabled
|
||||
? "Public"
|
||||
: currentVisibility === "PRIVATE"
|
||||
? "Private"
|
||||
: "Shared";
|
||||
const CurrentIcon = currentVisibility === "PRIVATE" ? User : Users;
|
||||
const buttonLabel = currentVisibility === "PRIVATE" ? "Private" : "Shared";
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
|
|
@ -211,67 +186,31 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS
|
|||
{/* Divider */}
|
||||
<div className="border-t border-border my-1" />
|
||||
|
||||
{/* Public Share Option */}
|
||||
{/* Public Link Option */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handlePublicShareToggle}
|
||||
disabled={isTogglingPublic}
|
||||
onClick={handleCreatePublicLink}
|
||||
disabled={isCreatingSnapshot}
|
||||
className={cn(
|
||||
"w-full flex items-center gap-2.5 px-2.5 py-2 rounded-md transition-all",
|
||||
"hover:bg-accent/50 cursor-pointer",
|
||||
"focus:outline-none",
|
||||
"disabled:opacity-50 disabled:cursor-not-allowed",
|
||||
isPublicEnabled && "bg-accent/80"
|
||||
"disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"size-7 rounded-md shrink-0 grid place-items-center",
|
||||
isPublicEnabled ? "bg-primary/10" : "bg-muted"
|
||||
)}
|
||||
>
|
||||
<Globe
|
||||
className={cn(
|
||||
"size-4 block",
|
||||
isPublicEnabled ? "text-primary" : "text-muted-foreground"
|
||||
)}
|
||||
/>
|
||||
<div className="size-7 rounded-md shrink-0 grid place-items-center bg-muted">
|
||||
<Globe className="size-4 block text-muted-foreground" />
|
||||
</div>
|
||||
<div className="flex-1 text-left min-w-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className={cn("text-sm font-medium", isPublicEnabled && "text-primary")}>
|
||||
Public
|
||||
<span className="text-sm font-medium">
|
||||
{isCreatingSnapshot ? "Creating link..." : "Create public link"}
|
||||
</span>
|
||||
{isPublicEnabled && (
|
||||
<span className="text-xs bg-primary/10 text-primary px-1.5 py-0.5 rounded">
|
||||
ON
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-0.5 leading-snug">
|
||||
Anyone with the link can read
|
||||
Creates a shareable snapshot of this chat
|
||||
</p>
|
||||
</div>
|
||||
{isPublicEnabled && publicShareToken && (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCopyPublicLink();
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.stopPropagation();
|
||||
handleCopyPublicLink();
|
||||
}
|
||||
}}
|
||||
className="shrink-0 p-1.5 rounded-md hover:bg-muted transition-colors cursor-pointer"
|
||||
title="Copy public link"
|
||||
>
|
||||
<Link2 className="size-4 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue