1+ import React , { useEffect , useState } from 'react' ;
2+ import { useClient } from 'sanity' ;
3+ import { CopyIcon } from '@sanity/icons' ;
4+ import { Button , Dialog , Text , Stack , Card } from '@sanity/ui' ;
5+ import { RecycleIcon } from 'lucide-react' ;
6+
7+ interface SharePreviewActionButtonProps {
8+ id : string ;
9+ type : string ;
10+ onClose : ( ) => void ;
11+ }
12+
13+ const SharePreviewActionButton : React . FC < SharePreviewActionButtonProps > = ( { id, type, onClose } ) => {
14+ const [ shareUrl , setShareUrl ] = useState < string | null > ( null ) ;
15+ const [ loading , setLoading ] = useState ( false ) ;
16+ const [ error , setError ] = useState < string | null > ( null ) ;
17+ const [ copied , setCopied ] = useState ( false ) ;
18+ const client = useClient ( { apiVersion : '2025-09-22' } ) ;
19+
20+ const fetchShareUrl = async ( ) => {
21+ setLoading ( true ) ;
22+ setError ( null ) ;
23+ setShareUrl ( null ) ;
24+ console . log ( 'Generating preview link for document ID:' , id ) ;
25+ try {
26+ const res = await fetch ( '/api/generate-preview-token' , {
27+ method : 'POST' ,
28+ headers : { 'Content-Type' : 'application/json' } ,
29+ body : JSON . stringify ( {
30+ documentId : id ,
31+ secret : process . env . NEXT_PUBLIC_PREVIEW_TOKEN_SECRET ,
32+ } ) ,
33+ } ) ;
34+ const data = await res . json ( ) ;
35+ if ( res . ok && data . token ) {
36+ setShareUrl ( `${ window . location . origin } /preview/${ data . token } ` ) ;
37+ } else {
38+ setError ( data . error || 'Failed to generate link' ) ;
39+ }
40+ } catch ( e ) {
41+ setError ( 'Error generating link' ) ;
42+ } finally {
43+ setLoading ( false ) ;
44+ }
45+ } ;
46+
47+ useEffect ( ( ) => {
48+ fetchShareUrl ( ) ;
49+ // eslint-disable-next-line react-hooks/exhaustive-deps
50+ } , [ ] ) ;
51+
52+ return (
53+ < Dialog
54+ header = "Shareable Preview Link"
55+ id = "share-preview-dialog"
56+ width = { 1 }
57+ onClose = { onClose }
58+ >
59+ < Card padding = { 4 } >
60+ < Stack space = { 3 } >
61+ { loading && < Text > Generating link...</ Text > }
62+ { ! loading && shareUrl && < >
63+ < Text as = "p" > Copy and share this link:</ Text >
64+ < Text as = "code" > { shareUrl } </ Text >
65+ < Button
66+ text = { copied ? 'Copied!' : 'Copy Link' }
67+ icon = { CopyIcon }
68+ tone = "primary"
69+ disabled = { copied }
70+ onClick = { async ( ) => {
71+ if ( shareUrl ) {
72+ await navigator . clipboard . writeText ( shareUrl ) ;
73+ setCopied ( true ) ;
74+ setTimeout ( ( ) => setCopied ( false ) , 1000 ) ;
75+ }
76+ } }
77+ style = { { marginTop : 8 } }
78+ />
79+ < Button
80+ text = "Regenerate"
81+ icon = { RecycleIcon }
82+ tone = "primary"
83+ onClick = { async ( ) => {
84+ await fetchShareUrl ( ) ;
85+ } }
86+ style = { { marginTop : 8 } }
87+ />
88+ </ > }
89+ { ! loading && error && (
90+ < Card padding = { 3 } tone = "critical" border radius = { 2 } style = { { marginTop : 8 } } >
91+ < Text > { error } </ Text >
92+ </ Card >
93+ ) }
94+ </ Stack >
95+ </ Card >
96+ </ Dialog >
97+ ) ;
98+ } ;
99+
100+ export default SharePreviewActionButton ;
0 commit comments