diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx index 7a4394790..e57e99b67 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx @@ -13,24 +13,41 @@ export default function AppDetailsPageClient({ appId }: { appId: AppId }) { const adminApp = useAdminApp()!; const project = adminApp.useProject(); + const config = project.useConfig(); + + const isEnabled = config.apps.installed[appId]?.enabled ?? false; + + const appFrontend = ALL_APPS_FRONTEND[appId]; + if (!(appFrontend as any)) { + throw new StackAssertionError(`App frontend not found for appId: ${appId}`, { appId }); + } + const appPath = getAppPath(project.id, appFrontend); const handleEnable = async () => { await project.updateConfig({ [`apps.installed.${appId}.enabled`]: true, }); - const appFrontend = ALL_APPS_FRONTEND[appId]; - if (!(appFrontend as any)) { - throw new StackAssertionError(`App frontend not found for appId: ${appId}`, { appId }); - } - const path = getAppPath(project.id, appFrontend); - router.push(path); + router.push(appPath); + }; + + const handleOpen = () => { + router.push(appPath); + }; + + const handleDisable = async () => { + await project.updateConfig({ + [`apps.installed.${appId}.enabled`]: false, + }); }; return ( runAsynchronouslyWithAlert(handleEnable())} + onOpen={handleOpen} + onDisable={async () => runAsynchronouslyWithAlert(handleDisable())} /> ); diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx index a57c1f83a..de66744d6 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx @@ -188,7 +188,7 @@ export default function PageClient() { {/* Apps Grid */} {filteredApps.length > 0 ? ( -
+
{filteredApps.map(appId => ( void, }) { const app = ALL_APPS[appId]; - const appFrontend = ALL_APPS_FRONTEND[appId]; - const [isHovered, setIsHovered] = useState(false); - const [isProcessing, setIsProcessing] = useState(false); const [showWarningModal, setShowWarningModal] = useState(false); const projectId = useProjectId(); @@ -62,12 +59,7 @@ export function AppSquare({ const isEnabled = config.apps.installed[appId]?.enabled ?? false; const appDetailsPath = `/projects/${projectId}/apps/${appId}`; - const handleToggleEnabled = async (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - - if (isProcessing) return; - + const handleToggleEnabled = async () => { // Show warning modal for alpha/beta apps when enabling if (!isEnabled && app.stage !== "stable") { setShowWarningModal(true); @@ -79,46 +71,38 @@ export function AppSquare({ }; const performToggle = async () => { - setIsProcessing(true); - try { - await project.updateConfig({ - [`apps.installed.${appId}.enabled`]: !isEnabled, - }); - onToggleEnabled?.(!isEnabled); - } finally { - setIsProcessing(false); - } + await project.updateConfig({ + [`apps.installed.${appId}.enabled`]: !isEnabled, + }); + onToggleEnabled?.(!isEnabled); }; return ( <> -
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > +
- {/* Icon container - fixed height portion */} -
+ {/* Icon container */} +
- {/* Text container - fixed height portion */} -
+ {/* Text container */} +
- {/* Hover actions */} -
- + {/* Three-dot menu in top-right corner */} +
+ + + + + + + {isEnabled ? 'Disable' : 'Enable'} + + +
- {/* Status badges in top-right corner */} + {/* Status badge - enabled checkmark */} {isEnabled && ( -
+
)} + {/* Status badge - alpha/beta */} {!isEnabled && app.stage !== "stable" && ( -
+
Promise, + onOpen?: () => void, + onDisable?: () => Promise, titleComponent?: FunctionComponent | string, }) { const app = ALL_APPS[appId]; @@ -69,9 +75,10 @@ export function AppStoreEntry({ ]; return ( -
- {/* Hero Section */} -
+ +
+ {/* Hero Section */} +
{/* App Icon */} @@ -136,17 +143,35 @@ export function AppStoreEntry({ {/* CTA Button */}
- -
- - No additional cost -
+ {isEnabled ? ( + <> + + {onDisable && ( + + )} + + ) : ( + + )}
@@ -239,8 +264,7 @@ export function AppStoreEntry({
)} - {/* Description Section */} - + {/* Description Section */}

About This App @@ -249,7 +273,6 @@ export function AppStoreEntry({ {appFrontend.storeDescription}

-
{/* Screenshot Preview Modal */} !open && setPreviewIndex(null)}> @@ -313,6 +336,7 @@ export function AppStoreEntry({
-
+
+ ); } diff --git a/apps/dashboard/src/components/cmdk-commands.tsx b/apps/dashboard/src/components/cmdk-commands.tsx index de33af940..2c7243986 100644 --- a/apps/dashboard/src/components/cmdk-commands.tsx +++ b/apps/dashboard/src/components/cmdk-commands.tsx @@ -234,14 +234,12 @@ const AvailableAppPreview = memo(function AvailableAppPreview({ )} {/* Description */} - {appFrontend.storeDescription && ( -
-

About

-
- {appFrontend.storeDescription} -
+
+

About

+
+ {appFrontend.storeDescription}
- )} +
diff --git a/apps/dashboard/src/lib/apps-frontend.tsx b/apps/dashboard/src/lib/apps-frontend.tsx index d1f6074ce..7a1e74418 100644 --- a/apps/dashboard/src/lib/apps-frontend.tsx +++ b/apps/dashboard/src/lib/apps-frontend.tsx @@ -37,7 +37,7 @@ export type AppFrontend = { getBreadcrumbItems?: (stackAdminApp: StackAdminApp, relativePart: string) => Promise, navigationItems: AppNavigationItem[], screenshots: (string | StaticImageData)[], - storeDescription: React.ReactNode, + storeDescription: JSX.Element, }; export function getAppPath(projectId: string, appFrontend: AppFrontend) {