diff --git a/kinode/packages/app_store/app_store/src/http_api.rs b/kinode/packages/app_store/app_store/src/http_api.rs index c214e16f..7d91e9e0 100644 --- a/kinode/packages/app_store/app_store/src/http_api.rs +++ b/kinode/packages/app_store/app_store/src/http_api.rs @@ -30,7 +30,7 @@ pub fn init_frontend(our: &Address) { "ui", true, false, - vec!["/", "/my-apps", "/apps/:id", "/publish"], + vec!["/", "/my-apps", "/app/:id", "/publish"], ) .expect("failed to serve static UI"); diff --git a/kinode/packages/app_store/ui/src/constants/path.ts b/kinode/packages/app_store/ui/src/constants/path.ts index 52167a93..de3c86e5 100644 --- a/kinode/packages/app_store/ui/src/constants/path.ts +++ b/kinode/packages/app_store/ui/src/constants/path.ts @@ -1,4 +1,4 @@ export const MY_APPS_PATH = '/my-apps'; export const STORE_PATH = '/'; export const PUBLISH_PATH = '/publish'; -export const APP_DETAILS_PATH = '/apps'; +export const APP_DETAILS_PATH = '/app'; diff --git a/kinode/packages/app_store/ui/src/index.css b/kinode/packages/app_store/ui/src/index.css index ba3b2b2e..d2772092 100644 --- a/kinode/packages/app_store/ui/src/index.css +++ b/kinode/packages/app_store/ui/src/index.css @@ -437,4 +437,74 @@ .status.not-installed { background-color: var(--gray); color: var(--white); +} + +.app-info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + margin-bottom: 1rem; +} + +.app-info-item { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.app-info-item>span:first-child { + font-weight: bold; + margin-bottom: 0.5rem; +} + +.app-info-item a { + color: var(--blue); + text-decoration: none; +} + +.app-info-item a:hover { + text-decoration: underline; +} + +.mirrors-dropdown { + position: relative; +} + +.mirrors-dropdown-toggle { + background: none; + border: none; + cursor: pointer; + display: flex; + align-items: center; + color: var(--off-black); +} + +.mirrors-list { + position: absolute; + top: 100%; + left: 0; + background-color: white; + border: 1px solid var(--gray-light); + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + list-style-type: none; + padding: 0; + margin: 0; + z-index: 10; + max-height: 200px; + overflow-y: auto; +} + +.mirrors-list li { + padding: 8px 12px; + border-bottom: 1px solid var(--gray-light); +} + +.mirrors-list li:last-child { + border-bottom: none; +} + +.mirrors-list li:hover { + background-color: var(--gray-lighter); } \ No newline at end of file diff --git a/kinode/packages/app_store/ui/src/pages/AppPage.tsx b/kinode/packages/app_store/ui/src/pages/AppPage.tsx index 7e4805bc..18652fa0 100644 --- a/kinode/packages/app_store/ui/src/pages/AppPage.tsx +++ b/kinode/packages/app_store/ui/src/pages/AppPage.tsx @@ -1,17 +1,17 @@ import React, { useState, useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { FaGlobe, FaPeopleGroup, FaCode } from "react-icons/fa6"; +import { FaGlobe, FaPeopleGroup, FaCode, FaLink, FaCaretDown } from "react-icons/fa6"; import { AppInfo } from "../types/Apps"; import useAppsStore from "../store"; import { PUBLISH_PATH } from "../constants/path"; +import { FaCheckCircle, FaTimesCircle } from "react-icons/fa"; export default function AppPage() { const { getApp, installApp, updateApp, uninstallApp, setMirroring, setAutoUpdate } = useAppsStore(); const navigate = useNavigate(); const { id } = useParams(); const [app, setApp] = useState(undefined); - const [launchPath, setLaunchPath] = useState(''); useEffect(() => { if (id) { @@ -19,18 +19,6 @@ export default function AppPage() { } }, [id, getApp]); - useEffect(() => { - fetch('/apps').then(data => data.json()) - .then((data: Array<{ package_name: string, path: string }>) => { - if (Array.isArray(data)) { - const homepageAppData = data.find(otherApp => app?.package === otherApp.package_name) - if (homepageAppData) { - setLaunchPath(homepageAppData.path) - } - } - }) - }, [app]) - const handleInstall = () => app && installApp(app); const handleUpdate = () => app && updateApp(app); const handleUninstall = () => app && uninstallApp(app); @@ -45,7 +33,9 @@ export default function AppPage() { const version = app.metadata?.properties?.current_version || "Unknown"; const versions = Object.entries(app.metadata?.properties?.code_hashes || {}); const hash = app.state?.our_version || (versions[(versions.length || 1) - 1] || ["", ""])[1]; - const mirrors = app.metadata?.properties?.mirrors?.length || 0; + const mirrors = app.metadata?.properties?.mirrors || []; + + const tbaLink = `https://optimistic.etherscan.io/address/${app.tba}`; return (
@@ -68,8 +58,44 @@ export default function AppPage() {
Mirrors - {mirrors} + {mirrors.length} +
+
+ + TBA + + {`${app.package}.${app.publisher}`} + +
+
+ Installed + {app.installed ? : } +
+ {app.state && ( + <> +
+ Mirrored From + {app.state.mirrored_from || "N/A"} +
+
+ Capabilities Approved + {app.state.caps_approved ? : } +
+
+ Mirroring + {app.state.mirroring ? : } +
+
+ Auto Update + {app.state.auto_update ? : } +
+
+ Verified + {app.state.verified ? : } +
+ + )}
{app.metadata?.properties?.screenshots && (
@@ -96,11 +122,27 @@ export default function AppPage() { ) : ( )} - {launchPath && ( - Launch - )}
); -} \ No newline at end of file +} + +const MirrorsDropdown = ({ mirrors }: { mirrors: string[] }) => { + const [isOpen, setIsOpen] = useState(false); + + return ( +
+ + {isOpen && ( + + )} +
+ ); +}; \ No newline at end of file diff --git a/kinode/packages/app_store/ui/src/pages/MyAppsPage.tsx b/kinode/packages/app_store/ui/src/pages/MyAppsPage.tsx index cd7152c2..30249412 100644 --- a/kinode/packages/app_store/ui/src/pages/MyAppsPage.tsx +++ b/kinode/packages/app_store/ui/src/pages/MyAppsPage.tsx @@ -5,6 +5,7 @@ import { useNavigate, Link } from "react-router-dom"; import { AppInfo } from "../types/Apps"; import useAppsStore from "../store"; import { PUBLISH_PATH } from "../constants/path"; +import { appId } from "../utils/app"; export default function MyAppsPage() { const { apps, getApps } = useAppsStore(); @@ -67,7 +68,7 @@ interface AppEntryProps { const AppEntry: React.FC = ({ app }) => { return ( - +

{app.metadata?.name || app.package}

{app.metadata?.description || "No description available"}

diff --git a/kinode/packages/app_store/ui/src/pages/StorePage.tsx b/kinode/packages/app_store/ui/src/pages/StorePage.tsx index 020518c4..24a227d4 100644 --- a/kinode/packages/app_store/ui/src/pages/StorePage.tsx +++ b/kinode/packages/app_store/ui/src/pages/StorePage.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import useAppsStore from "../store"; import { AppInfo } from "../types/Apps"; +import { appId } from '../utils/app' import { Link } from "react-router-dom"; import { FaGlobe, FaPeopleGroup, FaCode } from "react-icons/fa6"; @@ -61,7 +62,7 @@ export default function StorePage() { {filteredApps.map((app) => ( - + ))} @@ -81,7 +82,7 @@ const AppRow: React.FC = ({ app }) => { return ( - + {app.metadata?.name || app.package} diff --git a/kinode/packages/app_store/ui/src/types/Apps.ts b/kinode/packages/app_store/ui/src/types/Apps.ts index e9fc80d5..7ccceb24 100644 --- a/kinode/packages/app_store/ui/src/types/Apps.ts +++ b/kinode/packages/app_store/ui/src/types/Apps.ts @@ -6,7 +6,7 @@ export interface MyApps { } export interface AppListing { - owner?: string + tba: string package: string publisher: string metadata_hash: string diff --git a/kinode/packages/app_store/ui/vite.config.ts b/kinode/packages/app_store/ui/vite.config.ts index f0a0f5e9..625a5e05 100644 --- a/kinode/packages/app_store/ui/vite.config.ts +++ b/kinode/packages/app_store/ui/vite.config.ts @@ -39,30 +39,28 @@ export default defineConfig({ server: { open: true, proxy: { - '^/our\\.js': { + [`^${BASE_URL}/our.js`]: { target: PROXY_URL, changeOrigin: true, rewrite: (path) => { - console.log('Rewriting path for our.js:', path); + console.log('Proxying jsrequest:', path); return '/our.js'; }, }, - '^/kinode\\.css': { + [`^${BASE_URL}/kinode.css`]: { target: PROXY_URL, changeOrigin: true, rewrite: (path) => { - console.log('Rewriting path for kinode.css:', path); + console.log('Proxying csrequest:', path); return '/kinode.css'; }, }, + // This route will match all other HTTP requests to the backend [`^${BASE_URL}/(?!(@vite/client|src/.*|node_modules/.*|@react-refresh|$))`]: { target: PROXY_URL, changeOrigin: true, - rewrite: (path) => { - console.log('Rewriting path for other requests:', path); - return path.replace(BASE_URL, ''); - }, }, + },