@@ -37,14 +37,17 @@ import {
3737} from '@teable/ui-lib/shadcn' ;
3838import { toast } from '@teable/ui-lib/shadcn/ui/sonner' ;
3939import confetti from 'canvas-confetti' ;
40- import { Camera , Send , Copy } from 'lucide-react' ;
40+ import { Camera , Send , Copy , ExternalLink } from 'lucide-react' ;
4141import { useTranslation } from 'next-i18next' ;
42- import { useState , useRef , useEffect , useMemo } from 'react' ;
42+ import { useState , useRef , useEffect , useMemo , useCallback } from 'react' ;
4343import { useIsCloud } from '@/features/app/hooks/useIsCloud' ;
4444import { ROOT_ID } from '../../../base/base-node/hooks' ;
4545import { useBaseNodeContext } from '../../../base/base-node/hooks/useBaseNodeContext' ;
46+ import { useAppPublishContext } from './AppPublishContext' ;
4647import { NodeSelect } from './NodeSelect' ;
4748import { NodeTreeSelect } from './NodeTreeSelect' ;
49+ import type { IUnpublishedApp } from './UnpublishedAppsDialog' ;
50+ import { UnpublishedAppsDialog , getUnpublishedAppNodes } from './UnpublishedAppsDialog' ;
4851
4952const attachmentManager = new AttachmentManager ( 1 ) ;
5053
@@ -73,6 +76,7 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
7376 const baseId = base ?. id ;
7477 const { treeItems } = useBaseNodeContext ( ) ;
7578 const isCloud = useIsCloud ( ) ;
79+ const appPublishContext = useAppPublishContext ( ) ;
7680
7781 const queryClient = useQueryClient ( ) ;
7882
@@ -210,6 +214,9 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
210214 const [ hasLoadedTemplate , setHasLoadedTemplate ] = useState ( false ) ;
211215 const [ successDialogOpen , setSuccessDialogOpen ] = useState ( false ) ;
212216 const [ shareUrl , setShareUrl ] = useState ( '' ) ;
217+ const [ unpublishedAppsDialogOpen , setUnpublishedAppsDialogOpen ] = useState ( false ) ;
218+ const [ unpublishedApps , setUnpublishedApps ] = useState < IUnpublishedApp [ ] > ( [ ] ) ;
219+ const [ externalApps , setExternalApps ] = useState < IUnpublishedApp [ ] | undefined > ( undefined ) ;
213220
214221 // Initialize selected nodes on first load
215222 useEffect ( ( ) => {
@@ -348,6 +355,35 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
348355 } ) ;
349356 } ;
350357
358+ const handlePublishClick = useCallback ( ( ) => {
359+ if ( ! title || ! description ) {
360+ toast . error ( t ( 'publishBase.tips.publishValidation' ) ) ;
361+ return ;
362+ }
363+
364+ if ( selectedNodeIds . length === 0 ) {
365+ toast . error ( t ( 'publishBase.tips.atLeastOneNode' ) ) ;
366+ return ;
367+ }
368+
369+ // Check for unpublished app nodes
370+ const unpublishedAppNodes = getUnpublishedAppNodes ( selectedNodeIds , treeItems ) ;
371+ if ( unpublishedAppNodes . length > 0 ) {
372+ setUnpublishedApps ( unpublishedAppNodes ) ;
373+ setExternalApps ( undefined ) ; // Reset external apps state
374+ setUnpublishedAppsDialogOpen ( true ) ;
375+ return ;
376+ }
377+
378+ // No unpublished apps, proceed with publishing
379+ publishBaseMutate ( { title, description : description || '' } ) ;
380+ } , [ title , description , selectedNodeIds , treeItems , publishBaseMutate , t ] ) ;
381+
382+ const handleContinuePublish = useCallback ( ( ) => {
383+ setUnpublishedAppsDialogOpen ( false ) ;
384+ publishBaseMutate ( { title, description : description || '' } ) ;
385+ } , [ title , description , publishBaseMutate ] ) ;
386+
351387 return (
352388 < >
353389 < Dialog open = { open } onOpenChange = { setOpen } >
@@ -457,19 +493,7 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
457493 ) }
458494 < Button
459495 className = "flex w-full items-center gap-2"
460- onClick = { ( ) => {
461- if ( ! title || ! description ) {
462- toast . error ( t ( 'publishBase.tips.publishValidation' ) ) ;
463- return ;
464- }
465-
466- if ( selectedNodeIds . length === 0 ) {
467- toast . error ( t ( 'publishBase.tips.atLeastOneNode' ) ) ;
468- return ;
469- }
470-
471- publishBaseMutate ( { title, description : description || '' } ) ;
472- } }
496+ onClick = { handlePublishClick }
473497 disabled = { publishBaseLoading }
474498 >
475499 < Send className = "size-4" />
@@ -576,6 +600,14 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
576600 >
577601 < Copy className = "size-4" />
578602 </ Button >
603+ < Button
604+ size = "icon"
605+ variant = "ghost"
606+ className = "size-9 shrink-0 rounded-none border-l p-0"
607+ onClick = { ( ) => window . open ( shareUrl , '_blank' ) }
608+ >
609+ < ExternalLink className = "size-4" />
610+ </ Button >
579611 </ div >
580612 ) }
581613 </ div >
@@ -614,6 +646,14 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
614646 < Button size = "sm" variant = "outline" className = "size-9 p-0" onClick = { handleCopyUrl } >
615647 < Copy className = "size-4" />
616648 </ Button >
649+ < Button
650+ size = "sm"
651+ variant = "outline"
652+ className = "size-9 p-0"
653+ onClick = { ( ) => window . open ( shareUrl , '_blank' ) }
654+ >
655+ < ExternalLink className = "size-4" />
656+ </ Button >
617657 </ div >
618658
619659 { isCloud && (
@@ -650,6 +690,16 @@ export const PublishBaseDialog = (props: IPublishBaseDialogProps) => {
650690 </ div >
651691 </ DialogContent >
652692 </ Dialog >
693+
694+ < UnpublishedAppsDialog
695+ open = { unpublishedAppsDialogOpen }
696+ onOpenChange = { setUnpublishedAppsDialogOpen }
697+ unpublishedApps = { unpublishedApps }
698+ treeItems = { treeItems }
699+ onContinue = { handleContinuePublish }
700+ onPublishApp = { appPublishContext . publishApp }
701+ externalApps = { externalApps }
702+ />
653703 </ >
654704 ) ;
655705} ;
0 commit comments