@@ -7,16 +7,21 @@ import { toast } from "sonner";
77import { NoticeBanner } from "@/app/(main)/websites/_components/notice-banner" ;
88import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard" ;
99import {
10+ getWebsiteByIdKey ,
11+ getWebsitesListKey ,
1012 updateWebsiteCache ,
1113 useDeleteWebsite ,
1214 useUpdateWebsite ,
1315 useWebsite ,
1416 type Website ,
17+ type WebsitesListData ,
1518} from "@/hooks/use-websites" ;
1619import { orpc } from "@/lib/orpc" ;
20+ import { publicConfig } from "@databuddy/env/public" ;
1721import { TOAST_MESSAGES } from "../../_components/constants/settings-constants" ;
1822import {
1923 ArrowRightIcon ,
24+ ArrowSquareOutIcon ,
2025 CheckIcon ,
2126 ClipboardIcon ,
2227 GlobeIcon ,
@@ -71,6 +76,45 @@ export default function GeneralSettingsPage() {
7176
7277 const toggleMutation = useMutation ( {
7378 ...orpc . websites . togglePublic . mutationOptions ( ) ,
79+ onMutate : async ( { id, isPublic : nextIsPublic } ) => {
80+ const getByIdKey = getWebsiteByIdKey ( id ) ;
81+ const listKey = getWebsitesListKey ( ) ;
82+
83+ await Promise . all ( [
84+ queryClient . cancelQueries ( { queryKey : getByIdKey } ) ,
85+ queryClient . cancelQueries ( { queryKey : listKey } ) ,
86+ ] ) ;
87+
88+ const previousWebsite = queryClient . getQueryData < Website > ( getByIdKey ) ;
89+ const previousList = queryClient . getQueryData < WebsitesListData > ( listKey ) ;
90+ const withPublicState = ( website : Website ) : Website => ( {
91+ ...website ,
92+ isPublic : nextIsPublic ,
93+ } ) ;
94+
95+ queryClient . setQueryData < Website > ( getByIdKey , ( current ) =>
96+ current ? withPublicState ( current ) : current
97+ ) ;
98+ queryClient . setQueryData < WebsitesListData > ( listKey , ( current ) =>
99+ current
100+ ? {
101+ ...current ,
102+ websites : current . websites . map ( ( website ) =>
103+ website . id === id ? withPublicState ( website ) : website
104+ ) ,
105+ }
106+ : current
107+ ) ;
108+
109+ return { getByIdKey, listKey, previousList, previousWebsite } ;
110+ } ,
111+ onError : ( _error , _variables , context ) => {
112+ if ( ! context ) {
113+ return ;
114+ }
115+ queryClient . setQueryData ( context . getByIdKey , context . previousWebsite ) ;
116+ queryClient . setQueryData ( context . listKey , context . previousList ) ;
117+ } ,
74118 onSuccess : ( updatedWebsite : Website ) => {
75119 updateWebsiteCache ( queryClient , updatedWebsite ) ;
76120 queryClient . invalidateQueries ( {
@@ -95,13 +139,14 @@ export default function GeneralSettingsPage() {
95139 onCopy : ( ) => toast . success ( "Public link copied to clipboard" ) ,
96140 } ) ;
97141
98- const isPublic = websiteData ?. isPublic ?? false ;
99- const shareableLink = useMemo ( ( ) => {
100- if ( ! ( websiteData && typeof window !== "undefined" ) ) {
101- return "" ;
102- }
103- return `${ window . location . origin } /public/${ websiteId } ` ;
104- } , [ websiteData , websiteId ] ) ;
142+ const isPublic =
143+ ( toggleMutation . isPending
144+ ? toggleMutation . variables ?. isPublic
145+ : websiteData ?. isPublic ) ?? false ;
146+ const shareableLink = useMemo (
147+ ( ) => `${ publicConfig . urls . dashboard } /public/${ websiteId } ` ,
148+ [ websiteId ]
149+ ) ;
105150
106151 const hasChanges =
107152 ! ! websiteData &&
@@ -173,6 +218,10 @@ export default function GeneralSettingsPage() {
173218 [ websiteData , websiteId , toggleMutation ]
174219 ) ;
175220
221+ const handleOpenPublicPage = useCallback ( ( ) => {
222+ window . open ( shareableLink , "_blank" , "noopener,noreferrer" ) ;
223+ } , [ shareableLink ] ) ;
224+
176225 if ( ! websiteData ) {
177226 return < GeneralSettingsSkeleton /> ;
178227 }
@@ -271,11 +320,11 @@ export default function GeneralSettingsPage() {
271320 </ div >
272321 < div className = "flex items-center gap-2" >
273322 < code className = "min-w-0 flex-1 overflow-x-auto break-all rounded border bg-secondary px-3 py-2 font-mono text-xs" >
274- { shareableLink || `/public/ ${ websiteId } ` }
323+ { shareableLink }
275324 </ code >
276325 < Button
277326 aria-label = "Copy public overview link"
278- disabled = { ! ( isPublic && shareableLink ) }
327+ disabled = { ! isPublic }
279328 onClick = { ( ) => copyLink ( shareableLink ) }
280329 size = "sm"
281330 variant = "ghost"
@@ -286,6 +335,15 @@ export default function GeneralSettingsPage() {
286335 < ClipboardIcon className = "size-4" />
287336 ) }
288337 </ Button >
338+ < Button
339+ aria-label = "Open public overview"
340+ disabled = { ! isPublic }
341+ onClick = { handleOpenPublicPage }
342+ size = "sm"
343+ variant = "ghost"
344+ >
345+ < ArrowSquareOutIcon className = "size-4" />
346+ </ Button >
289347 </ div >
290348 < NoticeBanner
291349 description = {
0 commit comments