homepage and appstore touch-up

This commit is contained in:
Tobias Merkle 2024-05-10 15:16:26 -04:00
parent 4682c3d082
commit 02c74caf51
34 changed files with 916 additions and 624 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,8 +14,8 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/main:app_store:sys/assets/index-kpw1YN6W.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-i4SytJ9j.css">
<script type="module" crossorigin src="/main:app_store:sys/assets/index-EW63vnI-.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-USC8YCBq.css">
</head>
<body>

View File

@ -1,5 +1,5 @@
{
"name": "kit-ui",
"name": "kinode-app-store",
"private": true,
"version": "0.0.0",
"type": "module",
@ -54,4 +54,4 @@
"typescript": "^5.2.2",
"vite": "^5.0.8"
}
}
}

View File

@ -110,7 +110,7 @@ function App() {
const props = { provider, packageAbi };
return (
<div className="flex flex-col c h-screen w-screen">
<div className="flex flex-col c h-screen w-screen max-h-screen max-w-screen overflow-x-hidden special-appstore-background">
<Web3ReactProvider connectors={connectors}>
<Router basename={BASE_URL}>
<Routes>

View File

@ -1,27 +1,19 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
import { useNavigate } from "react-router-dom";
import UpdateButton from "./UpdateButton";
import DownloadButton from "./DownloadButton";
import InstallButton from "./InstallButton";
import LaunchButton from "./LaunchButton";
import { FaCheck } from "react-icons/fa6";
interface ActionButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
}
export default function ActionButton({ app, ...props }: ActionButtonProps) {
const { updateApp, downloadApp, installApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [mirror, setMirror] = useState(app.metadata?.properties?.mirrors?.[0] || "Other");
const [customMirror, setCustomMirror] = useState("");
const [caps, setCaps] = useState<string[]>([]);
const [launchPath, setLaunchPath] = useState('');
const [loading, setLoading] = useState("");
const { clean, installed, downloaded, updatable } = useMemo(() => {
export default function ActionButton({ app, isIcon = false, ...props }: ActionButtonProps) {
const [incrementNumber, setIncrementNumber] = useState(0);
const { installed, downloaded, updatable } = useMemo(() => {
const versions = Object.entries(app?.metadata?.properties?.code_hashes || {});
const latestHash = (versions.find(([v]) => v === app.metadata?.properties?.current_version) || [])[1];
@ -31,217 +23,46 @@ export default function ActionButton({ app, ...props }: ActionButtonProps) {
const updatable =
Boolean(app.state?.our_version && latestHash) &&
app.state?.our_version !== latestHash &&
app.publisher !== window.our.node;
app.publisher !== (window as any).our.node;
return {
clean: !installed && !downloaded && !updatable,
installed,
downloaded,
updatable,
};
}, [app]);
}, [app, incrementNumber]);
const [launchPath, setLaunchPath] = useState('');
useEffect(() => {
setMirror(app.metadata?.properties?.mirrors?.[0] || "Other");
}, [app.metadata?.properties?.mirrors]);
useEffect(() => {
if (installed) {
fetch('/apps').then(data => data.json())
.then((data: Array<{ package_name: string, path: string }>) => {
// console.log(data)
if (Array.isArray(data)) {
// console.log('is array')
const homepageAppData = data.find(otherApp => app.package === otherApp.package_name)
if (homepageAppData) {
// console.log('found the good appness', homepageAppData.package_name, homepageAppData.path);
setLaunchPath(homepageAppData.path)
}
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)
}
})
}
}, [installed])
const onClick = useCallback(async () => {
if (installed && !updatable && launchPath) {
window.location.href = `/${launchPath.replace('/', '')}`
return;
} else {
if (downloaded) {
getCaps(app).then((manifest) => {
setCaps(manifest.request_capabilities);
});
}
setShowModal(true);
}
}, [app, installed, downloaded, updatable, setShowModal, getCaps, launchPath]);
const download = useCallback(async (e: FormEvent) => {
e.preventDefault();
e.stopPropagation();
const targetMirror = mirror === "Other" ? customMirror : mirror;
if (!targetMirror) {
window.alert("Please select a mirror");
return;
}
try {
setLoading(`Downloading ${getAppName(app)}...`);
await downloadApp(app, targetMirror);
const interval = setInterval(() => {
getMyApp(app)
.then(() => {
setLoading("");
setShowModal(false);
clearInterval(interval);
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(
`Failed to download app from ${targetMirror}, please try a different mirror.`
);
setLoading("");
}
}, [mirror, customMirror, app, downloadApp, getMyApp]);
const install = useCallback(async () => {
try {
setLoading(`Installing ${getAppName(app)}...`);
await installApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to install, please try again.`);
setLoading("");
}
}, [app, installApp, getMyApp]);
const update = useCallback(async () => {
try {
setLoading(`Updating ${getAppName(app)}...`);
await updateApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to update, please try again.`);
setLoading("");
}
}, [app, updateApp, getMyApp]);
const appName = getAppName(app);
}
})
}, [app, incrementNumber])
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm min-w-[100px] px-2 py-1 self-start", props.className, {
'bg-orange': installed,
'hidden': installed && !updatable && !launchPath
})}
onClick={onClick}
>
{installed && updatable
? "Update"
: installed && launchPath
? "Launch"
: installed
? "Installed"
: downloaded
? "Install"
: "Download"}
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : clean ? (
<form className="flex flex-col items-center gap-2" onSubmit={download}>
<h4>Download '{appName}'</h4>
<h5 style={{ margin: 0 }}>Select Mirror</h5>
<select value={mirror} onChange={(e) => setMirror(e.target.value)}>
{((app.metadata?.properties?.mirrors || []).concat(["Other"])).map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
{mirror === "Other" && (
<input
type="text"
value={customMirror}
onChange={(e) => setCustomMirror(e.target.value)}
placeholder="Mirror, i.e. 'template.os'"
className="p-1 max-w-[240px] w-full"
required
autoFocus
/>
)}
<button type="submit">
Download
</button>
</form>
) : downloaded ? (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
<button type="button" onClick={install}>
Approve & Install
</button>
</>
) : (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
{/* <h5>Send Messages:</h5> */}
<br />
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
{/* <h5>Receive Messages:</h5>
<ul>
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul> */}
<button type="button" onClick={update}>
Approve & Update
</button>
</>
)}
</Modal>
{(installed && launchPath)
? <LaunchButton app={app} {...props} isIcon={isIcon} launchPath={launchPath} />
: (installed && updatable)
? <UpdateButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
: !downloaded
? <DownloadButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
: !installed
? <InstallButton app={app} {...props} isIcon={isIcon} callback={() => setIncrementNumber(incrementNumber + 1)} />
: isIcon
? <button
className="pointer-events none icon clear absolute top-0 right-0"
>
<FaCheck />
</button>
: <div>Installed</div>}
</>
);
}

View File

@ -4,25 +4,39 @@ import AppHeader from "./AppHeader";
import ActionButton from "./ActionButton";
import { AppInfo } from "../types/Apps";
import { appId } from "../utils/app";
import MoreActions from "./MoreActions";
import { isMobileCheck } from "../utils/dimensions";
import classNames from "classnames";
import { useNavigate } from "react-router-dom";
import { APP_DETAILS_PATH } from "../constants/path";
interface AppEntryProps extends React.HTMLAttributes<HTMLDivElement> {
app: AppInfo;
size?: "small" | "medium" | "large";
overrideImageSize?: "small" | "medium" | "large";
}
export default function AppEntry({ app, ...props }: AppEntryProps) {
export default function AppEntry({ app, size = "medium", overrideImageSize, ...props }: AppEntryProps) {
const isMobile = isMobileCheck()
const navigate = useNavigate()
return (
<div {...props} key={appId(app)} className={classNames("flex justify-between w-full rounded hover:bg-white/10 card", {
'flex-wrap gap-2': isMobile
})}>
<AppHeader app={app} size="small" />
<div className="flex mr-1 items-start">
<ActionButton app={app} className="mr-2" />
<MoreActions app={app} />
</div>
<div
{...props}
key={appId(app)}
className={classNames("flex justify-between rounded-lg hover:bg-white/10 card cursor-pointer", props.className, {
'flex-wrap gap-2': isMobile,
'flex-col relative': size !== 'large'
})}
onClick={() => navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)}
>
<AppHeader app={app} size={size} overrideImageSize={overrideImageSize} />
<ActionButton
app={app}
isIcon={size !== 'large'}
className={classNames({
'absolute top-0 right-0': size !== 'large',
'bg-orange text-lg min-w-1/5': size === 'large'
})} />
</div>
);
}

View File

@ -1,54 +1,83 @@
import React from "react";
import { AppInfo } from "../types/Apps";
import { appId } from "../utils/app";
import { useNavigate } from "react-router-dom";
import classNames from "classnames";
import { APP_DETAILS_PATH } from "../constants/path";
import ColorDot from "./ColorDot";
import { isMobileCheck } from "../utils/dimensions";
interface AppHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
app: AppInfo;
size?: "small" | "medium" | "large";
overrideImageSize?: "small" | "medium" | "large"
}
export default function AppHeader({
app,
size = "medium",
overrideImageSize,
...props
}: AppHeaderProps) {
const navigate = useNavigate()
const isMobile = isMobileCheck()
return (
<div
{...props}
className={classNames('flex w-full justify-content-start', size, props.className, { 'cursor-pointer': size !== 'large' })}
onClick={() => navigate(`/${APP_DETAILS_PATH}/${appId(app)}`)}
>
{app.metadata?.image
? <img
src={app.metadata.image}
alt="app icon"
className={classNames('mr-2', { 'h-32 rounded-md': size === 'large', 'h-12 rounded': size !== 'large' })}
/>
: <ColorDot
num={app.metadata_hash}
dotSize={size}
className={classNames('mr-2')}
/>}
<div className={classNames("flex flex-col", { 'gap-2 max-w-3/4': isMobile })}>
const appName = <div
className={classNames({
'text-3xl font-[OpenSans]': size === 'large',
'text-xl': size !== 'large'
})}
>
{app.metadata?.name || appId(app)}
</div>
const imageSize = overrideImageSize || size
return <div
{...props}
className={classNames('flex w-full justify-content-start', size, props.className, {
'flex-col': size === 'small',
'gap-2': isMobile,
'gap-4': !isMobile,
'gap-6': !isMobile && size === 'large'
})}
>
{size === 'small' && appName}
{app.metadata?.image
? <img
src={app.metadata.image}
alt="app icon"
className={classNames('object-cover', {
'rounded': !imageSize,
'rounded-lg': imageSize === 'small',
'rounded-xl': imageSize === 'medium',
'rounded-2xl': imageSize === 'large',
'h-32': imageSize === 'large' || imageSize === 'small',
'h-20': imageSize === 'medium',
})}
/>
: <ColorDot
num={app.metadata_hash}
dotSize={imageSize}
/>}
<div className={classNames("flex flex-col", {
'gap-2': isMobile,
'gap-4 max-w-3/4': isMobile && size !== 'small'
})}>
{size !== 'small' && appName}
{app.metadata?.description && (
<div
className={classNames("whitespace-nowrap overflow-hidden text-ellipsis", { 'text-3xl': size === 'large', })}
style={{
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
className={classNames({
'text-2xl': size === 'large'
})}
>
{app.metadata?.name || appId(app)}
{app.metadata.description}
</div>
{app.metadata?.description && size !== "large" && (
<div className="whitespace-nowrap overflow-hidden text-ellipsis">
{app.metadata?.description?.slice(0, 100)}
</div>
)}
</div>
)}
</div>
);
</div>
}

View File

@ -33,8 +33,8 @@ const ColorDot: React.FC<ColorDotProps> = ({
<div {...props} className={classNames('flex', props.className)}>
<div
className={classNames('m-0 align-self-center border rounded-full outline-black', {
'h-20 w-20': !isMobile && dotSize === 'large',
'h-16 w-16': !isMobile && dotSize === 'medium',
'h-32 w-32': !isMobile && dotSize === 'large',
'h-18 w-18': !isMobile && dotSize === 'medium',
'h-12 w-12': isMobile || dotSize === 'small',
'border-4': !isMobile,
'border-2': isMobile,

View File

@ -0,0 +1,111 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
import { useNavigate } from "react-router-dom";
import { FaDownload } from "react-icons/fa6";
interface DownloadButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void;
}
export default function DownloadButton({ app, isIcon = false, callback, ...props }: DownloadButtonProps) {
const { downloadApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [mirror, setMirror] = useState(app.metadata?.properties?.mirrors?.[0] || "Other");
const [customMirror, setCustomMirror] = useState("");
const [loading, setLoading] = useState("");
useEffect(() => {
setMirror(app.metadata?.properties?.mirrors?.[0] || "Other");
}, [app.metadata?.properties?.mirrors]);
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setShowModal(true);
}, [app, setShowModal, getCaps]);
const download = useCallback(async (e: FormEvent) => {
e.preventDefault();
e.stopPropagation();
const targetMirror = mirror === "Other" ? customMirror : mirror;
if (!targetMirror) {
window.alert("Please select a mirror");
return;
}
try {
setLoading(`Downloading ${getAppName(app)}...`);
await downloadApp(app, targetMirror);
const interval = setInterval(() => {
getMyApp(app)
.then(() => {
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(
`Failed to download app from ${targetMirror}, please try a different mirror.`
);
setLoading("");
}
}, [mirror, customMirror, app, downloadApp, getMyApp]);
const appName = getAppName(app);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className, { 'icon clear': isIcon })}
onClick={onClick}
>
{isIcon ? <FaDownload /> : 'Download'}
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : (
<form className="flex flex-col items-center gap-2" onSubmit={download}>
<h4>Download '{appName}'</h4>
<h5>Select Mirror</h5>
<select value={mirror} onChange={(e) => setMirror(e.target.value)}>
{((app.metadata?.properties?.mirrors || []).concat(["Other"])).map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
{mirror === "Other" && (
<input
type="text"
value={customMirror}
onChange={(e) => setCustomMirror(e.target.value)}
placeholder="Mirror, i.e. 'template.os'"
className="p-1 max-w-[240px] w-full"
required
autoFocus
/>
)}
<button type="submit">
Download
</button>
</form>
)}
</Modal>
</>
);
}

View File

@ -0,0 +1,19 @@
import classNames from "classnames";
import React from "react"
import { FaHome } from "react-icons/fa"
import { isMobileCheck } from "../utils/dimensions";
const HomeButton: React.FC = () => {
const isMobile = isMobileCheck()
return <button
className={classNames("clear absolute p-2", {
'top-2 left-2': isMobile,
'top-8 left-8': !isMobile
})}
onClick={() => window.location.href = '/'}
>
<FaHome size={24} />
</button>
}
export default HomeButton;

View File

@ -0,0 +1,88 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
import { FaI } from "react-icons/fa6";
interface InstallButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void;
}
export default function InstallButton({ app, isIcon = false, callback, ...props }: InstallButtonProps) {
const { installApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [caps, setCaps] = useState<string[]>([]);
const [loading, setLoading] = useState("");
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
getCaps(app).then((manifest) => {
setCaps(manifest.request_capabilities);
});
setShowModal(true);
}, [app, setShowModal, getCaps]);
const install = useCallback(async () => {
try {
setLoading(`Installing ${getAppName(app)}...`);
await installApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to install, please try again.`);
setLoading("");
}
}, [app, installApp, getMyApp]);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className, {
'icon clear': isIcon
})}
onClick={onClick}
>
{isIcon ? <FaI /> : "Install"}
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
<button type="button" onClick={install}>
Approve & Install
</button>
</>
)}
</Modal>
</>
);
}

View File

@ -0,0 +1,33 @@
import React, { useCallback, useEffect, useState } from "react";
import { AppInfo } from "../types/Apps";
import classNames from "classnames";
import { FaPlay } from "react-icons/fa6";
interface LaunchButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
launchPath: string;
isIcon?: boolean;
}
export default function LaunchButton({ app, launchPath, isIcon = false, ...props }: LaunchButtonProps) {
const onLaunch = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
window.location.href = `/${launchPath.replace('/', '')}`
return;
}, [app, launchPath]);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className, {
'icon clear': isIcon
})}
onClick={onLaunch}
>
{isIcon ? <FaPlay /> : "Launch"}
</button>
</>
);
}

View File

@ -27,7 +27,7 @@ const Modal: React.FC<ModalProps> = ({
return (
<div
className={classNames(`bg-black/25 fixed top-0 bottom-0 left-0 right-0 flex flex-col c z-30 min-h-[10em] min-w-[30em]`,
className={classNames(`bg-black/25 backdrop-blur-lg fixed top-0 bottom-0 left-0 right-0 flex flex-col c z-30 min-h-[10em] min-w-[30em]`,
{ show }
)}
onClick={hide}

View File

@ -5,12 +5,11 @@ import {
FaDownload,
FaMagnifyingGlass,
FaUpload,
FaX,
} from "react-icons/fa6";
import { MY_APPS_PATH, PUBLISH_PATH } from "../constants/path";
import classNames from "classnames";
import { FaHome } from "react-icons/fa";
import { isMobileCheck } from "../utils/dimensions";
interface SearchHeaderProps {
value?: string;
@ -34,67 +33,59 @@ export default function SearchHeader({
const canGoBack = location.key !== "default";
const isMyAppsPage = location.pathname === MY_APPS_PATH;
const isMobile = isMobileCheck()
return (
<div className="flex justify-between">
{location.pathname !== '/' ? (
<button className="flex flex-col c mr-2 icon" onClick={() => {
<div className={classNames("flex justify-between", {
"gap-4": isMobile,
"gap-8": !isMobile
})}>
{location.pathname !== '/' && <button
className="flex flex-col c icon icon-orange"
onClick={() => {
if (onBack) {
onBack()
} else {
canGoBack ? navigate(-1) : navigate('/')
}
}}>
<FaArrowLeft />
</button>
) : (
<button
className="flex flex-col c mr-2 icon"
onClick={() => window.location.href = '/'}
>
<FaHome />
</button>
)}
<FaArrowLeft />
</button>}
{!hidePublish && <button
className="flex flex-col c mr-2 icon"
className="flex flex-col c icon icon-orange"
onClick={() => navigate(PUBLISH_PATH)}
>
<FaUpload />
</button>}
{!hideSearch && (
<div className="flex mr-2 flex-1 rounded-md">
<button
className="icon mr-2"
type="button"
onClick={() => inputRef.current?.focus()}
>
<FaMagnifyingGlass />
</button>
<div className="flex flex-1 rounded-md relative">
<input
type="text"
ref={inputRef}
onChange={(event) => onChange(event.target.value)}
value={value}
placeholder="Search for apps..."
className="w-full mr-2"
className="w-full self-stretch grow"
/>
{value.length > 0 && <button
className="icon"
onClick={() => onChange("")}
<button
className="icon border-0 absolute right-4 top-1/2 -translate-y-1/2"
type="button"
onClick={() => inputRef.current?.focus()}
>
<FaX />
</button>}
<FaMagnifyingGlass />
</button>
</div>
)}
<div className="flex">
<button
className={classNames("flex alt")}
onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))}
>
<FaDownload className="mr-2" />
<span>My Apps</span>
</button>
</div>
<button
className={classNames("flex c", {
"gap-4": isMobile,
"gap-8 basis-1/5": !isMobile
})}
onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))}
>
<span>My Apps</span>
<FaDownload />
</button>
</div>
);
}

View File

@ -0,0 +1,94 @@
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import { AppInfo } from "../types/Apps";
import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
interface UpdateButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
isIcon?: boolean;
callback?: () => void
}
export default function UpdateButton({ app, callback, isIcon = false, ...props }: UpdateButtonProps) {
const { updateApp, getCaps, getMyApp } =
useAppsStore();
const [showModal, setShowModal] = useState(false);
const [caps, setCaps] = useState<string[]>([]);
const [loading, setLoading] = useState("");
const onClick = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
getCaps(app).then((manifest) => {
setCaps(manifest.request_capabilities);
});
setShowModal(true);
}, [app, setShowModal, getCaps]);
const update = useCallback(async () => {
try {
setLoading(`Updating ${getAppName(app)}...`);
await updateApp(app);
const interval = setInterval(() => {
getMyApp(app)
.then((app) => {
if (!app.installed) return;
setLoading("");
setShowModal(false);
clearInterval(interval);
callback && callback();
})
.catch(console.log);
}, 2000);
} catch (e) {
console.error(e);
window.alert(`Failed to update, please try again.`);
setLoading("");
}
}, [app, updateApp, getMyApp]);
return (
<>
<button
{...props}
type="button"
className={classNames("text-sm self-start", props.className)}
onClick={onClick}
>
Update
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : (
<>
<h4>Approve App Permissions</h4>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
{/* <h5>Send Messages:</h5> */}
<br />
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul>
{/* <h5>Receive Messages:</h5>
<ul>
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
</ul> */}
<button type="button" onClick={update}>
Approve & Update
</button>
</>
)}
</Modal>
</>
);
}

View File

@ -60,7 +60,7 @@ h6 {
button,
button[type="submit"],
.button {
@apply flex m-0 py-2 px-6 rounded border-orange bg-orange border-2 cursor-pointer place-items-center place-content-center text-center rounded-lg heading transition ease-in-out duration-100 hover:bg-black text-white font-[OpenSans];
@apply flex m-0 py-2 px-6 rounded border-orange bg-orange border cursor-pointer place-items-center place-content-center text-center rounded-lg heading transition ease-in-out duration-100 hover:bg-black text-white font-[OpenSans];
}
.clear {
@ -80,7 +80,15 @@ button[type="submit"],
}
.icon.alt {
@apply border-black/25 hover:border-white/25
@apply border-black/25 hover:border-white/25;
}
.icon.icon-orange {
@apply border-orange bg-orange/25;
}
.icon.clear {
@apply border-0;
}
body {
@ -98,7 +106,7 @@ textarea,
input[type="text"],
input[type="password"],
input[type="checkbox"] {
@apply px-4 py-2 rounded-lg bg-orange bg-opacity-25 text-white border border-orange border-2;
@apply px-4 py-2 rounded-lg bg-orange bg-opacity-25 text-white border border-solid border-orange;
}
input[type="checkbox"] {
@ -132,4 +140,9 @@ button:disabled {
.c {
@apply place-items-center place-content-center;
}
.special-appstore-background {
background-color: #22211f;
background-image: radial-gradient(circle at -20% -68%, #f75a2991 36%, transparent 56.05%), linear-gradient(37deg, #86000185 19%, transparent 45.05%), linear-gradient(-36deg, #8600016e 26%, transparent 50.05%);
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo, useCallback } from "react";
import React, { useState, useEffect, useMemo, useCallback, ReactElement } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { AppInfo } from "../types/Apps";
@ -9,6 +9,10 @@ import SearchHeader from "../components/SearchHeader";
import { PageProps } from "../types/Page";
import { appId } from "../utils/app";
import { PUBLISH_PATH } from "../constants/path";
import HomeButton from "../components/HomeButton";
import classNames from "classnames";
import { isMobileCheck } from "../utils/dimensions";
import { FaGlobe, FaPeopleGroup, FaStar } from "react-icons/fa6";
interface AppPageProps extends PageProps { }
@ -48,80 +52,127 @@ export default function AppPage() {
app?.state?.our_version ||
(versions[(versions.length || 1) - 1] || ["", ""])[1];
const isMobile = isMobileCheck()
const appDetails: Array<{ top: ReactElement, middle: ReactElement, bottom: ReactElement }> = [
{
top: <div>0 ratings</div>,
middle: <span className="text-2xl">5.0</span>,
bottom: <div className="flex-center gap-1">
<FaStar />
<FaStar />
<FaStar />
<FaStar />
<FaStar />
</div>
},
{
top: <div>Developer</div>,
middle: <FaPeopleGroup size={36} />,
bottom: <div>
{app?.publisher}
</div>
},
{
top: <div>Version</div>,
middle: <span className="text-2xl">{version}</span>,
bottom: <div>
{hash.slice(0, 5)}...{hash.slice(-5)}
</div>
},
{
top: <div>Mirrors</div>,
middle: <FaGlobe size={36} />,
bottom: <div>
{app?.metadata?.properties?.mirrors?.length || 0}
</div>
}
]
return (
<div className="flex flex-col w-full max-w-[900px]">
<SearchHeader value="" onChange={() => null} hideSearch />
<div className="card mt1">
{app ? (
<>
<div className="flex justify-between">
<AppHeader app={app} size="large" />
<ActionButton app={app} className="mr-1" />
<div className={classNames("flex flex-col w-full max-w-[900px]",
{
'gap-4': isMobile,
'gap-8': !isMobile,
})}
>
<HomeButton />
<SearchHeader
value=""
onChange={() => null}
hideSearch
hidePublish
/>
<div className={classNames("flex-col-center card !rounded-3xl", {
'p-12 gap-4': isMobile,
'p-24 gap-8': !isMobile,
})}>
{app ? <>
<AppHeader app={app} size="large" />
<div className="w-5/6 h-0 border border-orange" />
<div className={classNames("flex items-start text-xl", {
'gap-4': isMobile,
'gap-8': !isMobile,
})}>
{appDetails.map((detail, index) => <>
<div
className="flex-col-center gap-2 justify-between opacity-50 self-stretch"
key={index}
>
{detail.top}
{detail.middle}
{detail.bottom}
</div>
{index !== appDetails.length - 1 && <div className="h-3/4 w-0 border border-orange self-center" />}
</>)}
</div>
{/* <div className="flex flex-col mt-2">
<div className="flex mt-1 items-start">
<div className="w-1/4">Publisher</div>
<div className="mb-1 w-3/4">{app.publisher}</div>
</div>
<div className="flex flex-col mt-2">
<div className="flex mt-1 items-start">
<div className="w-1/4">Description</div>
<div className="mb-1 w-3/4">
{(app.metadata?.description || "No description given").slice(
0,
2000
)}
</div>
</div>
<div className="flex mt-1 items-start">
<div className="w-1/4">Publisher</div>
<div className="mb-1 w-3/4">{app.publisher}</div>
</div>
<div className="flex mt-1 items-start">
<div className="w-1/4">Version</div>
<div className="mb-1 w-3/4">{version}</div>
</div>
<div className="flex mt-1 items-start">
<div className="w-1/4">Mirrors</div>
<div className="w-3/4 flex flex-col">
{(app.metadata?.properties?.mirrors || []).map(
(mirror, index) => (
<div key={index + mirror} className="mb-1">
{mirror}
</div>
)
)}
</div>
</div>
{/* <div className="flex mt-1 items-start">
<div className="w-1/4">Permissions</div>
<div className="w-3/4 flex flex-col">
{app.permissions?.map((permission, index) => (
<div key={index + permission} className="mb-1">{permission}</div>
))}
</div>
</div> */}
<div className="flex mt-1 items-start">
<div className="w-1/4">Hash</div>
<div className="w-3/4 break-all">
{hash}
</div>
<div className="flex mt-1 items-start">
<div className="w-1/4">Version</div>
<div className="mb-1 w-3/4">{version}</div>
</div>
<div className="flex mt-1 items-start">
<div className="w-1/4">Mirrors</div>
<div className="w-3/4 flex flex-col">
{(app.metadata?.properties?.mirrors || []).map(
(mirror, index) => (
<div key={index + mirror} className="mb-1">
{mirror}
</div>
)
)}
</div>
</div>
<div className="app-screenshots flex mt-2 overflow-x-auto max-w-full">
{(app.metadata?.properties?.screenshots || []).map(
<div className="flex mt-1 items-start">
<div className="w-1/4">Hash</div>
<div className="w-3/4 break-all">
{hash}
</div>
</div>
</div> */}
{Array.isArray(app.metadata?.properties?.screenshots)
&& app.metadata?.properties.screenshots.length > 0
&& <div className="flex overflow-x-auto max-w-full">
{app.metadata.properties.screenshots.map(
(screenshot, index) => (
<img key={index + screenshot} src={screenshot} className="mr-2 max-h-20 max-w-full rounded border border-black" />
)
)}
</div>
{app.installed && (
<button type="button" onClick={goToPublish}>
Publish
</button>
)}
</>
) : (
<>
<h4>App details not found for </h4>
<h4>{params.id}</h4>
</>
)}
</div>}
<ActionButton app={app} className={classNames("self-center bg-orange text-lg px-12")} />
{app.installed && app.state?.mirroring && (
<button type="button" onClick={goToPublish}>
Publish
</button>
)}
</> : <>
<h4>App details not found for </h4>
<h4>{params.id}</h4>
</>}
</div>
</div>
);

View File

@ -9,6 +9,7 @@ import { PageProps } from "../types/Page";
import { useNavigate } from "react-router-dom";
import { appId } from "../utils/app";
import { PUBLISH_PATH } from "../constants/path";
import HomeButton from "../components/HomeButton";
export default function MyAppsPage() { // eslint-disable-line
const { myApps, getMyApps } = useAppsStore()
@ -53,6 +54,7 @@ export default function MyAppsPage() { // eslint-disable-line
return (
<div className="flex flex-col w-full max-w-[900px]">
<HomeButton />
<SearchHeader value={searchQuery} onChange={searchMyApps} />
<div className="flex justify-between items-center mt-2">
<h3>My Packages</h3>

View File

@ -16,6 +16,7 @@ import { AppInfo } from "../types/Apps";
import Checkbox from "../components/Checkbox";
import Jazzicon from "../components/Jazzicon";
import { Tooltip } from "../components/Tooltip";
import HomeButton from "../components/HomeButton";
const { useIsActivating } = hooks;
@ -208,7 +209,8 @@ export default function PublishPage({
}, [listedApps, packageName, publisherId, isUpdate, setIsUpdate]);
return (
<div className="max-w-[900px] w-full">
<div className="max-w-[900px] w-full flex flex-col">
<HomeButton />
<SearchHeader
hideSearch
hidePublish

View File

@ -9,6 +9,8 @@ import { PageProps } from "../types/Page";
import { appId } from "../utils/app";
import classNames from 'classnames';
import { FaArrowRotateRight } from "react-icons/fa6";
import { isMobileCheck } from "../utils/dimensions";
import HomeButton from "../components/HomeButton";
interface StorePageProps extends PageProps { }
@ -21,6 +23,7 @@ export default function StorePage() {
const [searchQuery, setSearchQuery] = useState<string>("");
const [displayedApps, setDisplayedApps] = useState<AppInfo[]>(listedApps);
const [page, setPage] = useState(1);
const [tags, setTags] = useState<string[]>([])
const pages = useMemo(
() =>
@ -42,6 +45,14 @@ export default function StorePage() {
getListedApps()
.then((apps) => {
setDisplayedApps(Object.values(apps));
let _tags: string[] = [];
for (const app of Object.values(apps)) {
_tags = _tags.concat((app.metadata as any || {}).tags || [])
}
if (_tags.length === 0) {
_tags = ['App', 'Tags', 'Coming', 'Soon', 'tm'];
}
setTags(Array.from(new Set(_tags)))
})
.catch((error) => console.error(error));
}, []); // eslint-disable-line
@ -112,25 +123,48 @@ export default function StorePage() {
}
}, [rebuildIndex]);
const isMobile = isMobileCheck()
return (
<div className="max-w-[900px] w-full">
<div className={classNames("flex flex-col max-w-[900px] w-full", {
'gap-4': isMobile,
'gap-6': !isMobile
})}>
<HomeButton />
<SearchHeader value={searchQuery} onChange={searchApps} />
<div className="flex justify-between items-center my-2 mx-0">
<h4>New</h4>
<div className={classNames("flex items-center self-stretch justify-between", {
'gap-4': isMobile,
'gap-8': !isMobile
})}>
<button
className="flex flex-col c mr-auto ml-1 icon"
className="flex flex-col c icon icon-orange"
onClick={tryRebuildIndex}
title="Rebuild index"
>
<FaArrowRotateRight />
</button>
{tags.slice(0, isMobile ? 3 : 6).map(tag => (
<button
key={tag}
className="clear flex c rounded-full !bg-white/10 !hover:bg-white/25"
onClick={() => {
console.log('clicked tag', tag)
}}
>
{tag}
</button>
))}
<select
value={resultsSort}
onChange={(e) => {
setResultsSort(e.target.value);
sortApps(e.target.value);
}}
className={classNames({
'basis-1/5': !isMobile
})}
>
<option>Recently published</option>
<option>Most popular</option>
@ -138,13 +172,52 @@ export default function StorePage() {
<option>Recently updated</option>
</select>
</div>
<div className="flex flex-col flex-1 overflow-y-auto gap-2 max-h-[80vh]">
{displayedApps.map((app) => (
<AppEntry
key={appId(app) + (app.state?.our_version || "")}
app={app}
/>
))}
{!searchQuery ? <>
<h2>Top apps this week...</h2>
<div className={classNames("flex gap-2", {
'flex-wrap': isMobile
})}>
{displayedApps.slice(0, 4).map((app) => (
<AppEntry
key={appId(app) + (app.state?.our_version || "")}
size='medium'
app={app}
className={classNames("grow", {
'w-1/4': !isMobile,
'w-1/2': isMobile
})}
/>
))}
</div>
<h2>Must-have apps!</h2>
<div className={classNames("flex gap-2", {
'flex-wrap': isMobile
})}>
{displayedApps.slice(0, 6).map((app) => (
<AppEntry
key={appId(app) + (app.state?.our_version || "")}
size='small'
app={app}
overrideImageSize="large"
className={classNames("grow", {
'w-1/6': !isMobile,
'w-1/2': isMobile
})}
/>
))}
</div>
</> : <div className={classNames("flex-col-center", {
'gap-2': isMobile,
'gap-4': !isMobile,
})}>
{displayedApps.map(app => <AppEntry
size='large'
app={app}
className="self-stretch items-center"
overrideImageSize="medium"
/>)}
</div>}
<div className="flex flex-col gap-2 overflow-y-auto">
{pages.length > 1 && (
<div className="flex self-center">
{page !== pages[0] && (

View File

@ -5,16 +5,16 @@ export const appId = (app: AppInfo) => `${app.package}:${app.publisher}`
export const getAppName = (app: AppInfo) => app.metadata?.name || appId(app)
export enum AppType {
Downloaded = 'downloaded',
Installed = 'installed',
Local = 'local',
System = 'system',
Downloaded = 'downloaded',
Installed = 'installed',
Local = 'local',
System = 'system',
}
export const getAppType = (app: AppInfo) => {
if (app.publisher === 'sys') {
return AppType.System
} else if (app.state?.our_version && !app.state?.capsApproved) {
} else if (app.state?.our_version && !app.state?.caps_approved) {
return AppType.Downloaded
} else if (!app.metadata) {
return AppType.Local

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,8 +9,8 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-CiPfZ2kc.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C9o9YkgK.css">
<script type="module" crossorigin src="/assets/index-BrbxaEm2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-iMQiSiXv.css">
</head>
<body>

View File

@ -9,8 +9,8 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-CiPfZ2kc.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C9o9YkgK.css">
<script type="module" crossorigin src="/assets/index-BrbxaEm2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-iMQiSiXv.css">
</head>
<body>

View File

@ -1,5 +1,5 @@
{
"name": "ui",
"name": "kinode-homepage-ui",
"private": true,
"version": "0.0.0",
"type": "module",
@ -31,4 +31,4 @@
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
}
}

View File

@ -7,10 +7,11 @@ const AllApps: React.FC<{ expanded: boolean }> = ({ expanded }) => {
const { apps } = useHomepageStore()
const isMobile = isMobileCheck()
return <div className={classNames('flex-center flex-wrap gap-4 overflow-y-auto self-stretch relative', {
'max-h-0': !expanded,
'p-8 max-h-[1000px]': expanded,
'placeholder': isMobile
return <div className={classNames('flex-center flex-wrap overflow-y-auto fixed h-screen w-screen backdrop-blur-md transition transition-all ease-in-out duration-500', {
'top-[100vh]': !expanded,
'top-0': expanded,
'gap-4 p-8': isMobile,
'gap-8 p-16': !isMobile,
})}>
{apps.length === 0
? <div>Loading apps...</div>

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import KinodeText from '../components/KinodeText'
import KinodeBird from '../components/KinodeBird'
import useHomepageStore from '../store/homepageStore'
import useHomepageStore, { HomepageApp } from '../store/homepageStore'
import { FaChevronDown, FaChevronUp, FaScrewdriverWrench, FaV } from 'react-icons/fa6'
import AppsDock from '../components/AppsDock'
import AllApps from '../components/AllApps'
@ -20,37 +20,56 @@ interface AppStoreApp {
function Homepage() {
const [our, setOur] = useState('')
const [allAppsExpanded, setAllAppsExpanded] = useState(false)
const { apps, setApps, isHosted, fetchHostedStatus, showWidgetsSettings, setShowWidgetsSettings } = useHomepageStore()
const { setApps, isHosted, fetchHostedStatus, showWidgetsSettings, setShowWidgetsSettings } = useHomepageStore()
const isMobile = isMobileCheck()
const getAppPathsAndIcons = () => {
Promise.all([
fetch('/apps').then(res => res.json() as any as HomepageApp[]),
fetch('/main:app_store:sys/apps').then(res => res.json())
]).then(([appsData, appStoreData]) => {
console.log({ appsData, appStoreData })
const appz = appsData.map(app => ({
...app,
is_favorite: false, // Assuming initial state for all apps
}));
appStoreData.forEach((appStoreApp: AppStoreApp) => {
const existingAppIndex = appz.findIndex(a => a.package_name === appStoreApp.package);
if (existingAppIndex === -1) {
appz.push({
package_name: appStoreApp.package,
path: '',
label: appStoreApp.package,
state: appStoreApp.state,
is_favorite: false
});
} else {
appz[existingAppIndex] = {
...appz[existingAppIndex],
state: appStoreApp.state
};
}
});
setApps(appz);
// TODO: be less dumb about this edge case!
for (
let i = 0;
i < 5 && appz.find(a => a.package_name === 'app_store' && !a.base64_icon);
i++
) {
getAppPathsAndIcons();
}
});
}
useEffect(() => {
getAppPathsAndIcons();
}, [our]);
useEffect(() => {
fetch('/apps')
.then(res => res.json())
.then(data => setApps(data))
.then(() => {
fetch('/main:app_store:sys/apps')
.then(res => res.json())
.then(data => {
const appz = [...apps]
data.forEach((app: AppStoreApp) => {
if (!appz.find(a => a.package_name === app.package)) {
appz.push({
package_name: app.package,
path: '',
label: app.package,
state: app.state,
is_favorite: false
})
} else {
const i = appz.findIndex(a => a.package_name === app.package)
if (i !== -1) {
appz[i] = { ...appz[i], state: app.state }
}
}
})
setApps(appz)
})
})
fetch('/our')
.then(res => res.text())
.then(data => {
@ -95,13 +114,14 @@ function Homepage() {
<AppsDock />
<Widgets />
<button
className={classNames("clear flex-center self-center", {
'-mb-1': !allAppsExpanded,
className={classNames("fixed alt clear flex-center self-center z-20", {
'bottom-2 right-2': isMobile,
'bottom-8 right-8': !isMobile,
})}
onClick={() => setAllAppsExpanded(!allAppsExpanded)}
>
{allAppsExpanded ? <FaChevronDown /> : <FaChevronUp />}
<span className="ml-2">{allAppsExpanded ? 'Collapse' : 'All installed apps'}</span>
<span className="ml-2">{allAppsExpanded ? 'Collapse' : 'All apps'}</span>
</button>
<AllApps expanded={allAppsExpanded} />
{showWidgetsSettings && <WidgetsSettingsModal />}

View File

@ -1141,88 +1141,18 @@ Constrain images and videos to the parent width and preserve their intrinsic asp
</style>
</head>
<body><noscript>You need to enable JavaScript to run this app.</noscript>
<body
style="background-color: #22211f;
background-image: linear-gradient(-55deg, #f75a2977 0%, transparent 72.05%),linear-gradient(-11deg, #86000172 3%, transparent 57.05%);">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<svg width="1440" height="1024" viewBox="0 0 1440 1024" fill="none" xmlns="http://www.w3.org/2000/svg" style="position: fixed;
top: -300px;
left: -350px;
z-index: -1;
filter: blur(100px);
transform: scale(3);">
<g clip-path="url(#clip0_152_8506)">
<rect width="1440" height="2777" transform="translate(0 -5)" fill="#22211F" />
<path
d="M2122.93 951.101C1925.12 1458.86 1658.87 2101.89 1376.85 1992.03C601.547 2045.26 -152.056 1158.27 434.949 891.737C1021.95 625.205 1605.89 -327.704 1970.46 -167.208C2361.3 4.85831 2320.74 443.343 2122.93 951.101Z"
fill="url(#paint0_radial_152_8506)" />
<path
d="M274.686 1388.42C15.0266 1578.85 -501.874 1262.71 -695.499 998.693C-889.123 734.676 -935.603 448.185 -675.943 257.757C-416.284 67.3282 500.306 639.9 693.93 903.918C887.555 1167.94 534.346 1197.99 274.686 1388.42Z"
fill="url(#paint1_linear_152_8506)" />
<path
d="M673.921 -604.363C829.107 -41.0363 308.158 505 -81.9719 505C-472.102 505 -788.365 72.0217 -788.365 -462.084C-788.365 -996.189 -1002.49 -1551 -612.361 -1551C-222.231 -1551 673.921 -1138.47 673.921 -604.363Z"
fill="#F75A29" fill-opacity="0.7" />
<g filter="url(#filter0_b_152_8506)">
<rect width="1464" height="2823" transform="translate(0 -5)" fill="#22211F" fill-opacity="0.01" />
</g>
</g>
<defs>
<filter id="filter0_b_152_8506" x="-314" y="-319" width="2092" height="3451" filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="157" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_152_8506" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_152_8506" result="shape" />
</filter>
<radialGradient id="paint0_radial_152_8506" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
gradientTransform="translate(1444 1488.65) rotate(-86.9561) scale(699.105 590.175)">
<stop offset="0.635" stop-color="#F35422" stop-opacity="0.7" />
<stop offset="1" stop-color="#E60D16" stop-opacity="0.35" />
</radialGradient>
<linearGradient id="paint1_linear_152_8506" x1="-389.52" y1="620.673" x2="747.127" y2="934.639"
gradientUnits="userSpaceOnUse">
<stop stop-color="#F35422" />
<stop offset="1" stop-color="#E60D16" stop-opacity="0.1" />
</linearGradient>
<clipPath id="clip0_152_8506">
<rect width="1440" height="2777" fill="white" transform="translate(0 -5)" />
</clipPath>
</defs>
</svg>
<div id="signup-page" class="flex flex-col place-items-center place-content-center h-screen font-sans">
<div id="home-form-header" class="flex flex-col place-content-center place-items-center"
style="gap: 24px; margin: 32px 0 56px;">
<h3 style="text-align: center;">Login to</h3>
<svg width="289.5" height="36" viewBox="0 0 580 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_641)">
<path d="M0.824922 1.07031L0.794922 70.0703H14.7949L14.8049 1.07031H0.824922Z" fill="#FFF5D9"></path>
<path d="M16.5947 36.8803L41.2547 1.07031H58.2447L33.1647 36.8803L61.2447 70.0703H42.9947L16.5947 36.8803Z"
fill="#FFF5D9"></path>
<path d="M119.885 1.07031H105.765V70.0703H119.885V1.07031Z" fill="#FFF5D9"></path>
<path
d="M173.185 1.07031V70.0703H186.775V26.8303L224.045 70.0703H234.825V1.07031H221.325V45.6803L183.445 1.07031H173.185Z"
fill="#FFF5D9"></path>
<path
d="M342.465 8.86C333.025 0.15 321.645 0 318.535 0C315.475 0 303.575 0.22 294.005 9.52C283.845 19.4 283.805 32.24 283.795 35.66C283.785 39.3 283.895 49.03 290.805 57.99C300.855 71.02 316.695 71.31 318.535 71.32C321.375 71.32 334.185 71 343.965 60.66C353.065 51.04 353.265 39.4 353.275 35.66C353.275 32.49 353.305 18.86 342.455 8.86H342.465ZM318.435 58.01C307.095 58.01 297.895 47.95 297.895 35.54C297.895 23.13 307.085 13.07 318.435 13.07C329.785 13.07 338.975 23.13 338.975 35.54C338.975 47.95 329.785 58.01 318.435 58.01Z"
fill="#FFF5D9"></path>
<path
d="M450.495 12.0802C444.975 5.46023 437.135 0.990234 427.955 0.990234C417.555 0.990234 405.295 1.07023 402.295 1.07023V69.9802C405.285 69.9802 417.555 70.0602 427.955 70.0602C445.525 70.0602 458.445 53.4102 459.065 36.8602C459.395 28.0102 456.185 18.9002 450.495 12.0802ZM440.085 49.9502C436.895 53.8702 432.705 56.6902 427.665 57.5602C424.025 58.1902 420.095 57.8302 416.405 57.8302C416.405 50.4002 416.405 42.9802 416.405 35.5502V13.2202C423.795 13.2202 430.525 12.7002 436.605 17.6002C440.275 20.5602 442.925 24.7102 444.165 29.2402C444.525 30.5402 444.765 31.8802 444.875 33.2302C445.395 39.3702 443.995 45.1402 440.085 49.9502Z"
fill="#FFF5D9"></path>
<path
d="M508.135 0.990234V70.0602H552.715V57.9302H522.035V40.4202H547.125V28.0702H521.995V13.3202H552.715V0.990234H508.135Z"
fill="#FFF5D9"></path>
<path
d="M574.835 66.0398H572.745L571.015 63.0698H569.845V66.0398H567.805V57.5498H571.765C572.845 57.5498 573.865 57.9298 574.425 58.9398C575.205 60.3698 574.665 62.3798 573.105 63.0298C573.725 64.1198 574.225 64.9498 574.845 66.0398H574.835ZM570.375 61.0798H570.845C571.335 61.0798 572.365 61.0798 572.365 60.2898C572.365 59.5598 571.335 59.5598 570.845 59.5598H570.375V61.0798Z"
fill="#FFF5D9"></path>
<path
d="M570.964 69.0002C574.913 69.0002 578.114 65.799 578.114 61.8502C578.114 57.9014 574.913 54.7002 570.964 54.7002C567.016 54.7002 563.814 57.9014 563.814 61.8502C563.814 65.799 567.016 69.0002 570.964 69.0002Z"
stroke="#FFF5D9" stroke-width="2.2" stroke-miterlimit="10"></path>
</g>
<defs>
<clipPath id="clip0_6_641">
<rect width="578.41" height="71.32" fill="white" transform="translate(0.794922)"></rect>
</clipPath>
</defs>
</svg>
<h1 style="font-family: 'Futura', sans-serif; letter-spacing: 0.4em; font-size: xxx-large; margin-left: 1em;">
KINODE<span style="font-size: small">&reg;</span>
</h1>
<svg style="margin-top: 0.5em;" width="67.5" height="48" viewBox="0 0 122 81" fill="none"
xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_651)">
@ -1240,7 +1170,7 @@ Constrain images and videos to the parent width and preserve their intrinsic asp
<form id="signup-form" class="flex flex-col">
<h3 class="flex flex-col ml-2 text-lg"> ${node} </h3>
<div class="flex mt-2 mb-2"> Enter Password </div>
<input type="password" id="password" required="" minlength="6" name="password" placeholder="Password"
<input autofocus type="password" id="password" required="" minlength="6" name="password" placeholder="Password"
oninput="document.getElementById('password-err').style.display = 'none';" value="" class="self-stretch mb-2">
<div id="password-err" class="login-row flex mb-2" style="display: none;"> Incorrect Password </div>
<div class="flex flex-col leading-6 self-stretch mb-2">
@ -1260,7 +1190,7 @@ Constrain images and videos to the parent width and preserve their intrinsic asp
if ('${fake}' === 'true') {
document.getElementById("fake-or-not").innerHTML = "Fake node -- any password will work!";
} else {
document.getElementById("fake-or-not").innerHTML = "Restart node to change networking info";
document.getElementById("fake-or-not").innerHTML = "To change your networking info, please restart your node.";
}
async function login(password) {

View File

@ -1,5 +1,5 @@
{
"name": "register",
"name": "kinode-register-ui",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:8080",