mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-12-23 08:32:23 +03:00
app_store UI: details page
This commit is contained in:
parent
a3168c1274
commit
340d8d1436
@ -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");
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
}
|
@ -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<AppInfo | undefined>(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 (
|
||||
<div className="app-page">
|
||||
@ -68,8 +58,44 @@ export default function AppPage() {
|
||||
<div className="app-info-item">
|
||||
<FaGlobe size={36} />
|
||||
<span>Mirrors</span>
|
||||
<span>{mirrors}</span>
|
||||
<span>{mirrors.length}</span>
|
||||
<MirrorsDropdown mirrors={mirrors} />
|
||||
</div>
|
||||
<div className="app-info-item">
|
||||
<FaLink size={36} />
|
||||
<span>TBA</span>
|
||||
<a href={tbaLink} target="_blank" rel="noopener noreferrer">
|
||||
{`${app.package}.${app.publisher}`}
|
||||
</a>
|
||||
</div>
|
||||
<div className="app-info-item">
|
||||
<span>Installed</span>
|
||||
{app.installed ? <FaCheckCircle color="green" /> : <FaTimesCircle color="red" />}
|
||||
</div>
|
||||
{app.state && (
|
||||
<>
|
||||
<div className="app-info-item">
|
||||
<span>Mirrored From</span>
|
||||
<span>{app.state.mirrored_from || "N/A"}</span>
|
||||
</div>
|
||||
<div className="app-info-item">
|
||||
<span>Capabilities Approved</span>
|
||||
{app.state.caps_approved ? <FaCheckCircle color="green" /> : <FaTimesCircle color="red" />}
|
||||
</div>
|
||||
<div className="app-info-item">
|
||||
<span>Mirroring</span>
|
||||
{app.state.mirroring ? <FaCheckCircle color="green" /> : <FaTimesCircle color="red" />}
|
||||
</div>
|
||||
<div className="app-info-item">
|
||||
<span>Auto Update</span>
|
||||
{app.state.auto_update ? <FaCheckCircle color="green" /> : <FaTimesCircle color="red" />}
|
||||
</div>
|
||||
<div className="app-info-item">
|
||||
<span>Verified</span>
|
||||
{app.state.verified ? <FaCheckCircle color="green" /> : <FaTimesCircle color="red" />}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{app.metadata?.properties?.screenshots && (
|
||||
<div className="app-screenshots">
|
||||
@ -96,11 +122,27 @@ export default function AppPage() {
|
||||
) : (
|
||||
<button onClick={handleInstall}>Install</button>
|
||||
)}
|
||||
{launchPath && (
|
||||
<a href={launchPath} className="button">Launch</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const MirrorsDropdown = ({ mirrors }: { mirrors: string[] }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="mirrors-dropdown">
|
||||
<button onClick={() => setIsOpen(!isOpen)} className="mirrors-dropdown-toggle">
|
||||
Mirrors <FaCaretDown />
|
||||
</button>
|
||||
{isOpen && (
|
||||
<ul className="mirrors-list">
|
||||
{mirrors.map((mirror, index) => (
|
||||
<li key={index}>{mirror}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -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<AppEntryProps> = ({ app }) => {
|
||||
return (
|
||||
<Link to={`/apps/${app.package}`} className="app-entry">
|
||||
<Link to={`/app/${appId(app)}`} className="app-entry">
|
||||
<h3>{app.metadata?.name || app.package}</h3>
|
||||
<p>{app.metadata?.description || "No description available"}</p>
|
||||
</Link>
|
||||
|
@ -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() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredApps.map((app) => (
|
||||
<AppRow key={app.package} app={app} />
|
||||
<AppRow key={appId(app)} app={app} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -81,7 +82,7 @@ const AppRow: React.FC<AppRowProps> = ({ app }) => {
|
||||
return (
|
||||
<tr className="app-row">
|
||||
<td>
|
||||
<Link to={`/app-details/${app.package}`} className="app-name">
|
||||
<Link to={`/app/${appId(app)}`} className="app-name">
|
||||
{app.metadata?.name || app.package}
|
||||
</Link>
|
||||
</td>
|
||||
|
@ -6,7 +6,7 @@ export interface MyApps {
|
||||
}
|
||||
|
||||
export interface AppListing {
|
||||
owner?: string
|
||||
tba: string
|
||||
package: string
|
||||
publisher: string
|
||||
metadata_hash: string
|
||||
|
@ -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, '');
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user