Merge pull request #301 from kinode-dao/tm/login

appstore facelift
This commit is contained in:
bitful-pannul 2024-04-10 17:58:50 -04:00 committed by GitHub
commit 0fff1d726e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
60 changed files with 5059 additions and 2764 deletions

23
.dockerignore Normal file
View File

@ -0,0 +1,23 @@
target/
wit/
**/target/
**/wit/
**/*.wasm
.vscode
.app-signing
.DS_Store
*.swp
*.swo
*.zip
/home
packages/**/pkg/*.wasm
packages/**/wit
*/**/node_modules
.env
kinode/src/bootstrapped_processes.rs
kinode/packages/**/wasi_snapshot_preview1.wasm
LICENSE
pull_request_template.md
README.md
Dockerfile

32
Dockerfile Normal file
View File

@ -0,0 +1,32 @@
# syntax=docker/dockerfile:1
FROM rust AS builder
COPY . /tmp/source
WORKDIR /tmp/source
RUN apt-get update
RUN apt-get install clang -y
RUN cargo install wasm-tools && \
rustup install nightly && \
rustup target add wasm32-wasi && \
rustup target add wasm32-wasi --toolchain nightly && \
cargo install cargo-wasi
RUN cargo +nightly build -p kinode --release
FROM debian:12-slim
RUN apt-get update
RUN apt-get install openssl -y
COPY --from=builder /tmp/source/target/release/kinode /bin/kinode
ENV LD_LIBRARY_PATH=/lib
ENV RUST_BACKTRACE=full
ENTRYPOINT [ "/bin/kinode" ]
CMD [ "/kinode-home" ]
EXPOSE 8080
EXPOSE 9000

View File

@ -134,3 +134,25 @@ Download and install an app:
m our@main:app_store:sys '{"Download": {"package": {"package_name": "<pkg>", "publisher_node": "<node>"}, "install_from": "<node>"}}'
m our@main:app_store:sys '{"Install": {"package_name": "<pkg>", "publisher_node": "<node>"}}'
```
## Running as a Docker container
This image expects a volume mounted at `/kinode-home`. This volume may be empty or may contain another Kinode's data. It will be used as the home directory of your Kinode.
The image includes EXPOSE directives for TCP port `8080` and TCP port `9000`. Port `8080` is used for serving the Kinode web dashboard over HTTP, and it may be mapped to a different port on the host. Port `9000` is optional and is only required for a direct node.
If you are running a direct node, you must map port `9000` to the same port on the host and on your router. Otherwise, your Kinode will not be able to connect to the rest of the network as connection info is written to the chain, and this information is based on the view from inside the Docker container.
To build a local Docker image, run the following command in this project root.
```
docker build -t 0xlynett/kinode .
```
For example:
```
docker volume create kinode-volume
docker run -d -p 8080:8080 -it --name my-kinode \
--mount type=volume,source=kinode-volume,destination=/kinode-home \
0xlynett/kinode
```

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

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

@ -0,0 +1,4 @@
<svg width="779" height="514" viewBox="0 0 779 514" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M753.092 5.91932C756.557 5.09976 755.962 -0.00012207 752.401 -0.00012207H426.001C424.755 -0.00012207 423.639 0.77027 423.197 1.93535L236.968 492.6C235.729 495.865 240.123 498.255 242.191 495.441L569.357 50.1132C569.778 49.5392 570.391 49.1339 571.084 48.97L753.092 5.91932Z" fill="#FFF5D9"/>
<path d="M11.9665 40.2288C9.10949 38.777 10.2135 34.4583 13.4167 34.5557L404.273 46.4367C406.334 46.4993 407.719 48.5749 406.986 50.5023L347.438 206.981C346.804 208.647 344.865 209.396 343.275 208.588L11.9665 40.2288Z" fill="#FFF5D9"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -15,8 +15,8 @@
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet'>
<script type="module" crossorigin src="/main:app_store:sys/assets/index-YeOEFbyC.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-JESB3UJK.css">
<script type="module" crossorigin src="/main:app_store:sys/assets/index-przgvy-e.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-avBSgupm.css">
</head>
<body>

View File

@ -0,0 +1 @@
npm run build:copy && cd ~/kinode && cargo +nightly build -p kinode && cd kinode/packages/app_store/ui

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@
"dependencies": {
"@ethersproject/hash": "^5.7.0",
"@kinode/client-api": "^0.1.0",
"@metamask/jazzicon": "^2.0.0",
"@szhsin/react-menu": "^4.1.0",
"@web3-react/coinbase-wallet": "^8.2.3",
"@web3-react/core": "^8.2.2",
@ -27,12 +28,14 @@
"@web3-react/walletconnect": "^8.2.3",
"@web3-react/walletconnect-connector": "^6.2.13",
"@web3-react/walletconnect-v2": "^8.5.1",
"classnames": "^2.5.1",
"ethers": "^5.7.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-router-dom": "^6.21.3",
"tailwindcss": "^3.4.3",
"unocss": "^0.59.0-beta.1",
"zustand": "^4.4.7"
},
"devDependencies": {

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,4 @@
<svg width="779" height="514" viewBox="0 0 779 514" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M753.092 5.91932C756.557 5.09976 755.962 -0.00012207 752.401 -0.00012207H426.001C424.755 -0.00012207 423.639 0.77027 423.197 1.93535L236.968 492.6C235.729 495.865 240.123 498.255 242.191 495.441L569.357 50.1132C569.778 49.5392 570.391 49.1339 571.084 48.97L753.092 5.91932Z" fill="#FFF5D9"/>
<path d="M11.9665 40.2288C9.10949 38.777 10.2135 34.4583 13.4167 34.5557L404.273 46.4367C406.334 46.4993 407.719 48.5749 406.986 50.5023L347.438 206.981C346.804 208.647 344.865 209.396 343.275 208.588L11.9665 40.2288Z" fill="#FFF5D9"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,381 +0,0 @@
#root {
max-width: 700px;
margin: 0 auto;
padding: 2rem 0;
text-align: center;
width: 75%;
max-height: calc(100vh - 64px);
min-height: calc(100vh - 64px);
}
/* General */
.row {
display: flex;
flex-direction: row;
align-items: center;
}
.row.center {
justify-content: center;
}
.row.between {
justify-content: space-between;
}
.row.around {
justify-content: space-around;
}
.col {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.col.center {
align-items: center;
}
.card {
background-color: var(--input-background);
border-radius: 0.75em;
border: 1px solid var(--orange-medium);
padding: 1em;
}
button.action-btn {
min-width: 100px;
}
button.small {
padding: 0.25em 0.5em;
height: auto;
}
/* Specific */
.searchbar {
height: 2.25em;
padding: .5em 1em;
border-radius: 16px;
flex: 1;
background-color: var(--input-background);
text-align: left;
}
.searchbar>input {
border: none;
height: 1.5em;
margin-left: 0.5em;
flex: 1;
}
button.connect-wallet {
margin: 1em auto 0;
}
.my-pkg-btn {
margin-left: 1em;
}
.my-pkg-btn.selected {
background-color: var(--bg-gray-medium);
}
.app-header {
cursor: pointer;
width: calc(100% - 10.3em);
justify-content: flex-start;
}
.app-header:hover {
text-decoration: underline;
}
.app-header.large:hover {
text-decoration: none;
cursor: default;
}
.app-header.small>img {
height: 3em;
margin-right: 1em;
border-radius: 0.375em;
}
.app-header>img {
height: 3em;
margin-right: 1em;
border-radius: 0.375em;
}
.app-header.large>img {
height: 5em;
margin-right: 1em;
border-radius: 0.5em;
}
.app-header.large .app-name {
font-size: 1.5em;
}
.app-entry {
width: 100%;
}
.app-actions {
margin-right: 0.5em;
}
.dropdown {
cursor: pointer;
position: relative;
}
.dropdown>ul {
background-color: var(--orange-medium);
padding: 0.5em 1em;
border-radius: 0.5em;
align-items: flex-start;
text-align: left;
border: 1px solid var(--orange-medium);
display: flex;
flex-direction: column;
}
.dropdown .dropdown-header {
align-self: flex-end;
}
.dropdown .dropdown-list {
position: absolute;
top: 1em;
right: -0.5em;
}
.page-selector {
margin: 0.25em 0.5em;
}
.page-selector.selected {
font-weight: 900;
}
.back-btn {
margin-right: 1em;
justify-content: center;
width: 2.5em;
}
.app-details {
margin-top: 0.5em;
align-items: flex-start;
}
.app-details .title {
width: 8em;
text-align: left;
}
.app-details .value {
margin-bottom: 0.5em;
text-align: left;
max-width: calc(100% - 8em);
}
.app-details .value.underline {
text-decoration: underline;
}
.app-details .value.permission {
background-color: var(--bg-gray-medium);
border-radius: 2em;
padding: 0.25em 0.5em;
margin-bottom: 0.5em;
}
.app-screenshots {
margin-top: 0.5em;
overflow-x: scroll;
max-width: 100%;
}
.app-screenshots>img {
margin-right: 1em;
max-height: 10em;
max-width: 100%;
border-radius: 0.5em;
border: 1px solid var(--bg-gray-medium);
}
.search-icon {
cursor: pointer;
color: var(--bg-gray-solid);
font-size: 1.25em;
}
.f-width {
width: 100%;
}
#loading h3 {
text-align: center;
}
#loader {
display: inline-block;
position: relative;
width: 48px;
height: 48px;
margin-top: 16px;
}
#loader div {
box-sizing: border-box;
display: block;
position: absolute;
width: 36px;
height: 36px;
margin: 6px;
border: 6px solid #fff;
border-radius: 50%;
animation: loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #fff transparent transparent transparent;
}
#loader div:nth-child(1) {
animation-delay: -0.45s;
}
#loader div:nth-child(2) {
animation-delay: -0.3s;
}
#loader div:nth-child(3) {
animation-delay: -0.15s;
}
@keyframes loader {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.action-entry {
margin-bottom: 0.25em;
color: inherit;
white-space: nowrap;
cursor: pointer;
padding: 0.25em;
}
.action-entry:hover {
transform: scale(1.05);
}
.action-entry:first-child {
margin-top: 0.25em;
}
.my-apps-list {
flex: 1;
height: 100%;
overflow-y: scroll;
max-height: calc(100vh - 10em);
border-radius: 0.5em;
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.title {
width: calc(100% - 6em);
}
.title>div {
max-width: 100%;
}
.modal-backdrop {
background-color: rgba(0, 0, 0, 0.3);
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 3;
min-height: 10em;
min-width: 20em;
}
.modal-backdrop .close {
position: absolute;
top: 0.5em;
right: 0.5em;
font-size: 18px;
font-weight: 200;
cursor: pointer;
transform: rotate(45deg);
}
.modal {
position: relative;
background-color: var(--dark-background);
color: black;
border-radius: 8px;
padding: 24px;
line-height: 24px;
max-width: 500px;
min-width: 300px;
color: var(--text-light);
}
.modal .modal-title {
margin-top: 0;
margin-bottom: 0.5em;
}
.modal .modal-content {
align-items: center;
width: 100%;
gap: 1em;
}
form.new {
gap: 1em;
}
form.metadata {
gap: 0.5em;
align-items: center;
}
form.metadata input {
width: 100%;
}
form.metadata .row {
margin-top: 1em;
}
form.metadata .col.label {
width: 80%;
}
.page-title {
align-items: center;
margin: 1em 0;
}

View File

@ -12,7 +12,6 @@ import { MY_APPS_PATH } from "./constants/path";
import { ChainId, PACKAGE_STORE_ADDRESSES } from "./constants/chain";
import PublishPage from "./pages/PublishPage";
import { hooks as metaMaskHooks, metaMask } from './utils/metamask'
import "./App.css";
const connectors: [MetaMask, Web3ReactHooks][] = [
[metaMask, metaMaskHooks],
@ -43,9 +42,8 @@ const RPC_URL = import.meta.env.VITE_SEPOLIA_RPC_URL;
const BASE_URL = import.meta.env.BASE_URL;
if (window.our) window.our.process = BASE_URL?.replace("/", "");
const PROXY_TARGET = `${
import.meta.env.VITE_NODE_URL || "http://localhost:8080"
}${BASE_URL}`;
const PROXY_TARGET = `${import.meta.env.VITE_NODE_URL || "http://localhost:8080"
}${BASE_URL}`;
// This env also has BASE_URL which should match the process + package name
const WEBSOCKET_URL = import.meta.env.DEV // eslint-disable-line
@ -96,7 +94,7 @@ function App() {
if (!nodeConnected) {
return (
<div className="node-not-connected">
<div className="flex flex-col c">
<h2 style={{ color: "red" }}>Node not connected</h2>
<h4>
You need to start a node at {PROXY_TARGET} before you can use this UI
@ -109,16 +107,18 @@ function App() {
const props = { provider, packageAbi };
return (
<Web3ReactProvider connectors={connectors}>
<Router basename={BASE_URL}>
<Routes>
<Route path="/" element={<StorePage {...props} />} />
<Route path={MY_APPS_PATH} element={<MyAppsPage {...props} />} />
<Route path="/app-details/:id" element={<AppPage {...props} />} />
<Route path="/publish" element={<PublishPage {...props} />} />
</Routes>
</Router>
</Web3ReactProvider>
<div className="flex flex-col c h-screen w-screen">
<Web3ReactProvider connectors={connectors}>
<Router basename={BASE_URL}>
<Routes>
<Route path="/" element={<StorePage {...props} />} />
<Route path={MY_APPS_PATH} element={<MyAppsPage {...props} />} />
<Route path="/app-details/:id" element={<AppPage {...props} />} />
<Route path="/publish" element={<PublishPage {...props} />} />
</Routes>
</Router>
</Web3ReactProvider>
</div>
);
}

View File

@ -1,18 +0,0 @@
<svg width="580" height="72" 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 d="M16.5947 36.8803L41.2547 1.07031H58.2447L33.1647 36.8803L61.2447 70.0703H42.9947L16.5947 36.8803Z" fill="#FFF5D9"/>
<path d="M119.885 1.07031H105.765V70.0703H119.885V1.07031Z" fill="#FFF5D9"/>
<path d="M173.185 1.07031V70.0703H186.775V26.8303L224.045 70.0703H234.825V1.07031H221.325V45.6803L183.445 1.07031H173.185Z" fill="#FFF5D9"/>
<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 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 d="M508.135 0.990234V70.0602H552.715V57.9302H522.035V40.4202H547.125V28.0702H521.995V13.3202H552.715V0.990234H508.135Z" fill="#FFF5D9"/>
<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 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"/>
</g>
<defs>
<clipPath id="clip0_6_641">
<rect width="578.41" height="71.32" fill="white" transform="translate(0.794922)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -4,6 +4,7 @@ import useAppsStore from "../store/apps-store";
import Modal from "./Modal";
import { getAppName } from "../utils/app";
import Loader from "./Loader";
import classNames from "classnames";
interface ActionButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
@ -136,22 +137,22 @@ export default function ActionButton({ app, ...props }: ActionButtonProps) {
<button
{...props}
type="button"
className={`small action-btn ${props.className || ""}`}
className={classNames("text-sm min-w-[100px] px-2 py-1 self-start", props.className)}
onClick={onClick}
>
{installed && updatable
? "Update"
: installed
? "Installed"
: downloaded
? "Install"
: "Download"}
? "Installed"
: downloaded
? "Install"
: "Download"}
</button>
<Modal show={showModal} hide={() => setShowModal(false)}>
{loading ? (
<Loader msg={loading} />
) : clean ? (
<form className="col" style={{alignItems: "center", gap: "1em"}} onSubmit={download}>
<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)}>
@ -167,7 +168,7 @@ export default function ActionButton({ app, ...props }: ActionButtonProps) {
value={customMirror}
onChange={(e) => setCustomMirror(e.target.value)}
placeholder="Mirror, i.e. 'template.os'"
style={{ padding: "0.5em", maxWidth: 240, width: "100%" }}
className="p-1 max-w-[240px] w-full"
required
autoFocus
/>
@ -179,10 +180,10 @@ export default function ActionButton({ app, ...props }: ActionButtonProps) {
) : downloaded ? (
<>
<h4>Approve App Permissions</h4>
<h5 style={{ margin: 0 }}>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
<ul className="col" style={{ alignItems: "flex-start" }}>
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}
@ -194,12 +195,12 @@ export default function ActionButton({ app, ...props }: ActionButtonProps) {
) : (
<>
<h4>Approve App Permissions</h4>
<h5 style={{ margin: 0 }}>
<h5 className="m-0">
{getAppName(app)} needs the following permissions:
</h5>
{/* <h5>Send Messages:</h5> */}
<br />
<ul className="col" style={{ alignItems: "flex-start" }}>
<ul className="flex flex-col items-start">
{caps.map((cap) => (
<li key={cap}>{cap}</li>
))}

View File

@ -12,10 +12,10 @@ interface AppEntryProps extends React.HTMLAttributes<HTMLDivElement> {
export default function AppEntry({ app, ...props }: AppEntryProps) {
return (
<div {...props} key={appId(app)} className="app-entry row between">
<div {...props} key={appId(app)} className="flex justify-between w-full rounded hover:bg-white/10 card">
<AppHeader app={app} size="small" />
<div className="app-actions row">
{!app.state?.caps_approved && <ActionButton app={app} style={{ marginRight: "1em" }} />}
<div className="flex mr-1 items-start">
{!app.state?.caps_approved && <ActionButton app={app} className="mr-2" />}
<MoreActions app={app} />
</div>
</div>

View File

@ -2,6 +2,7 @@ import React from "react";
import { AppInfo } from "../types/Apps";
import { appId } from "../utils/app";
import { useNavigate } from "react-router-dom";
import classNames from "classnames";
interface AppHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
app: AppInfo;
@ -18,7 +19,7 @@ export default function AppHeader({
return (
<div
{...props}
className={`app-header row ${size} ${props.className || ""}`}
className={classNames('flex w-full justify-content-start', size, props.className, { 'cursor-pointer': size !== 'large' })}
onClick={() => navigate(`/app-details/${appId(app)}`)}
>
<img
@ -27,11 +28,18 @@ export default function AppHeader({
"https://png.pngtree.com/png-vector/20190215/ourmid/pngtree-vector-question-mark-icon-png-image_515448.jpg"
}
alt="app icon"
className={classNames('mr-2', { 'h-32 rounded-md': size === 'large', 'h-12 rounded': size !== 'large' })}
/>
<div className="col title">
<div className="app-name ellipsis">{app.metadata?.name || appId(app)}</div>
<div className="flex flex-col w-full">
<div
className={classNames("whitespace-nowrap overflow-hidden text-ellipsis", { 'text-3xl': size === 'large', })}
>
{app.metadata?.name || appId(app)}
</div>
{app.metadata?.description && size !== "large" && (
<div className="ellipsis">{app.metadata?.description?.slice(0, 100)}</div>
<div className="whitespace-nowrap overflow-hidden text-ellipsis">
{app.metadata?.description?.slice(0, 100)}
</div>
)}
</div>
</div>

View File

@ -1,4 +1,5 @@
import React from "react";
import { FaCheck } from "react-icons/fa6";
export default function Checkbox({
readOnly = false,
@ -10,7 +11,7 @@ export default function Checkbox({
setChecked?: (checked: boolean) => void;
}) {
return (
<div style={{ position: "relative" }}>
<div className="relative">
<input
type="checkbox"
id="checked"
@ -21,9 +22,10 @@ export default function Checkbox({
readOnly={readOnly}
/>
{checked && (
<span onClick={() => setChecked && setChecked(false)} className="checkmark">
&#10003;
</span>
<FaCheck
className="absolute left-1 top-1 cursor-pointer"
onClick={() => setChecked && setChecked(false)}
/>
)}
</div>
);

View File

@ -1,15 +1,21 @@
import React from 'react';
import { FaEllipsisH } from 'react-icons/fa';
import { Menu, MenuButton } from '@szhsin/react-menu';
import classNames from 'classnames';
interface DropdownProps extends React.HTMLAttributes<HTMLDivElement> {
}
export default function Dropdown({ ...props }: DropdownProps) {
return (
<Menu {...props} className={"dropdown " + props.className} menuButton={<MenuButton className="small">
<FaEllipsisH style={{ marginBottom: '-0.125em' }} />
</MenuButton>}>
<Menu
{...props}
unmountOnClose={true}
className={classNames("relative", props.className)}
menuButton={<MenuButton className="small">
<FaEllipsisH className='-mb-1' />
</MenuButton>}
>
{props.children}
</Menu>
)

View File

@ -0,0 +1,27 @@
import React, { useEffect, useRef } from 'react';
import jazzicon from '@metamask/jazzicon';
interface JazziconProps extends React.HTMLAttributes<HTMLDivElement> {
address: string;
diameter?: number;
}
const Jazzicon: React.FC<JazziconProps> = ({ address, diameter = 40, ...props }) => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (address && ref.current) {
const seed = parseInt(address.slice(2, 10), 16); // Derive a seed from Ethereum address
const icon = jazzicon(diameter, seed);
// Clear the current icon
ref.current.innerHTML = '';
// Append the new icon
ref.current.appendChild(icon);
}
}, [address, diameter]);
return <div {...props} ref={ref} />;
};
export default Jazzicon;

View File

@ -1,5 +1,6 @@
import React, { useCallback, useEffect, useState } from "react";
import { AppInfo } from "../types/Apps";
import { FaX } from "react-icons/fa6";
interface Props {
app?: AppInfo;
@ -111,9 +112,9 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
};
return (
<form className="col card metadata" style={{ gap: "0.5em" }}>
<form className="flex flex-col card mt-2 gap-2">
<h4>Fill out metadata</h4>
<div className="col label">
<div className="flex flex-col w-3/4">
<label className="metadata-label">Name</label>
<input
type="text"
@ -122,7 +123,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
onChange={(e) => handleFieldChange("name", e.target.value)}
/>
</div>
<div className="col label">
<div className="flex flex-col w-3/4">
<label className="metadata-label">Description</label>
<input
type="text"
@ -131,7 +132,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
onChange={(e) => handleFieldChange("description", e.target.value)}
/>
</div>
<div className="col label">
<div className="flex flex-col w-3/4">
<label className="metadata-label">Image URL</label>
<input
type="text"
@ -140,7 +141,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
onChange={(e) => handleFieldChange("image", e.target.value)}
/>
</div>
<div className="col label">
<div className="flex flex-col w-3/4">
<label className="metadata-label">External URL</label>
<input
type="text"
@ -149,7 +150,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
onChange={(e) => handleFieldChange("external_url", e.target.value)}
/>
</div>
<div className="col label">
<div className="flex flex-col w-3/4">
<label className="metadata-label">Animation URL</label>
<input
type="text"
@ -158,7 +159,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
onChange={(e) => handleFieldChange("animation_url", e.target.value)}
/>
</div>
<div className="col label">
<div className="flex flex-col w-3/4">
<label className="metadata-label">Package Name</label>
<input
type="text"
@ -167,7 +168,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
onChange={(e) => handleFieldChange("package_name", e.target.value)}
/>
</div>
<div className="col label">
<div className="flex flex-col w-3/4">
<label className="metadata-label">Current Version</label>
<input
type="text"
@ -176,7 +177,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
onChange={(e) => handleFieldChange("current_version", e.target.value)}
/>
</div>
<div className="col label">
<div className="flex flex-col w-3/4">
<label className="metadata-label">Publisher</label>
<input
type="text"
@ -185,7 +186,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
onChange={(e) => handleFieldChange("publisher", e.target.value)}
/>
</div>
<div className="col label">
<div className="flex flex-col w-3/4">
<label className="metadata-label">Mirrors (separated by commas)</label>
<input
type="text"
@ -200,25 +201,16 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
/>
</div>
<div
className="col label"
style={{
gap: "0.5em",
}}
className="flex flex-col w-3/4 gap-2"
>
<div
className="row"
style={{
gap: "0.5em",
marginTop: 0,
justifyContent: "space-between",
width: "100%",
}}
className="flex gap-2 mt-0 justify-between w-full"
>
<h5 style={{ margin: 0 }}>Code Hashes</h5>
<h5 className="m-0">Code Hashes</h5>
<button
type="button"
onClick={() => setCodeHashes([...codeHashes, ["", ""]])}
className="small"
className="clear"
>
Add code hash
</button>
@ -227,8 +219,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
{codeHashes.map(([version, hash], ind, arr) => (
<div
key={ind + "_code_hash"}
className="row"
style={{ gap: "0.5em", marginTop: 0, width: "100%" }}
className="flex gap-2 mt-0 w-full"
>
<input
type="text"
@ -241,7 +232,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
return newHashes;
})
}
style={{ flex: 1 }}
className="flex-1"
/>
<input
type="text"
@ -254,7 +245,7 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
return newHashes;
})
}
style={{ flex: 5 }}
className="flex-5"
/>
{arr.length > 1 && (
<button
@ -262,24 +253,19 @@ const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
onClick={() =>
setCodeHashes((prev) => prev.filter((_, i) => i !== ind))
}
style={{
fontSize: "2em",
height: 32,
lineHeight: "1em",
padding: "0 0.2em",
}}
className="icon"
>
&times;
<FaX />
</button>
)}
</div>
))}
</div>
<div className="row" style={{ gap: "0.5em", margin: "1em 0" }}>
<button type="button" onClick={handleSubmit}>
<div className="flex gap-2 my-4">
<button type="button" onClick={handleSubmit} className="alt">
Download JSON
</button>
<button type="button" onClick={handleClearForm}>
<button type="button" onClick={handleClearForm} className="clear">
Clear Form
</button>
<button type="button" onClick={goBack}>

View File

@ -1,5 +1,6 @@
import classNames from 'classnames'
import React, { MouseEvent } from 'react'
import { FaPlus } from 'react-icons/fa'
import { FaX } from 'react-icons/fa6'
export interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
show: boolean
@ -25,13 +26,30 @@ const Modal: React.FC<ModalProps> = ({
}
return (
<div className={`modal-backdrop ${show ? 'show' : ''}`} onClick={hide}>
<div {...props} className={`col modal ${props.className || ''}`} onClick={dontHide}>
{Boolean(title) && <h4 className='modal-title'>{title}</h4>}
<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]`,
{ show }
)}
onClick={hide}
>
<div
{...props}
className={`flex flex-col relative bg-black/90 rounded-lg py-6 px-12 ${props.className || ''}`}
onClick={dontHide}
>
{Boolean(title) && <h4 className='mt-0 mb-2'>{title}</h4>}
{!hideClose && (
<FaPlus className='close' onClick={hide} />
<button
className='icon absolute top-1 right-1'
onClick={hide}
>
<FaX />
</button>
)}
<div className='col modal-content' onClick={dontHide}>
<div
className='flex flex-col items-center w-full'
onClick={dontHide}
>
{props.children}
</div>
</div>

View File

@ -1,6 +1,5 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { MenuItem } from "@szhsin/react-menu";
import Dropdown from "./Dropdown";
import { AppInfo } from "../types/Apps";
@ -9,74 +8,75 @@ import useAppsStore from "../store/apps-store";
interface MoreActionsProps extends React.HTMLAttributes<HTMLButtonElement> {
app: AppInfo;
className?: string;
}
export default function MoreActions({ app }: MoreActionsProps) {
export default function MoreActions({ app, className }: MoreActionsProps) {
const { uninstallApp, setMirroring, setAutoUpdate } = useAppsStore();
const navigate = useNavigate();
const downloaded = Boolean(app.state);
if (!downloaded) {
if (!app.metadata) return <div style={{ width: 38 }} />;
if (!app.metadata) return <></>;
return (
<Dropdown>
{app.metadata?.description && (
<MenuItem
className="action-entry"
onClick={() => navigate(`/app-details/${appId(app)}`)}
>
View Details
</MenuItem>
)}
{app.metadata?.external_url && (
<MenuItem>
<Dropdown className={className}>
<div className="flex flex-col bg-black/50 p-2 rounded-lg">
{app.metadata?.description && (
<button
className="my-1 whitespace-nowrap clear"
onClick={() => navigate(`/app-details/${appId(app)}`)}
>
View Details
</button>
)}
{app.metadata?.external_url && (
<a
style={{
color: "inherit",
whiteSpace: "nowrap",
cursor: "pointer",
marginTop: "0.25em",
}}
target="_blank"
href={app.metadata?.external_url}
className="mb-1 whitespace-nowrap button clear"
>
View Site
</a>
</MenuItem>
)}
)}
</div>
</Dropdown>
);
}
return (
<Dropdown>
<MenuItem
className="action-entry"
onClick={() => navigate(`/app-details/${appId(app)}`)}
>
View Details
</MenuItem>
{app.installed && (
<>
<MenuItem className="action-entry" onClick={() => uninstallApp(app)}>
Uninstall
</MenuItem>
<MenuItem
className="action-entry"
onClick={() => setMirroring(app, !app.state?.mirroring)}
>
{app.state?.mirroring ? "Stop" : "Start"} Mirroring
</MenuItem>
<MenuItem
className="action-entry"
onClick={() => setAutoUpdate(app, !app.state?.auto_update)}
>
{app.state?.auto_update ? "Disable" : "Enable"} Auto Update
</MenuItem>
</>
)}
<Dropdown className={className}>
<div className="flex flex-col bg-black/50 p-2 rounded-lg">
<button
className="my-1 whitespace-nowrap clear"
onClick={() => navigate(`/app-details/${appId(app)}`)}
>
View Details
</button>
{app.installed && (
<>
<button
className="mb-1 whitespace-nowrap clear"
onClick={() => uninstallApp(app)}
>
Uninstall
</button>
<button
className="mb-1 whitespace-nowrap clear"
onClick={() => setMirroring(app, !app.state?.mirroring)}
>
{app.state?.mirroring ? "Stop" : "Start"} Mirroring
</button>
<button
className="mb-1 whitespace-nowrap clear"
onClick={() => setAutoUpdate(app, !app.state?.auto_update)}
>
{app.state?.auto_update ? "Disable" : "Enable"} Auto Update
</button>
</>
)}
</div>
</Dropdown>
);
}

View File

@ -3,12 +3,13 @@ import { useLocation, useNavigate } from "react-router-dom";
import {
FaArrowLeft,
FaDownload,
FaRegTimesCircle,
FaSearch,
FaMagnifyingGlass,
FaUpload,
} from "react-icons/fa";
FaX,
} from "react-icons/fa6";
import { MY_APPS_PATH } from "../constants/path";
import classNames from "classnames";
interface SearchHeaderProps {
value?: string;
@ -32,9 +33,9 @@ export default function SearchHeader({
const isMyAppsPage = location.pathname === MY_APPS_PATH;
return (
<div className="search-header row between">
<div className="flex justify-between">
{location.pathname !== '/' ? (
<button className="back-btn col center" onClick={() => {
<button className="flex flex-col c mr-1 icon" onClick={() => {
if (onBack) {
onBack()
} else {
@ -45,40 +46,44 @@ export default function SearchHeader({
</button>
) : (
<button
className="back-btn col center"
className="flex flex-col c mr-1 alt"
onClick={() => navigate("/publish")}
>
<FaUpload />
</button>
)}
{!hideSearch && (
<div className="searchbar row">
<FaSearch
className="search-icon"
<div className="flex mx-2 flex-1 rounded-md">
<button
className="icon"
type="button"
onClick={() => inputRef.current?.focus()}
/>
>
<FaMagnifyingGlass />
</button>
<input
type="text"
ref={inputRef}
onChange={(event) => onChange(event.target.value)}
value={value}
placeholder="Search for apps..."
className="w-full ml-2"
/>
{value.length > 0 && (
<FaRegTimesCircle
className="search-icon"
style={{ margin: "0 -0.25em 0 0.25em" }}
onClick={() => onChange("")}
/>
)}
{value.length > 0 && <button
className="icon ml-2"
onClick={() => onChange("")}
>
<FaX />
</button>}
</div>
)}
<div className="row">
<div className="flex">
<button
className={`my-pkg-btn row ${isMyAppsPage ? "selected" : ""}`}
className={classNames("flex ml-1 alt")}
onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))}
>
<FaDownload style={{ marginRight: "0.5em" }} />
My Packages
<FaDownload className="mr-1" />
My Apps
</button>
</div>
</div>

View File

@ -0,0 +1,39 @@
import { React, useState } from "react"
import classNames from 'classnames'
import { FaQuestion, FaX } from 'react-icons/fa6'
interface TooltipProps {
text: string
button?: React.ReactNode
className?: string
position?: "top" | "bottom" | "left" | "right"
}
export const Tooltip: React.FC<TooltipProps> = ({ text, button, className, position }) => {
const [showTooltip, setShowTooltip] = useState(false)
return <div className={classNames("flex place-items-center place-content-center text-sm relative cursor-pointer shrink", className)}>
<div onClick={() => setShowTooltip(!showTooltip)}>
{button || <button
className="icon ml-4"
type='button'
>
<FaQuestion />
</button>}
</div>
<div className={classNames('absolute rounded bg-black p-2 min-w-[200px] z-10',
{
"hidden": !showTooltip,
"top-8": position === "top" || !position,
"bottom-8": position === "bottom",
"right-8": position === "left",
"left-8": position === "right",
})}>
{text}
</div>
<button className={classNames("absolute bg-black icon right-0 top-0", {
"hidden": !showTooltip,
})} onClick={() => setShowTooltip(false)}>
<FaX />
</button>
</div>
}

View File

@ -2,45 +2,23 @@
@tailwind components;
@tailwind utilities;
:root {
--text-light: #FFF5D9;
--text-dark: #22211F;
--text-orange: #FF7533;
--orange-light: #F36822;
--orange-medium: #F35422;
--orange-burnt: #E25F35;
@font-face {
font-family: 'Futura';
src: url('./fonts/Futura-Heavy.ttf');
}
--medium-gray: #7E7E7E;
--gray-button: rgba(253, 245, 220, 0.25);
--dark-background: rgb(130, 59, 28);
--input-background: rgba(243, 84, 34, 0.25);
/* orange-medium */
@font-face {
font-family: 'OpenSans';
src: url('./fonts/OpenSans-CondBold.ttf');
}
@font-face {
font-family: 'Barlow';
src: url('./fonts/BarlowCondensed-Black.ttf');
}
body {
margin: 0;
font-size: 16px;
color: var(--text-light);
font-weight: 400;
background: url('./assets/background.jpg') no-repeat center center fixed;
background-size: cover;
background-color: var(--dark-background);
height: 100vh;
width: 100vw;
}
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
a,
button,
input {
font-family: 'Barlow Condensed', sans-serif;
font-family: 'Barlow', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"';
}
h1,
@ -49,174 +27,107 @@ h3,
h4,
h5,
h6 {
line-height: 1.5em;
font-weight: 500;
margin: 0;
letter-spacing: -0.01em;
@apply leading-6 m-0 font-[OpenSans]
}
h1.display {
letter-spacing: 0.25em;
text-transform: uppercase;
@apply font-[Futura] text-3xl font-normal;
}
h1 {
font-size: 64px;
@apply text-3xl;
}
h2 {
font-size: 48px;
@apply text-2xl;
}
h3 {
font-size: 36px;
@apply text-xl;
}
h4 {
font-size: 24px;
}
h5 {
font-size: 20px;
@apply text-lg;
}
h6 {
font-size: 16px;
}
.col {
display: flex;
flex-direction: column;
}
.row {
display: flex;
flex-direction: row;
align-items: center;
}
input,
button {
all: unset;
}
input[type="text"],
input[type="password"],
input[type="checkbox"] {
padding: 1em;
border: 1px solid var(--orange-medium);
border-radius: 8px;
box-sizing: border-box;
font-size: 1em;
background-color: var(--input-background);
color: var(--text-light);
text-align: left;
}
input[type="text"],
input[type="password"] {
width: 100%;
}
input[type="checkbox"] {
padding: 0.25em 0.8em;
cursor: pointer;
}
input[type="checkbox"]:checked {
background-color: var(--orange-medium);
}
.checkmark {
position: absolute;
left: 4px;
font-size: 24px;
top: -5px;
cursor: pointer;
}
::placeholder {
color: var(--text-light);
}
::-webkit-input-placeholder::placeholder {
color: var(--text-light);
}
::-moz-placeholder::placeholder {
color: var(--text-light);
}
::-ms-input-placeholder {
color: var(--text-light);
}
label {
font-size: 20px;
@apply text-sm;
}
button,
[type='button'],
[type='reset'],
[type='submit'] {
padding: 0.75em 1em;
margin: 0;
font-weight: 500;
border-width: 1px;
border-style: solid;
border-color: var(--orange-medium);
/* border-image: linear-gradient(to right, var(--orange-medium), var(--orange-light)); */
border-radius: 8px;
background: var(--orange-medium);
box-sizing: border-box;
cursor: pointer;
font-size: 1.125em;
transition: all 0.1s;
box-shadow: 0 1px 2px var(--orange-light);
color: var(--text-light);
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];
}
button.alt {
background-color: var(--text-light);
color: var(--text-dark);
border-color: var(--text-light);
box-shadow: 0 1px 2px var(--text-light);
.clear {
@apply bg-transparent border-transparent font-bold font-[Barlow] hover:bg-white/25;
}
button:hover {
opacity: 0.9;
box-shadow: none;
.alt {
@apply bg-white text-black border-white hover:text-white;
}
button:disabled {
background-color: var(--medium-gray);
border: 1px solid var(--medium-gray);
box-shadow: 0 1px 2px var(--medium-gray);
opacity: 0.7;
cursor: not-allowed;
.thin {
@apply px-0 border-none;
}
ul,
li {
.icon {
@apply flex items-center place-content-center bg-transparent w-11 p-3 text-[14px] rounded-full border-white/25;
}
.icon.alt {
@apply border-black/25 hover:border-white/25
}
body {
@apply bg-[url('./background.jpg')] bg-cover bg-no-repeat bg-center bg-fixed text-white;
}
input {
all: unset;
}
select,
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;
}
input[type="checkbox"] {
@apply w-2 h-2 cursor-pointer p-2;
}
input[type="checkbox"]:checked {
@apply bg-orange;
}
select {
padding: 0.25em 0.5em;
font-size: 0.9rem;
border: 1px solid var(--orange-medium);
background-color: var(--input-background);
color: var(--text-light);
border-radius: 8px;
/* Use a custom chevron image */
background-image: url('./assets/select-chevron.svg');
/* arrow image */
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="%23ffffff" d="M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6-6-6z"/></svg>');
background-repeat: no-repeat;
background-position: right 8px center;
/* Adjust the horizontal position to control padding */
background-size: 16px;
/* Adjust size of the chevron */
padding-right: 2em;
/* Adjust the padding to make room for the chevron */
background-position: right 1.25rem center;
@apply appearance-none pr-16;
}
-webkit-appearance: none;
/* Removes default styling on WebKit browsers like Safari */
-moz-appearance: none;
/* Removes default styling on Firefox */
appearance: none;
/* Standard property, currently not fully supported */
button:hover {
@apply opacity-90 shadow-none;
}
button:disabled {
@apply opacity-70 cursor-not-allowed bg-gray border-gray;
}
.obox,
.card {
@apply rounded-lg p-4 bg-orange/25;
}
.c {
@apply place-items-center place-content-center;
}

View File

@ -1,6 +1,8 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import '@unocss/reset/tailwind.css'
import 'uno.css'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(

View File

@ -9,7 +9,7 @@ import SearchHeader from "../components/SearchHeader";
import { PageProps } from "../types/Page";
import { appId } from "../utils/app";
interface AppPageProps extends PageProps {}
interface AppPageProps extends PageProps { }
export default function AppPage(props: AppPageProps) {
// eslint-disable-line
@ -48,64 +48,64 @@ export default function AppPage(props: AppPageProps) {
(versions[(versions.length || 1) - 1] || ["", ""])[1];
return (
<div style={{ width: "100%" }}>
<div className="flex flex-col w-full max-w-[900px]">
<SearchHeader value="" onChange={() => null} hideSearch />
<div className="card" style={{ marginTop: "1em" }}>
<div className="card mt1">
{app ? (
<>
<div className="row between">
<div className="flex justify-between">
<AppHeader app={app} size="large" />
<ActionButton app={app} style={{ marginRight: "0.5em" }} />
<ActionButton app={app} className="mr-1" />
</div>
<div className="col" style={{ marginTop: "1em" }}>
<div className="app-details row">
<div className="title">Description</div>
<div className="value">
<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="app-details row">
<div className="title">Publisher</div>
<div className="value underline">{app.publisher}</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="app-details row">
<div className="title">Version</div>
<div className="value">{version}</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="app-details row">
<div className="title">Mirrors</div>
<div className="col">
<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="value underline">
<div key={index + mirror} className="mb-1">
{mirror}
</div>
)
)}
</div>
</div>
{/* <div className="app-details row">
<div className="title">Permissions</div>
<div className="col">
{/* <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="value permission">{permission}</div>
<div key={index + permission} className="mb-1">{permission}</div>
))}
</div>
</div> */}
<div className="app-details row">
<div className="title">Hash</div>
<div className="value" style={{ wordBreak: "break-all" }}>
<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>
<div className="app-screenshots row">
<div className="app-screenshots flex mt-2 overflow-x-auto max-w-full">
{(app.metadata?.properties?.screenshots || []).map(
(screenshot, index) => (
<img key={index + screenshot} src={screenshot} />
<img key={index + screenshot} src={screenshot} className="mr-2 max-h-20 max-w-full rounded border border-black" />
)
)}
</div>

View File

@ -9,7 +9,7 @@ import { PageProps } from "../types/Page";
import { useNavigate } from "react-router-dom";
import { appId } from "../utils/app";
interface MyAppsPageProps extends PageProps {}
interface MyAppsPageProps extends PageProps { }
export default function MyAppsPage(props: MyAppsPageProps) { // eslint-disable-line
const { myApps, getMyApps } = useAppsStore()
@ -53,27 +53,25 @@ export default function MyAppsPage(props: MyAppsPageProps) { // eslint-disable-l
}, [myApps]);
return (
<div style={{ width: "100%", height: '100%' }}>
<div className="flex flex-col w-full max-w-[900px]">
<SearchHeader value={searchQuery} onChange={searchMyApps} />
<div className="row between page-title">
<h4 style={{ marginBottom: "0.5em" }}>My Packages</h4>
<button className="row" onClick={() => navigate('/publish')}>
<FaUpload style={{ marginRight: "0.5em" }} />
<div className="flex justify-between items-center mt-2">
<h4 className="mb-2">My Packages</h4>
<button onClick={() => navigate('/publish')}>
<FaUpload className="mr-2" />
Publish Package
</button>
</div>
<div className="my-apps-list">
<div className="new card col" style={{ gap: "1em" }}>
<h4>Downloaded</h4>
{(displayedApps.downloaded || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
<h4>Installed</h4>
{(displayedApps.installed || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
<h4>Local</h4>
{(displayedApps.local || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
<h4>System</h4>
{(displayedApps.system || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
</div>
<div className="flex flex-col card gap-2 mt-2">
<h4>Downloaded</h4>
{(displayedApps.downloaded || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
<h4>Installed</h4>
{(displayedApps.installed || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
<h4>Local</h4>
{(displayedApps.local || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
<h4>System</h4>
{(displayedApps.system || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
</div>
</div>
);

View File

@ -14,10 +14,12 @@ import useAppsStore from "../store/apps-store";
import MetadataForm from "../components/MetadataForm";
import { AppInfo } from "../types/Apps";
import Checkbox from "../components/Checkbox";
import Jazzicon from "../components/Jazzicon";
import { Tooltip } from "../components/Tooltip";
const { useIsActivating } = hooks;
interface PublishPageProps extends PageProps {}
interface PublishPageProps extends PageProps { }
export default function PublishPage({
provider,
@ -53,7 +55,7 @@ export default function PublishPage({
}, [state])
const connectWallet = useCallback(async () => {
await metaMask.activate().catch(() => {});
await metaMask.activate().catch(() => { });
try {
setChain(SEPOLIA_OPT_HEX);
@ -104,21 +106,21 @@ export default function PublishPage({
const tx = await (isUpdate
? packageAbi.updateMetadata(
BigNumber.from(
utils.solidityKeccak256(
["string", "bytes"],
[packageName, publisherIdDnsWireFormat]
)
),
metadataUrl,
metadata
)
BigNumber.from(
utils.solidityKeccak256(
["string", "bytes"],
[packageName, publisherIdDnsWireFormat]
)
),
metadataUrl,
metadata
)
: packageAbi.registerApp(
packageName,
publisherIdDnsWireFormat,
metadataUrl,
metadata
));
packageName,
publisherIdDnsWireFormat,
metadataUrl,
metadata
));
await new Promise((resolve) => setTimeout(resolve, 2000));
@ -170,42 +172,39 @@ export default function PublishPage({
}, [listedApps, packageName, publisherId, isUpdate, setIsUpdate]);
return (
<div style={{ width: "100%" }}>
<div className="max-w-[900px] w-full">
<SearchHeader hideSearch onBack={showMetadataForm ? () => setShowMetadataForm(false) : undefined} />
<div className="row between page-title">
<div className="flex justify-between items-center my-2">
<h4>Publish Package</h4>
{Boolean(account) && (
<div style={{ textAlign: "right", lineHeight: 1.5 }}>
{" "}
Connected as{" "}
{account?.slice(0, 6) + "..." + account?.slice(account.length - 6)}
</div>
)}
{Boolean(account) && <div className="card flex items-center">
<span>Publishing as:</span>
<Jazzicon address={account!} className="mx-2" />
<span className="font-mono">{account?.slice(0, 4)}...{account?.slice(-4)}</span>
</div>}
</div>
{loading ? (
<div className="col center">
<div className="flex flex-col items-center">
<Loader msg={loading} />
</div>
) : publishSuccess ? (
<div className="col center">
<h4 style={{ marginBottom: "0.5em" }}>Package Published!</h4>
<div style={{ marginBottom: "0.5em" }}>
<div className="flex flex-col items-center">
<h4 className="mb-2">Package Published!</h4>
<div className="mb-2">
<strong>Package Name:</strong> {publishSuccess.packageName}
</div>
<div style={{ marginBottom: "0.5em" }}>
<div className="mb-2">
<strong>Publisher ID:</strong> {publishSuccess.publisherId}
</div>
<button
className={`my-pkg-btn row`}
style={{ marginTop: "1em" }}
className={`flex ml-2 mt-2`}
onClick={() => setPublishSuccess(undefined)}
>
Publish Another Package
</button>
</div>
) : showMetadataForm ? (
<MetadataForm {...{packageName, publisherId, app: state?.app}} goBack={() => setShowMetadataForm(false)} />
<MetadataForm {...{ packageName, publisherId, app: state?.app }} goBack={() => setShowMetadataForm(false)} />
) : !account || !isActive ? (
<>
<h4 style={{}}>Please connect your wallet to publish a package</h4>
@ -217,28 +216,23 @@ export default function PublishPage({
<Loader msg="Approve connection in your wallet" />
) : (
<form
className="new card col"
style={{ flex: 1, overflowY: "scroll" }}
className="flex flex-col flex-1 overflow-y-auto"
onSubmit={publishPackage}
>
<div
className="row between"
style={{
cursor: "pointer",
padding: "0.5em",
margin: "0 0 0 -0.5em",
}}
className="flex cursor-pointer p-2 -mb-2"
onClick={() => setIsUpdate(!isUpdate)}
>
<Checkbox checked={isUpdate} readOnly />
<label htmlFor="update" style={{ cursor: "pointer", marginLeft: 8 }}>
<Checkbox
checked={isUpdate} readOnly
/>
<label htmlFor="update" className="cursor-pointer ml-4">
Update existing package
</label>
</div>
<div className="col f-width">
<div className="flex flex-col mb-2">
<label htmlFor="package-name">Package Name</label>
<input
style={{ minWidth: "80%" }}
id="package-name"
type="text"
required
@ -248,10 +242,9 @@ export default function PublishPage({
onBlur={checkIfUpdate}
/>
</div>
<div className="col f-width">
<div className="flex flex-col mb-2">
<label htmlFor="publisher-id">Publisher ID</label>
<input
style={{ minWidth: "80%" }}
id="publisher-id"
type="text"
required
@ -260,12 +253,11 @@ export default function PublishPage({
onBlur={checkIfUpdate}
/>
</div>
<div className="col f-width">
<div className="flex flex-col mb-2">
<label htmlFor="metadata-url">
Metadata URL
</label>
<input
style={{ minWidth: "80%" }}
id="metadata-url"
type="text"
required
@ -274,19 +266,20 @@ export default function PublishPage({
onBlur={calculateMetadataHash}
placeholder="https://github/my-org/my-repo/metadata.json"
/>
<div style={{ textAlign: "left", margin: "0.5em 0 0" }}>
Metadata is a JSON file that describes your package.
<br /> You can{" "}
<a onClick={() => setShowMetadataForm(true)} style={{ cursor: "pointer", textDecoration: "underline" }}>
fill out a template here
</a>
.
</div>
<div className="mt-2">
Metadata is a JSON file that describes your package.
<br /> You can{" "}
<a onClick={() => setShowMetadataForm(true)}
className="underline cursor-pointer"
>
fill out a template here
</a>
.
</div>
</div>
<div className="col f-width">
<div className="flex flex-col mb-2">
<label htmlFor="metadata-hash">Metadata Hash</label>
<input
style={{ minWidth: "80%" }}
readOnly
id="metadata-hash"
type="text"
@ -295,7 +288,7 @@ export default function PublishPage({
placeholder="Calculated automatically from metadata URL"
/>
</div>
<button type="submit" className="primary">
<button type="submit">
Publish
</button>
</form>

View File

@ -7,6 +7,7 @@ import AppEntry from "../components/AppEntry";
import SearchHeader from "../components/SearchHeader";
import { PageProps } from "../types/Page";
import { appId } from "../utils/app";
import classNames from 'classnames';
interface StorePageProps extends PageProps { }
@ -108,7 +109,7 @@ export default function StorePage(props: StorePageProps) {
);
return (
<div style={{ width: "100%" }}>
<div className="max-w-[900px] w-full">
{/* <div style={{ position: "absolute", top: 4, left: 8 }}>
ID: <strong>{window.our?.node}</strong>
</div> */}
@ -134,7 +135,7 @@ export default function StorePage(props: StorePageProps) {
</div>
))}
</div> */}
<div className="row between page-title">
<div className="flex justify-between items-center my-2 mx-0">
<h4>New</h4>
<select
@ -150,7 +151,7 @@ export default function StorePage(props: StorePageProps) {
<option>Recently updated</option>
</select>
</div>
<div className="new card col" style={{ flex: 1, overflowY: "auto", gap: "1em" }}>
<div className="flex flex-col flex-1 overflow-y-auto gap-2">
{displayedApps.map((app) => (
<AppEntry
key={appId(app) + (app.state?.our_version || "")}
@ -158,14 +159,14 @@ export default function StorePage(props: StorePageProps) {
/>
))}
{pages.length > 1 && (
<div className="row" style={{ alignSelf: "center" }}>
<div className="flex self-center">
{page !== pages[0] && (
<FaChevronLeft onClick={() => setPage(page - 1)} />
)}
{pages.map((p) => (
<div
key={`page-${p}`}
className={`page-selector ${p === page ? "selected" : ""}`}
className={classNames('my-1 mx-2', { "font-bold": p === page })}
onClick={() => setPage(p)}
>
{p}

View File

@ -1,5 +1,10 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import UnoCSS from 'unocss/vite'
import { transformerDirectives } from 'unocss'
import presetIcons from '@unocss/preset-icons'
import presetUno from '@unocss/preset-uno'
import presetWind from '@unocss/preset-wind'
/*
If you are developing a UI outside of a Kinode project,
@ -22,7 +27,39 @@ const PROXY_URL = (process.env.VITE_NODE_URL || 'http://127.0.0.1:8080').replace
console.log('process.env.VITE_NODE_URL', process.env.VITE_NODE_URL, PROXY_URL);
export default defineConfig({
plugins: [react()],
plugins: [
UnoCSS({
presets: [presetUno(), presetWind(), presetIcons()],
shortcuts: [
{
'flex-center': 'flex justify-center items-center',
'flex-col-center': 'flex flex-col justify-center items-center',
},
],
rules: [
],
theme: {
colors: {
'white': '#FFF5D9',
'black': '#22211F',
'orange': '#F35422',
'transparent': 'transparent',
'gray': '#7E7E7E',
},
font: {
'sans': ['Barlow', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"'],
'serif': ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
'mono': ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace'],
'heading': ['OpenSans', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"'],
'display': ['Futura', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"'],
},
},
transformers: [
transformerDirectives()
],
}),
react(),
],
base: BASE_URL,
build: {
rollupOptions: {

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -19,5 +19,4 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
src/abis/types/*
src/abis/types/*

View File

@ -1,7 +1,7 @@
{
"files": {
"main.css": "/static/css/main.6c087b1c.css",
"main.js": "/static/js/main.48b36a2c.js",
"main.css": "/static/css/main.eb8419e6.css",
"main.js": "/static/js/main.94cc3f8a.js",
"static/media/OpenSans-CondBold.ttf": "/static/media/OpenSans-CondBold.6293057f8484b6c0da03.ttf",
"static/media/BarlowCondensed-Black.ttf": "/static/media/BarlowCondensed-Black.3ba02bbdeb04e17f34bf.ttf",
"static/media/Futura-Heavy.ttf": "/static/media/Futura-Heavy.af72c25a6945b0f48abb.ttf",
@ -11,7 +11,7 @@
"static/media/kinode.svg": "/static/media/kinode.6b178bc9164b31d90099844a82d04497.svg"
},
"entrypoints": [
"static/css/main.6c087b1c.css",
"static/js/main.48b36a2c.js"
"static/css/main.eb8419e6.css",
"static/js/main.94cc3f8a.js"
]
}

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><title>Welcome - Kinode</title><meta charset="utf-8"/><meta http-equiv="pragma" content="no-cache"/><meta http-equiv="cache-control" content="no-cache"/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"><link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg=="><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 defer="defer" src="/static/js/main.48b36a2c.js"></script><link href="/static/css/main.6c087b1c.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><title>Welcome - Kinode</title><meta charset="utf-8"/><meta http-equiv="pragma" content="no-cache"/><meta http-equiv="cache-control" content="no-cache"/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"><link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg=="><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 defer="defer" src="/static/js/main.94cc3f8a.js"></script><link href="/static/css/main.eb8419e6.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,143 @@
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
Copyright (c) 2015 Jed Watson.
Based on code that is Copyright 2013-2015, Facebook, Inc.
All rights reserved.
*/
/*!
* Adapted from jQuery UI core
*
* http://jqueryui.com
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/category/ui-core/
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* use-sync-external-store-shim.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* use-sync-external-store-shim/with-selector.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @remix-run/router v1.15.3
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* React Router DOM v6.22.3
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* React Router v6.22.3
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* [js-sha3]{@link https://github.com/emn178/js-sha3}
*
* @version 0.5.7
* @author Chen, Yi-Cyuan [emn178@gmail.com]
* @copyright Chen, Yi-Cyuan 2015-2016
* @license MIT
*/
/**
* [js-sha3]{@link https://github.com/emn178/js-sha3}
*
* @version 0.8.0
* @author Chen, Yi-Cyuan [emn178@gmail.com]
* @copyright Chen, Yi-Cyuan 2015-2018
* @license MIT
*/

View File

@ -34,6 +34,7 @@
"jazzicon": "^1.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-modal": "^3.16.1",
"react-router-dom": "^6.16.0",
"react-scripts": "5.0.1",
@ -18660,6 +18661,14 @@
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==",
"license": "MIT"
},
"node_modules/react-icons": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz",
"integrity": "sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",

View File

@ -84,7 +84,7 @@ function ChainInfo({
</button>
<button
onClick={changeToNodeChain}
className="clear max-w-1/3"
className="clear max-w-1/3 z-10"
>
{generateNetworkIcon(networkName)}
<div className='ml-2'>

View File

@ -79,7 +79,7 @@ function KinodeHeader({
nodeChainId === OPTIMISM_OPT_HEX) && (
<Tooltip
position="left"
className="!absolute top-8 right-8"
className="!absolute top-8 right-8 z-10"
button={nodeChainId === SEPOLIA_OPT_HEX ? (
<img
alt="sepolia"
@ -105,7 +105,7 @@ function KinodeHeader({
</div>
{!hideConnect && (
<div
className="flex c max-w-[50vw] mb-8 absolute top-2 left-2"
className="flex c w-[99vw] mb-8 absolute top-2 left-2"
>
{isActive && account ? (
<ChainInfo

View File

@ -702,54 +702,38 @@ button:hover {
position: relative;
}
.bottom-2 {
bottom: 0.5rem;
}
.bottom-8 {
bottom: 2rem;
}
.left-8 {
left: 2rem;
}
.right-1 {
right: 0.25rem;
}
.right-8 {
right: 2rem;
}
.top-1 {
top: 0.25rem;
}
.top-8 {
top: 2rem;
}
.right-0 {
right: 0px;
}
.top-0 {
top: 0px;
}
.left-0 {
left: 0px;
}
.left-2 {
left: 0.5rem;
}
.left-8 {
left: 2rem;
}
.right-0 {
right: 0px;
}
.right-8 {
right: 2rem;
}
.top-0 {
top: 0px;
}
.top-2 {
top: 0.5rem;
}
.top-8 {
top: 2rem;
}
.z-10 {
z-index: 10;
}
@ -774,11 +758,6 @@ button:hover {
margin-bottom: 2rem;
}
.my-10 {
margin-top: 2.5rem;
margin-bottom: 2.5rem;
}
.mb-1 {
margin-bottom: 0.25rem;
}
@ -815,6 +794,10 @@ button:hover {
margin-top: 0.25rem;
}
.mt-10 {
margin-top: 2.5rem;
}
.mt-2 {
margin-top: 0.5rem;
}
@ -827,10 +810,6 @@ button:hover {
margin-top: 2rem;
}
.mt-10 {
margin-top: 2.5rem;
}
.block {
display: block;
}
@ -843,20 +822,20 @@ button:hover {
display: none;
}
.h-16 {
height: 4rem;
.h-32 {
height: 8rem;
}
.h-screen {
height: 100vh;
}
.h-32 {
height: 8rem;
.w-32 {
width: 8rem;
}
.w-16 {
width: 4rem;
.w-\[95vw\] {
width: 95vw;
}
.w-full {
@ -867,8 +846,8 @@ button:hover {
width: 100vw;
}
.w-32 {
width: 8rem;
.w-\[99vw\] {
width: 99vw;
}
.min-w-\[200px\] {
@ -883,8 +862,8 @@ button:hover {
max-width: 460px;
}
.max-w-\[50vw\] {
max-width: 50vw;
.max-w-\[450vw\] {
max-width: 450vw;
}
.shrink {
@ -957,15 +936,11 @@ button:hover {
line-height: 2rem;
}
.text-6xl {
font-size: 3.75rem;
.text-5xl {
font-size: 3rem;
line-height: 1;
}
.text-\[8px\] {
font-size: 8px;
}
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
@ -976,16 +951,6 @@ button:hover {
line-height: 1.25rem;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}
.text-5xl {
font-size: 3rem;
line-height: 1;
}
.text-xs {
font-size: 0.75rem;
line-height: 1rem;

View File

@ -5,6 +5,7 @@ import { utils, providers } from "ethers";
import { downloadKeyfile } from "../utils/download-keyfile";
import { ReactComponent as NameLogo } from "../assets/kinode.svg"
import { Tooltip } from "../components/Tooltip";
import { KinodeTitle } from "../components/KinodeTitle";
type SetPasswordProps = {
direct: boolean;
@ -113,11 +114,7 @@ function SetPassword({
return (
<>
<KinodeHeader
header={<h1
className="flex place-content-center place-items-center mb-4"
>
Set Password
</h1>}
header={<KinodeTitle prefix="Set Password" showLogo />}
openConnect={() => { }}
closeConnect={closeConnect}
nodeChainId={nodeChainId}
@ -128,7 +125,7 @@ function SetPassword({
<form id="signup-form" className="flex flex-col w-full max-w-[450px] gap-4" onSubmit={handleSubmit}>
<div className="flex flex-col w-full place-items-center place-content-center">
<div className="flex w-full place-items-center mb-2">
<label className="flex leading-6 place-items-center mt-2 cursor-pointer mb-2" style={{fontSize: 20}} htmlFor="password">New Password</label>
<label className="flex leading-6 place-items-center mt-2 cursor-pointer mb-2" style={{ fontSize: 20 }} htmlFor="password">New Password</label>
<Tooltip text={`This password will be used to log in if you restart your node or switch browsers.`} />
</div>
<div className="flex w-full place-items-center">
@ -148,7 +145,7 @@ function SetPassword({
</div>
<div className="flex flex-col w-full place-items-center place-content-center">
<div className="flex w-full place-items-center">
<label className="flex leading-6 place-items-center mt-2 cursor-pointer mb-4" style={{fontSize: 20}} htmlFor="confirm-password">Confirm Password</label>
<label className="flex leading-6 place-items-center mt-2 cursor-pointer mb-4" style={{ fontSize: 20 }} htmlFor="confirm-password">Confirm Password</label>
</div>
<div className="flex w-full place-items-center">
<input

File diff suppressed because it is too large Load Diff