diff --git a/kinode/packages/app_store/ui/src/constants/path.ts b/kinode/packages/app_store/ui/src/constants/path.ts
index 0f422617..08f6c817 100644
--- a/kinode/packages/app_store/ui/src/constants/path.ts
+++ b/kinode/packages/app_store/ui/src/constants/path.ts
@@ -2,4 +2,4 @@ export const STORE_PATH = '/';
export const PUBLISH_PATH = '/publish';
export const APP_DETAILS_PATH = '/app';
export const DOWNLOAD_PATH = '/download';
-export const MY_DOWNLOADS_PATH = '/my-downloads';
+export const MY_APPS_PATH = '/my-apps';
diff --git a/kinode/packages/app_store/ui/src/pages/MyDownloadsPage.tsx b/kinode/packages/app_store/ui/src/pages/MyAppsPage.tsx
similarity index 61%
rename from kinode/packages/app_store/ui/src/pages/MyDownloadsPage.tsx
rename to kinode/packages/app_store/ui/src/pages/MyAppsPage.tsx
index edafaf4c..fa2af6f7 100644
--- a/kinode/packages/app_store/ui/src/pages/MyDownloadsPage.tsx
+++ b/kinode/packages/app_store/ui/src/pages/MyAppsPage.tsx
@@ -3,15 +3,41 @@ import { FaFolder, FaFile, FaChevronLeft, FaSync, FaRocket, FaSpinner, FaCheck,
import useAppsStore from "../store";
import { DownloadItem, PackageManifest, PackageState } from "../types/Apps";
-export default function MyDownloadsPage() {
- const { fetchDownloads, fetchDownloadsForApp, startMirroring, stopMirroring, installApp, removeDownload, fetchInstalled, installed } = useAppsStore();
+// Core packages that cannot be uninstalled
+const CORE_PACKAGES = [
+ "app_store:sys",
+ "contacts:sys",
+ "kino_updates:sys",
+ "terminal:sys",
+ "chess:sys",
+ "kns_indexer:sys",
+ "settings:sys",
+ "homepage:sys"
+];
+
+export default function MyAppsPage() {
+ const {
+ fetchDownloads,
+ fetchDownloadsForApp,
+ startMirroring,
+ stopMirroring,
+ installApp,
+ removeDownload,
+ fetchInstalled,
+ installed,
+ uninstallApp
+ } = useAppsStore();
+
const [currentPath, setCurrentPath] = useState
([]);
const [items, setItems] = useState([]);
const [isInstalling, setIsInstalling] = useState(false);
+ const [isUninstalling, setIsUninstalling] = useState(false);
const [error, setError] = useState(null);
const [showCapApproval, setShowCapApproval] = useState(false);
const [manifest, setManifest] = useState(null);
const [selectedItem, setSelectedItem] = useState(null);
+ const [showUninstallConfirm, setShowUninstallConfirm] = useState(false);
+ const [appToUninstall, setAppToUninstall] = useState(null);
useEffect(() => {
loadItems();
@@ -33,6 +59,35 @@ export default function MyDownloadsPage() {
}
};
+ const initiateUninstall = (app: any) => {
+ const packageId = `${app.package_id.package_name}:${app.package_id.publisher_node}`;
+ if (CORE_PACKAGES.includes(packageId)) {
+ setError("Cannot uninstall core system packages");
+ return;
+ }
+ setAppToUninstall(app);
+ setShowUninstallConfirm(true);
+ };
+
+ const handleUninstall = async () => {
+ if (!appToUninstall) return;
+ setIsUninstalling(true);
+ const packageId = `${appToUninstall.package_id.package_name}:${appToUninstall.package_id.publisher_node}`;
+ try {
+ await uninstallApp(packageId);
+ await fetchInstalled();
+ await loadItems();
+ setShowUninstallConfirm(false);
+ setAppToUninstall(null);
+ } catch (error) {
+ console.error('Uninstallation failed:', error);
+ setError(`Uninstallation failed: ${error instanceof Error ? error.message : String(error)}`);
+ } finally {
+ setIsUninstalling(false);
+ }
+ };
+
+
const navigateToItem = (item: DownloadItem) => {
if (item.Dir) {
setCurrentPath([...currentPath, item.Dir.name]);
@@ -85,7 +140,6 @@ export default function MyDownloadsPage() {
if (!versionHash) throw new Error('Invalid file name format');
- // Construct packageId by combining currentPath and remaining parts of the filename
const packageId = [...currentPath, ...parts].join(':');
await installApp(packageId, versionHash);
@@ -121,8 +175,48 @@ export default function MyDownloadsPage() {
return (
-
Downloads
+
My Apps
+
+ {/* Installed Apps Section */}
+
Installed Apps
+
+
+
+ Package ID
+ Actions
+
+
+
+ {Object.values(installed).map((app) => {
+ const packageId = `${app.package_id.package_name}:${app.package_id.publisher_node}`;
+ const isCore = CORE_PACKAGES.includes(packageId);
+ return (
+
+ {packageId}
+
+ {isCore ? (
+ Core Package
+ ) : (
+ initiateUninstall(app)}
+ disabled={isUninstalling}
+ >
+ {isUninstalling ? : }
+ Uninstall
+
+ )}
+
+
+ );
+ })}
+
+
+
+
+ {/* Downloads Section */}
+
+
Downloads
{currentPath.length > 0 && (
@@ -172,7 +266,8 @@ export default function MyDownloadsPage() {
)}
{isFile && isInstalled && (
- )}
+ )}
+
);
})}
@@ -186,6 +281,45 @@ export default function MyDownloadsPage() {
)}
+ {/* Uninstall Confirmation Modal */}
+ {showUninstallConfirm && appToUninstall && (
+
+
+
Confirm Uninstall
+
+ Are you sure you want to uninstall this app?
+
+
+ Package ID: {`${appToUninstall.package_id.package_name}:${appToUninstall.package_id.publisher_node}`}
+
+ {appToUninstall.metadata?.name && (
+
+ Name: {appToUninstall.metadata.name}
+
+ )}
+
+ {
+ setShowUninstallConfirm(false);
+ setAppToUninstall(null);
+ }}
+ >
+ Cancel
+
+
+ {isUninstalling ? : 'Confirm Uninstall'}
+
+
+
+
+ )}
+
+
+
{showCapApproval && manifest && (
diff --git a/kinode/packages/app_store/ui/vite.config.ts b/kinode/packages/app_store/ui/vite.config.ts
index 625a5e05..8a556bed 100644
--- a/kinode/packages/app_store/ui/vite.config.ts
+++ b/kinode/packages/app_store/ui/vite.config.ts
@@ -17,7 +17,7 @@ The format is "/" + "process_name:package_name:publisher_node"
const BASE_URL = `/main:app_store:sys`;
// This is the proxy URL, it must match the node you are developing against
-const PROXY_URL = (process.env.VITE_NODE_URL || 'http://127.0.0.1:8080').replace('localhost', '127.0.0.1');
+const PROXY_URL = (process.env.VITE_NODE_URL || 'http://127.0.0.1:8080');
console.log('process.env.VITE_NODE_URL', process.env.VITE_NODE_URL, PROXY_URL);