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 */}
-
+
+
);
}
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) {