Add settings and peers integration and css nesting

This commit is contained in:
Arnaud 2024-10-30 19:29:14 +01:00
parent f2e3ed6958
commit bc1da83d03
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
66 changed files with 2219 additions and 1924 deletions

193
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "0.0.7", "version": "0.0.7",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codex-storage/marketplace-ui-components": "^0.0.27", "@codex-storage/marketplace-ui-components": "^0.0.28",
"@codex-storage/sdk-js": "^0.0.12", "@codex-storage/sdk-js": "^0.0.12",
"@sentry/browser": "^8.32.0", "@sentry/browser": "^8.32.0",
"@sentry/react": "^8.31.0", "@sentry/react": "^8.31.0",
@ -17,6 +17,7 @@
"@tanstack/react-router": "^1.58.7", "@tanstack/react-router": "^1.58.7",
"dotted-map": "^2.2.3", "dotted-map": "^2.2.3",
"echarts": "^5.5.1", "echarts": "^5.5.1",
"emoji-picker-react": "^4.12.0",
"idb-keyval": "^6.2.1", "idb-keyval": "^6.2.1",
"lucide-react": "^0.445.0", "lucide-react": "^0.445.0",
"react": "^18.3.1", "react": "^18.3.1",
@ -36,8 +37,9 @@
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.12", "eslint-plugin-react-refresh": "^0.4.12",
"postcss": "^8.4.47",
"postcss-nesting": "^13.0.1",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"sass-embedded": "^1.79.4",
"typescript": "5.5.4", "typescript": "5.5.4",
"vite": "^5.4.7" "vite": "^5.4.7"
}, },
@ -371,12 +373,14 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.1.0.tgz",
"integrity": "sha512-+2Mx67Y3skJ4NCD/qNSdBJNWtu6x6Qr53jeNg+QcwiL6mt0wK+3jwHH2x1p7xaYH6Ve2JKOVn0OxU35WsmqI9A==", "integrity": "sha512-+2Mx67Y3skJ4NCD/qNSdBJNWtu6x6Qr53jeNg+QcwiL6mt0wK+3jwHH2x1p7xaYH6Ve2JKOVn0OxU35WsmqI9A==",
"dev": true "dev": true,
"optional": true,
"peer": true
}, },
"node_modules/@codex-storage/marketplace-ui-components": { "node_modules/@codex-storage/marketplace-ui-components": {
"version": "0.0.27", "version": "0.0.28",
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.27.tgz", "resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.28.tgz",
"integrity": "sha512-jCNUFeHNUptKSupSCQriwTXjK0zC8Yi7kcVWI20p9GtfNKPMRykhrpqGgQ6AdMMB9ZAliY5m+PH9ie3J/PWicw==", "integrity": "sha512-C2Wj6Rb4RzdkXRtXsj+dnzP5NwnJbmlHfQ1R2jxqgaJ4DXBDnAnEGiiljHzHE2oP0P4Exx7CQBjrP7vWChMpNg==",
"dependencies": { "dependencies": {
"lucide-react": "^0.453.0" "lucide-react": "^0.453.0"
}, },
@ -385,6 +389,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@codex-storage/sdk-js": ">=0.0.12", "@codex-storage/sdk-js": ">=0.0.12",
"postcss-nesting": "^13.0.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1"
} }
@ -408,6 +413,48 @@
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@csstools/selector-resolve-nested": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz",
"integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss-selector-parser": "^7.0.0"
}
},
"node_modules/@csstools/selector-specificity": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz",
"integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss-selector-parser": "^7.0.0"
}
},
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0", "version": "4.4.0",
"dev": true, "dev": true,
@ -2101,7 +2148,9 @@
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
"integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
"dev": true "dev": true,
"optional": true,
"peer": true
}, },
"node_modules/callsites": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
@ -2209,7 +2258,9 @@
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
"dev": true "dev": true,
"optional": true,
"peer": true
}, },
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
@ -2234,6 +2285,17 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"dev": true, "dev": true,
@ -2312,6 +2374,20 @@
"integrity": "sha512-VbeVexmZ1IFh+5EfrYz1I0HTzHVIlJa112UEWhciPyeOcKJGeTv6N8WnG4wsQB81DGCaVEGhpSb6o6a8WYFXXg==", "integrity": "sha512-VbeVexmZ1IFh+5EfrYz1I0HTzHVIlJa112UEWhciPyeOcKJGeTv6N8WnG4wsQB81DGCaVEGhpSb6o6a8WYFXXg==",
"dev": true "dev": true
}, },
"node_modules/emoji-picker-react": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.12.0.tgz",
"integrity": "sha512-q2c8UcZH0eRIMj41bj0k1akTjk69tsu+E7EzkW7giN66iltF6H9LQvQvw6ugscsxdC+1lmt3WZpQkkY65J95tg==",
"dependencies": {
"flairup": "1.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16"
}
},
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@ -2680,6 +2756,11 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/flairup": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/flairup/-/flairup-1.0.0.tgz",
"integrity": "sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA=="
},
"node_modules/flat-cache": { "node_modules/flat-cache": {
"version": "3.2.0", "version": "3.2.0",
"dev": true, "dev": true,
@ -2828,7 +2909,9 @@
"version": "4.3.7", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
"integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
"dev": true "dev": true,
"optional": true,
"peer": true
}, },
"node_modules/import-fresh": { "node_modules/import-fresh": {
"version": "3.3.0", "version": "3.3.0",
@ -3086,7 +3169,6 @@
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.7", "version": "3.3.7",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -3209,7 +3291,6 @@
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.0", "version": "1.1.0",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
@ -3254,8 +3335,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.45", "version": "8.4.47",
"dev": true, "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -3270,16 +3352,53 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
"picocolors": "^1.0.1", "picocolors": "^1.1.0",
"source-map-js": "^1.2.0" "source-map-js": "^1.2.1"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postcss-nesting": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz",
"integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"dependencies": {
"@csstools/selector-resolve-nested": "^3.0.0",
"@csstools/selector-specificity": "^5.0.0",
"postcss-selector-parser": "^7.0.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss": "^8.4"
}
},
"node_modules/postcss-selector-parser": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz",
"integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"dev": true, "dev": true,
@ -3497,6 +3616,8 @@
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true, "dev": true,
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
@ -3506,6 +3627,8 @@
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.80.3.tgz", "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.80.3.tgz",
"integrity": "sha512-aTxTl4ToSAWg7ILFgAe+kMenj+zNlwHmHK/ZNPrOM8+HTef1Q6zuxolptYLijmHdZHKSMOkWYHgo5MMN6+GIyg==", "integrity": "sha512-aTxTl4ToSAWg7ILFgAe+kMenj+zNlwHmHK/ZNPrOM8+HTef1Q6zuxolptYLijmHdZHKSMOkWYHgo5MMN6+GIyg==",
"dev": true, "dev": true,
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@bufbuild/protobuf": "^2.0.0", "@bufbuild/protobuf": "^2.0.0",
"buffer-builder": "^0.2.0", "buffer-builder": "^0.2.0",
@ -3556,6 +3679,7 @@
"os": [ "os": [
"android" "android"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3572,6 +3696,7 @@
"os": [ "os": [
"android" "android"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3588,6 +3713,7 @@
"os": [ "os": [
"android" "android"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3604,6 +3730,7 @@
"os": [ "os": [
"android" "android"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3620,6 +3747,7 @@
"os": [ "os": [
"android" "android"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3636,6 +3764,7 @@
"os": [ "os": [
"darwin" "darwin"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3652,6 +3781,7 @@
"os": [ "os": [
"darwin" "darwin"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3668,6 +3798,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3684,6 +3815,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3700,6 +3832,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3716,6 +3849,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3732,6 +3866,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3748,6 +3883,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3764,6 +3900,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3780,6 +3917,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3796,6 +3934,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3812,6 +3951,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3828,6 +3968,7 @@
"os": [ "os": [
"win32" "win32"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3844,6 +3985,7 @@
"os": [ "os": [
"win32" "win32"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3860,6 +4002,7 @@
"os": [ "os": [
"win32" "win32"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
} }
@ -3869,6 +4012,8 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true, "dev": true,
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -3878,6 +4023,8 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true, "dev": true,
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
}, },
@ -3925,7 +4072,6 @@
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -4013,7 +4159,9 @@
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"dev": true "dev": true,
"optional": true,
"peer": true
}, },
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
@ -4122,6 +4270,11 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
} }
}, },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/valibot": { "node_modules/valibot": {
"version": "0.32.0", "version": "0.32.0",
"resolved": "https://registry.npmjs.org/valibot/-/valibot-0.32.0.tgz", "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.32.0.tgz",
@ -4131,7 +4284,9 @@
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
"dev": true "dev": true,
"optional": true,
"peer": true
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.9", "version": "5.4.9",

View File

@ -24,14 +24,15 @@
"React" "React"
], ],
"dependencies": { "dependencies": {
"@codex-storage/marketplace-ui-components": "^0.0.27", "@codex-storage/marketplace-ui-components": "^0.0.28",
"@codex-storage/sdk-js": "^0.0.12", "@codex-storage/sdk-js": "^0.0.12",
"@sentry/browser": "^8.32.0", "@sentry/browser": "^8.32.0",
"@sentry/react": "^8.31.0", "@sentry/react": "^8.31.0",
"@tanstack/react-query": "^5.51.15", "@tanstack/react-query": "^5.51.15",
"@tanstack/react-router": "^1.58.7", "@tanstack/react-router": "^1.58.7",
"echarts": "^5.5.1",
"dotted-map": "^2.2.3", "dotted-map": "^2.2.3",
"echarts": "^5.5.1",
"emoji-picker-react": "^4.12.0",
"idb-keyval": "^6.2.1", "idb-keyval": "^6.2.1",
"lucide-react": "^0.445.0", "lucide-react": "^0.445.0",
"react": "^18.3.1", "react": "^18.3.1",
@ -51,8 +52,9 @@
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.12", "eslint-plugin-react-refresh": "^0.4.12",
"postcss": "^8.4.47",
"postcss-nesting": "^13.0.1",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"sass-embedded": "^1.79.4",
"typescript": "5.5.4", "typescript": "5.5.4",
"vite": "^5.4.7" "vite": "^5.4.7"
}, },

6
postcss.config.json Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
'postcss-nesting': { /* plugin options */ },
},
}

View File

@ -0,0 +1,12 @@
<span>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M8 15.5C3.85775 15.5 0.5 12.1423 0.5 8C0.5 3.85775 3.85775 0.5 8 0.5C12.1423 0.5 15.5 3.85775 15.5 8C15.5 12.1423 12.1423 15.5 8 15.5ZM8 6.9395L5.879 4.81775L4.81775 5.879L6.9395 8L4.81775 10.121L5.879 11.1823L8 9.0605L10.121 11.1823L11.1823 10.121L9.0605 8L11.1823 5.879L10.121 4.81775L8 6.9395Z"
fill="#FB3748" />
</svg>
</span>

View File

@ -1,7 +1,13 @@
import "./appBar.css"; import "./appBar.css";
import { DashboardIcon } from "../DashboardIcon/DashboardIcon"; import { DashboardIcon } from "../DashboardIcon/DashboardIcon";
import { NodeIndicator } from "../NodeIndicator/NodeIndicator"; import { classnames } from "../../utils/classnames";
import { HttpNetworkIndicator } from "../HttpNetworkIndicator/HttpNetworkIndicator"; import { useNetwork } from "../../network/useNetwork";
import { NetworkFlashIcon } from "../NetworkFlashIcon/NetworkFlashIcon";
import { useQueryClient } from "@tanstack/react-query";
import { useEffect } from "react";
import { useCodexConnection } from "../../hooks/useCodexConnection";
import { NodesIcon } from "../Menu/NodesIcon";
import { usePersistence } from "../../hooks/usePersistence";
type Props = { type Props = {
/** /**
@ -11,29 +17,53 @@ type Props = {
onExpand: () => void; onExpand: () => void;
}; };
export function AppBar(props: Props) { export function AppBar(_: Props) {
console.info(props); console.debug(_);
const online = useNetwork();
const queryClient = useQueryClient();
const codex = useCodexConnection();
const persistence = usePersistence(codex.enabled);
useEffect(() => {
queryClient.invalidateQueries({
type: "active",
refetchType: "all",
});
}, [queryClient, codex.enabled]);
const offline = !online || !codex.enabled;
return ( return (
<div className="appBar"> <div
<div className="appBar-left"> className={classnames(
["app-bar"],
["app-bar--offline", offline],
["app-bar--no-persistence", !persistence.enabled]
)}>
<div className="row gap">
{/* <a className="appBar-burger" onClick={onExpand}> {/* <a className="appBar-burger" onClick={onExpand}>
<Menu size={"1.25rem"} /> <Menu size={"1.25rem"} />
</a> */} </a> */}
<div className="appBar-icon"> <span>
<DashboardIcon /> <DashboardIcon />
</div> </span>
<div className="appBar-textContainer">
<div className="appBar-title">Dashboard</div> <div>
<div className="appBar-subtitle"> <h1>Dashboard</h1>
Get Overview of your Codex Vault <h2>Get Overview of your Codex Vault</h2>
</div> </div>
</div> </div>
<aside className="row gap">
<div className="row gap">
<NetworkFlashIcon />
<span>Network</span>
</div> </div>
<div className="appBar-right"> <div className="row gap">
<HttpNetworkIndicator /> <NodesIcon variant={codex.enabled ? "success" : "failure"} />
<NodeIndicator /> <span>Node</span>
</div> </div>
</aside>
</div> </div>
); );
} }

View File

@ -1,30 +1,50 @@
.appBar { .app-bar {
height: 80px; height: 80px;
justify-content: space-between; justify-content: space-between;
border-bottom: 1px solid var(--codex-border-color); border-bottom: 1px solid var(--codex-border-color);
view-transition-name: main-header;
display: flex; display: flex;
padding: 20px 40px 20px 40px; padding-right: 48px;
border-bottom: 1px solid #2b303b; padding-left: 48px;
padding-top: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #96969633;
box-sizing: border-box; box-sizing: border-box;
background-color: #1c1c1c; background-color: #1c1c1c;
} border-right: 12px solid transparent;
position: sticky;
top: 0;
z-index: 1;
.appBar-burger { &:not(.app-bar--offline):not(.app-bar--no-persistence) {
cursor: pointer; border-right-color: #6ccc93;
color: var(--codex-color); }
display: flex;
}
.appBar, &.app-bar--offline {
.appBar-left, border-right-color: var(--codex-input-color-error);
.appBar-right { }
display: flex;
align-items: center;
gap: 16px;
}
.appBar-icon { &.app-bar--no-persistence {
border-right-color: rgb(var(--codex-color-warning));
}
h1 {
font:
500 18px/24px "Inter",
sans-serif;
letter-spacing: -0.015em;
color: white;
}
h2 {
font:
400 14px/20px "Inter",
sans-serif;
letter-spacing: -0.006em;
color: #969696cc;
}
> div {
span {
background: #141414; background: #141414;
height: 48px; height: 48px;
width: 48px; width: 48px;
@ -33,28 +53,28 @@
justify-content: center; justify-content: center;
border: 1px solid #353639; border: 1px solid #353639;
border-radius: 50%; border-radius: 50%;
} }
}
.appBar-title { aside {
font-family: Inter; svg {
font-size: 18px; padding: 10px;
font-weight: 500; background-color: #141414;
line-height: 24px; border-radius: var(--codex-border-radius);
letter-spacing: -0.015em; }
color: white;
}
.appBar-subtitle { span {
font-family: Inter; font:
font-size: 14px; 500 14px/20px "Inter",
font-weight: 400; sans-serif;
line-height: 20px;
letter-spacing: -0.006em; letter-spacing: -0.006em;
color: #969696cc; color: #8d8d8d;
}
}
} }
@media (min-width: 1000px) { /* @media (min-width: 1000px) {
.appBar-burger { .appBar-burger {
display: none; display: none;
} }
} } */

View File

@ -1,8 +1,4 @@
import { import { Cell, Row } from "@codex-storage/marketplace-ui-components";
Cell,
Row,
SimpleText,
} from "@codex-storage/marketplace-ui-components";
import { PrettyBytes } from "../../utils/bytes"; import { PrettyBytes } from "../../utils/bytes";
import "./AvailabilityDiskRow.css"; import "./AvailabilityDiskRow.css";
import { classnames } from "../../utils/classnames"; import { classnames } from "../../utils/classnames";
@ -26,9 +22,9 @@ export function AvailabilityDiskRow({ bytes }: Props) {
<div> <div>
<b>Node</b> <b>Node</b>
</div> </div>
<SimpleText size="small" variant="light"> <small className="text--light">
{PrettyBytes(bytes)} allocated for the node {PrettyBytes(bytes)} allocated for the node
</SimpleText> </small>
</div> </div>
</div> </div>
</Cell>, </Cell>,

View File

@ -1,6 +1,6 @@
import "./AvailabilityIdCell.css"; import "./AvailabilityIdCell.css";
import { Strings } from "../../utils/strings"; import { Strings } from "../../utils/strings";
import { Cell, SimpleText } from "@codex-storage/marketplace-ui-components"; import { Cell } from "@codex-storage/marketplace-ui-components";
import { PrettyBytes } from "../../utils/bytes"; import { PrettyBytes } from "../../utils/bytes";
import { availabilityColors } from "./availability.colors"; import { availabilityColors } from "./availability.colors";
import { AvailabilityWithSlots } from "./types"; import { AvailabilityWithSlots } from "./types";
@ -19,18 +19,13 @@ export function AvailabilityIdCell({ value, index }: Props) {
<div> <div>
<b>{value.name || Strings.shortId(value.id)}</b> <b>{value.name || Strings.shortId(value.id)}</b>
</div> </div>
<SimpleText size="small" variant="light"> <small className="text--light">
{PrettyBytes(value.totalSize)} allocated for the availability {PrettyBytes(value.totalSize)} allocated for the availability
</SimpleText> </small>
<div> <div>
{/* <div> <small className="text--light">
<SimpleText size="small" variant="light">
{a.id}
</SimpleText>
</div> */}
<SimpleText size="small" variant="light">
Max collateral {value.maxCollateral} | Min price {value.minPrice} Max collateral {value.maxCollateral} | Min price {value.minPrice}
</SimpleText> </small>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,8 +1,4 @@
import { import { Cell, Row } from "@codex-storage/marketplace-ui-components";
Cell,
Row,
SimpleText,
} from "@codex-storage/marketplace-ui-components";
import { PrettyBytes } from "../../utils/bytes"; import { PrettyBytes } from "../../utils/bytes";
import "./AvailabilitySlotRow.css"; import "./AvailabilitySlotRow.css";
import { classnames } from "../../utils/classnames"; import { classnames } from "../../utils/classnames";
@ -52,9 +48,9 @@ export function AvailabilitySlotRow({ bytes, active, id }: Props) {
<div> <div>
<b>Slot {id}</b> <b>Slot {id}</b>
</div> </div>
<SimpleText size="small" variant="light"> <small className="text--light">
{PrettyBytes(bytes)} allocated for the slot {PrettyBytes(bytes)} allocated for the slot
</SimpleText> </small>
</div> </div>
</div> </div>
</Cell>, </Cell>,

View File

@ -0,0 +1,15 @@
.background-img {
position: absolute;
right: -40px;
max-height: 90%;
width: auto;
/* z-index: -1; */
transition: opacity 0.35s;
opacity: 0.3;
@media (min-width: 1200px) {
& {
opacity: 1;
}
}
}

View File

@ -0,0 +1,35 @@
import "./BackgroundImage.css";
export function BackgroundImage() {
return (
<picture>
<source
srcSet="/img/onboarding@4x.webp 4x,
/img/onboarding@3x.webp 3x,
/img/onboarding@2x.webp 2x,
/img/onboarding@1.5x.webp 1.5x,
/img/onboarding.webp 1x"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
33vw"
type="image/webp"
/>
<source
srcSet="/img/onboarding@4x.png 4x,
/img/onboarding@3x.png 3x,
/img/onboarding@2x.png 2x,
/img/onboarding@1.5x.png 1.5x,
/img/onboarding.png 1x"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
33vw"
type="image/png"
/>
<img
src="/img/onboarding.png"
alt="Background Image"
className="background-img"
/>
</picture>
);
}

View File

@ -1,7 +1,4 @@
import { import { ButtonIcon } from "@codex-storage/marketplace-ui-components";
ButtonIcon,
SimpleText,
} from "@codex-storage/marketplace-ui-components";
import "./CardNumbers.css"; import "./CardNumbers.css";
import { Check, CircleX, Pencil } from "lucide-react"; import { Check, CircleX, Pencil } from "lucide-react";
import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react"; import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react";
@ -165,12 +162,7 @@ export function CardNumbers({
{error ? ( {error ? (
<small className="cardNumber-errorText">{error}</small> <small className="cardNumber-errorText">{error}</small>
) : ( ) : (
<SimpleText <small className="cardNumber-helperText">{helper}</small>
size="small"
variant="light"
className="cardNumber-helperText">
{helper}
</SimpleText>
)} )}
</div> </div>
); );

View File

@ -1,10 +1,5 @@
type Props = { export function ErrorCircleIcon() {
className?: string;
};
export function ErrorCircleIcon({ className = "" }: Props) {
return ( return (
<span className={"onboarding-healthCheck-icon " + className}>
<svg <svg
width="16" width="16"
height="16" height="16"
@ -16,6 +11,5 @@ export function ErrorCircleIcon({ className = "" }: Props) {
fill="#FB3748" fill="#FB3748"
/> />
</svg> </svg>
</span>
); );
} }

View File

@ -1,10 +1,9 @@
import { CircleX } from "lucide-react"; import { CircleX } from "lucide-react";
import { SimpleText } from "@codex-storage/marketplace-ui-components";
export function ErrorIcon() { export function ErrorIcon() {
return ( return (
<SimpleText variant="error"> <span className="text--error">
<CircleX size="4rem" /> <CircleX size="4rem" />
</SimpleText> </span>
); );
} }

View File

@ -0,0 +1,85 @@
.address {
display: flex;
align-items: center;
position: relative;
gap: 16px;
> div {
position: relative;
}
svg {
position: absolute;
top: 68px;
bottom: 0;
right: 18px;
}
.refresh {
position: relative;
top: 24px;
cursor: pointer;
svg {
position: initial;
}
&.address--fetching .refresh svg {
animation: rotate 2s linear infinite;
}
}
input[type="number"] {
width: 150px;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
}
.helper {
font-family: Azeret Mono;
font-size: 12px;
font-weight: 400;
line-height: 14px;
color: #828282;
padding-left: 1.25rem;
margin-top: 1.75rem;
margin-bottom: 3rem;
}
.health-checks {
margin-bottom: 32px;
li {
display: flex;
align-items: center;
padding: 16px 0;
gap: 16px;
border-top: 1px solid #96969633;
border-bottom: 1px solid #96969633;
&:first-child {
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
}
span {
display: flex;
align-items: center;
width: 20px;
height: 20px;
justify-content: center;
}
}
}

View File

@ -0,0 +1,199 @@
import { useQueryClient } from "@tanstack/react-query";
import { useEffect, useState, ClipboardEvent } from "react";
import { useDebug } from "../../hooks/useDebug";
import { usePersistence } from "../../hooks/usePersistence";
import { usePortForwarding } from "../../hooks/usePortForwarding";
import { CodexSdk } from "../../proxy";
import { ErrorCircleIcon } from "../ErrorCircleIcon/ErrorCircleIcon";
import { SuccessCheckIcon } from "../SuccessCheckIcon/SuccessCheckIcon";
import { WarningIcon } from "../WarningIcon/WarningIcon";
import { HealthCheckIcon } from "./HealthCheckIcon";
import { Input } from "@codex-storage/marketplace-ui-components";
import { classnames } from "../../utils/classnames";
import { DebugUtils } from "../../utils/debug";
import { Strings } from "../../utils/strings";
import { RefreshIcon } from "../RefreshIcon/RefreshIcon";
import "./HealthChecks.css";
type Props = {
online: boolean;
onStepValid: (valid: boolean) => void;
};
const throwOnError = false;
const defaultPort = 8070;
export function HealthChecks({ online, onStepValid }: Props) {
const codex = useDebug(throwOnError);
const portForwarding = usePortForwarding(codex.data);
const persistence = usePersistence(codex.isSuccess);
const [isInvalid, setIsInvalid] = useState(false);
const [url, setUrl] = useState(CodexSdk.url);
const queryClient = useQueryClient();
useEffect(() => {
if (codex.isSuccess) {
persistence.refetch();
portForwarding.refetch().then(({ isError }) => onStepValid(isError));
} else {
onStepValid(false);
}
}, [
persistence.refetch,
onStepValid,
portForwarding.refetch,
codex.isSuccess,
]);
const onAddressChange = (e: React.FormEvent<HTMLInputElement>) => {
const [, port] = Strings.splitURLAndPort(url);
const element = e.currentTarget;
const value = e.currentTarget.value;
if (
value.startsWith("http://") === false &&
value.startsWith("https://") === false
) {
setIsInvalid(true);
return;
}
setIsInvalid(!element.checkValidity());
setUrl(value + ":" + port);
};
const onPaste = (e: ClipboardEvent) => {
const text = e.clipboardData?.getData("text") || "";
try {
new URL(text);
setUrl(text);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_) {
// Nothing to do here
}
};
const onPortChange = (e: React.FormEvent<HTMLInputElement>) => {
const [address] = Strings.splitURLAndPort(url);
const element = e.currentTarget;
const value = element.value;
setUrl(address + ":" + value);
};
const onSave = () => {
if (isInvalid === true) {
return;
}
CodexSdk.updateURL(url)
.then(() => queryClient.invalidateQueries())
.then(() => codex.refetch());
};
let forwardingPortValue = defaultPort;
if (codex.isSuccess && codex.data) {
const port = DebugUtils.getTcpPort(codex.data);
if (!port.error) {
forwardingPortValue = port.data;
}
}
const [address, port] = Strings.splitURLAndPort(url);
return (
<>
<div
className={classnames(
["address"],
["address--fetching", portForwarding.isFetching || codex.isPending]
)}>
<div>
<Input
onPaste={onPaste}
id="url"
type="url"
label="Address"
isInvalid={isInvalid}
onChange={onAddressChange}
value={address}
placeholder="127.0.0.1"></Input>
{isInvalid ? (
<ErrorCircleIcon />
) : (
<SuccessCheckIcon variant="default" />
)}
</div>
<div>
<Input
id="port"
label="Port"
type="number"
onChange={onPortChange}
value={port}
placeholder="8080"></Input>
<SuccessCheckIcon variant="default"></SuccessCheckIcon>
</div>
<div className="refresh">
<RefreshIcon onClick={onSave}></RefreshIcon>
</div>
</div>
<ul className="helper">
<li>Port forwarding should be default {forwardingPortValue}.</li>
</ul>
<ul className="health-checks">
<li>
<span>
<HealthCheckIcon />
</span>
Health Check
</li>
<li>
<span>
{online ? (
<SuccessCheckIcon variant="primary"></SuccessCheckIcon>
) : (
<ErrorCircleIcon />
)}
</span>
Internet connection
</li>
<li>
<span>
{portForwarding.enabled ? (
<SuccessCheckIcon variant="primary"></SuccessCheckIcon>
) : (
<ErrorCircleIcon />
)}
</span>
Port forwarding
</li>
<li>
<span>
{codex.isSuccess ? (
<SuccessCheckIcon variant="primary"></SuccessCheckIcon>
) : (
<ErrorCircleIcon />
)}
</span>
Codex connection
</li>
<li>
<span>
{persistence.enabled ? (
<SuccessCheckIcon variant="primary"></SuccessCheckIcon>
) : (
<WarningIcon />
)}
</span>
Marketplace
</li>
</ul>
</>
);
}

View File

@ -1,24 +0,0 @@
.network-indicator {
display: flex;
align-items: center;
gap: 16px;
}
.network-indicator-icon {
background-color: #141414;
border-radius: var(--codex-border-radius);
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.network-indicator-text {
font-family: Inter;
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.006em;
color: #8d8d8d;
}

View File

@ -1,16 +0,0 @@
import { useNetwork } from "../../network/useNetwork";
import { NetworkFlashIcon } from "../NetworkFlashIcon/NetworkFlashIcon";
import "./HttpNetworkIndicator.css";
export function HttpNetworkIndicator() {
const online = useNetwork();
return (
<div className="network-indicator">
<div className="network-indicator-icon">
<NetworkFlashIcon online={online} />
</div>
<span className="network-indicator-text">Network</span>
</div>
);
}

View File

@ -1,14 +1,20 @@
import { attributes } from "../../utils/attributes";
type Props = { type Props = {
onClick?: () => void; onClick?: () => void;
width?: number;
height?: number;
}; };
export function Logo({ onClick }: Props) { export function Logo({ onClick, width, height }: Props) {
return ( return (
<svg <svg
onClick={onClick} onClick={onClick}
className="logo" className="logo"
width="30" {...attributes({
height="34" width: width?.toString() || false,
height: height?.toString() || false,
})}
viewBox="0 0 30 34" viewBox="0 0 30 34"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">

File diff suppressed because one or more lines are too long

View File

@ -6,10 +6,23 @@ import { Logo } from "../Logo/Logo";
import { Logotype } from "../Logotype/Logotype"; import { Logotype } from "../Logotype/Logotype";
import { ExpandIcon } from "./ExpandIcon"; import { ExpandIcon } from "./ExpandIcon";
import { classnames } from "../../utils/classnames"; import { classnames } from "../../utils/classnames";
import { Link } from "@tanstack/react-router";
import { HomeIcon } from "./HomeIcon";
import { WalletIcon } from "./WalletIcon";
import { FilesIcon } from "./FilesIcon";
import { NodesIcon } from "./NodesIcon";
import { AnalyticsIcon } from "./AnalyticsIcon";
import { DeviceIcon } from "./DeviceIcon";
import { PurchaseIcon } from "./PurchaseIcon";
import { HostIcon } from "./HostIcon";
import { PeersIcon } from "./PeersIcon";
import { LogsIcon } from "./LogsIcon";
import { SettingsIcon } from "./SettingsIcon";
import { HelpIcon } from "./HelpIcon";
import { DisclaimerIcon } from "./DisclaimerIcon";
export type MenuItemComponentProps = { export type MenuItemComponentProps = {
onClick: () => void; onClick: () => void;
className: string;
}; };
export type MenuItem = export type MenuItem =
@ -33,27 +46,9 @@ type Props = {
onClose: () => void; onClose: () => void;
onOpen?: () => void; onOpen?: () => void;
/**
* The menu items to be displayed
*/
items: MenuItem[];
className?: string;
/**
* The application version
*/
version?: string;
}; };
export function Menu({ export function Menu({ expanded, onClose, onOpen }: Props) {
expanded,
onClose,
onOpen,
items,
className = "",
}: Props) {
const [isExpanded, setIsExpanded] = useState<boolean | null>(null); const [isExpanded, setIsExpanded] = useState<boolean | null>(null);
useEffect(() => { useEffect(() => {
@ -70,44 +65,122 @@ export function Menu({
const onExpandMenu = () => setIsExpanded(isExpanded === false ? true : false); const onExpandMenu = () => setIsExpanded(isExpanded === false ? true : false);
const renderItem = (i: MenuItem, index: number) => {
switch (i.type) {
case "separator":
return <hr className="menu-item-separator" key={index}></hr>;
case "space":
return <div className="menu-space" key={index}></div>;
case "item":
return (
<i.Component onClick={onClose} className="menu-item" key={index} />
);
}
};
return ( return (
<> <>
<Backdrop onClose={onClose} open={expanded} /> <Backdrop onClose={onClose} open={expanded} />
<aside <aside
className={classnames( className={classnames(
[`menu ${className}`], [`menu`],
["menu--expanded", isExpanded === true], ["menu--expanded", isExpanded === true],
["menu--unexpanded", isExpanded === false] ["menu--unexpanded", isExpanded === false]
)} )}
{...attributes({ "aria-expanded": expanded })}> {...attributes({ "aria-expanded": expanded })}>
<div className="menu-container"> <div>
<div className="menu-header"> <header>
<Logo onClick={onLogoClick} /> <Logo onClick={onLogoClick} width={30} />
<Logotype height={34} className={"menu-logotype"} /> <Logotype height={34} />
<div className="menu-header-right"> <div>
<ExpandIcon onClick={onExpandMenu}></ExpandIcon> <ExpandIcon onClick={onExpandMenu}></ExpandIcon>
</div> </div>
{/* <span className="menu-separator">|</span> </header>
<span className="menu-name">Codex</span>
<span className="menu-state">ALPHA {version}</span> */}
</div>
<div className="menu-items"> <div className="items">
{items.map((item, index) => renderItem(item, index))} <Link to="/dashboard" activeOptions={{ exact: true }}>
<span>
<HomeIcon />
</span>
<span>Dashboard</span>
</Link>
<Link to="/dashboard/wallet">
<span>
<WalletIcon />
</span>
<span>Wallet</span>
</Link>
<Link to="/dashboard/files">
<span>
<FilesIcon />
</span>
<span>Files</span>
</Link>
<Link
to="/dashboard/nodes"
disabled={true}
aria-disabled={true}
data-title="Coming soon">
<span>
<NodesIcon variant="default" />
</span>
<span>Nodes</span>
</Link>
<Link
to="/dashboard/analytics"
disabled={true}
aria-disabled={true}
title="Coming soon"
data-title="Coming soon">
<span>
<AnalyticsIcon />
</span>
<span>Analytics</span>
</Link>
<Link
to="/dashboard/device"
disabled={true}
aria-disabled={true}
title="Coming soon"
data-title="Coming soon">
<span>
<DeviceIcon />
</span>
<span>Devices</span>
</Link>
<hr />
<Link to="/dashboard/purchases">
<span>
<PurchaseIcon />
</span>
<span>Purchases</span>
</Link>
<Link to="/dashboard/availabilities">
<span>
<HostIcon />
</span>
<span>Host</span>
</Link>
<hr />
<Link to="/dashboard/peers">
<span>
<PeersIcon />
</span>
<span>Peers</span>
</Link>
<Link to="/dashboard/logs">
<span>
<LogsIcon />
</span>
<span>Log</span>
</Link>
<section></section>
<Link to="/dashboard/settings">
<span>
<SettingsIcon />
</span>
<span>Settings</span>
</Link>
<Link to="/dashboard/help">
<span>
<HelpIcon />
</span>
<span>Help</span>
</Link>
<Link to="/dashboard/disclaimer">
<span>
<DisclaimerIcon />
</span>
<span>Disclaimer</span>
</Link>
</div> </div>
</div> </div>
</aside> </aside>

View File

@ -14,273 +14,14 @@
transition: width 0.5s; transition: width 0.5s;
min-width: 0; min-width: 0;
width: 272px; width: 272px;
} transform: translatex(0px);
.logo { &[aria-expanded] {
cursor: pointer;
min-width: 30px;
}
.menu--expanded .logo {
cursor: default;
}
.menu--expanded {
width: 272px;
}
.menu--unexpanded {
width: 80px;
}
/* @keyframes menu-in {
0% {
width: 0;
display: none;
}
100% {
width: auto;
display: inline-block;
}
}
*/
.menu-logotype,
.menu-header-right,
.menu-text {
display: inline-block;
overflow: hidden;
min-width: 0;
}
/* .menu--expanded .menu-header-right svg,
.menu--expanded .menu-logotype,
.menu--expanded .menu-header-right {
animation-name: header-right-menu-in;
animation-duration: 1s;
animation-fill-mode: forwards;
}
.menu--expanded .menu-header-right svg,
.menu--expanded .menu-logotype { */
/* display: none; */
/* animation-name: logotype-menu-in;
animation-duration: 1s;
animation-fill-mode: forwards;
} */
/* .menu--unexpanded .menu-logotype,
.menu--unexpanded .menu-header-right {
display: none;
} */
.menu-container {
display: flex;
flex-direction: column;
padding: 12px;
position: sticky;
top: 0;
height: calc(100vh - 24px);
overflow: auto;
}
.menu-header-right {
flex: 1;
text-align: right;
transition: opacity 0.35s;
cursor: pointer;
}
.menu-header-right:hover {
animation-name: example;
animation-duration: 2.5s;
animation-iteration-count: infinite;
}
.menu-backdrop {
display: none;
}
.menu-items {
border-top: 2px solid var(--codex-border-color);
padding-top: 1.5rem;
display: flex;
flex-direction: column;
position: relative;
height: 100%;
margin-bottom: 2.5rem;
}
.menu[aria-expanded] {
transform: translatex(0); transform: translatex(0);
min-width: 200px; min-width: 200px;
}
.menu-header {
padding: 12px;
display: flex;
align-items: center;
gap: 1.5rem;
margin-bottom: 1rem;
background-color: #060606;
border-radius: 8px;
}
.menu-item {
display: flex;
align-items: center;
gap: 0.75rem;
}
.menu-items::before {
height: 20px;
width: 8px;
background-color: var(--codex-color-primary);
position: absolute;
content: " ";
transition:
top 1s,
bottom 1s;
border-radius: 4px;
left: -16px;
}
.menu-items:has(.active:nth-child(1))::before {
top: 30px;
}
.menu-items:has(.active:nth-child(2))::before {
top: 72px;
}
.menu-items:has(.active:nth-child(3))::before {
top: 115px;
}
.menu-items:has(.active:nth-child(4))::before {
top: 158px;
}
.menu-items:has(.active:nth-child(5))::before {
top: 201px;
}
.menu-items:has(.active:nth-child(6))::before {
top: 244px;
}
.menu-items:has(.active:nth-child(8))::before {
top: 332px;
}
.menu-items:has(.active:nth-child(9))::before {
top: 375px;
}
.menu-items:has(.active:nth-child(11))::before {
top: 461px;
}
.menu-items:has(.active:nth-child(12))::before {
top: 504px;
}
.menu-items:has(.active:nth-child(14))::before {
top: calc(100% - 113px);
}
.menu-items:has(.active:nth-child(15))::before {
top: calc(100% - 70px);
}
.menu-items:has(.active:nth-child(16))::before {
top: calc(100% - 27px);
}
.menu-item:not(:first-child) {
margin-top: 0.5rem;
}
.menu-item {
padding: 8px 18px;
margin-bottom: 0;
text-decoration: none;
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.006em;
color: #969696;
border-radius: 8px;
transition: background-color 0.35s;
}
.menu-item:hover,
.menu-item.active {
background-color: var(--codex-highlight-color);
color: #c7c7c7;
}
.menu-item.active .menu-icon {
color: var(--codex-color-primary);
}
.menu-title {
text-transform: uppercase;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
padding-left: 0.75rem;
display: inline-block;
font-weight: 500;
}
.menu-item-separator {
margin-top: 1.5rem;
margin-bottom: 1.5rem;
border: 0.1px solid var(--codex-border-color);
width: 100%;
}
.menu-state {
font-size: 0.6rem;
background-color: var(--codex-background-light);
padding: 0.25rem;
border-radius: var(--codex-border-radius);
position: relative;
top: 1px;
}
.menu-footer {
text-align: center;
}
.menu-version {
padding: 0;
}
.menu-space {
flex: 1;
}
.menu-icon {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
transition: color 1s;
}
@media (min-width: 1000px) {
.menu {
transform: translatex(0px);
/* position: inherit; */
} }
}
.menu a { &:not(.menu--unexpanded) a[data-title]:hover::after {
position: relative;
}
.menu:not(.menu--unexpanded) a[data-title]:hover::after {
content: attr(data-title); content: attr(data-title);
background-color: #2f2f2f; background-color: #2f2f2f;
color: #fff; color: #fff;
@ -293,4 +34,203 @@
position: absolute; position: absolute;
right: 1rem; right: 1rem;
overflow: visible; overflow: visible;
}
&.menu--expanded {
width: 272px;
}
&.menu--unexpanded {
width: 80px;
.items {
a {
width: 26px;
gap: 0;
display: flex;
justify-content: center;
span + span {
font-size: 0;
}
}
}
}
> div {
display: flex;
flex-direction: column;
padding: 12px;
position: sticky;
top: 0;
height: calc(100vh - 24px);
overflow: auto;
}
header {
padding: 13px;
display: flex;
align-items: center;
gap: 1.5rem;
background-color: #060606;
border-radius: 8px;
> svg:first-child {
cursor: pointer;
min-width: 30px;
}
div {
flex: 1;
text-align: right;
transition: opacity 0.35s;
display: inline-block;
overflow: hidden;
min-width: 0;
svg {
cursor: pointer;
}
&:hover {
animation-name: example;
animation-duration: 2.5s;
animation-iteration-count: infinite;
}
}
}
.items {
padding-top: 1.5rem;
display: flex;
flex-direction: column;
position: relative;
height: 100%;
margin-bottom: 2.5rem;
gap: 0.5rem;
border-top: 1px solid #96969633;
&::before {
height: 20px;
width: 8px;
background-color: var(--codex-color-primary);
position: absolute;
content: " ";
transition:
top 1s,
bottom 1s;
border-radius: 4px;
left: -16px;
}
&:has(.active:nth-child(1))::before {
top: 30px;
}
&:has(.active:nth-child(2))::before {
top: 72px;
}
&:has(.active:nth-child(3))::before {
top: 115px;
}
&:has(.active:nth-child(4))::before {
top: 158px;
}
&:has(.active:nth-child(5))::before {
top: 201px;
}
&:has(.active:nth-child(6))::before {
top: 244px;
}
&:has(.active:nth-child(8))::before {
top: 339px;
}
&:has(.active:nth-child(9))::before {
top: 382px;
}
&:has(.active:nth-child(11))::before {
top: 475px;
}
&:has(.active:nth-child(12))::before {
top: 513px;
}
&:has(.active:nth-child(14))::before {
top: calc(100% - 113px);
}
&:has(.active:nth-child(15))::before {
top: calc(100% - 70px);
}
&:has(.active:nth-child(16))::before {
top: calc(100% - 27px);
}
&:not(:first-child) {
margin-top: 0.5rem;
}
hr {
margin-top: 1.5rem;
margin-bottom: 1.5rem;
border: 0.1px solid #96969633;
width: 100%;
}
section {
flex: 1;
}
a {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 8px 10px;
margin-bottom: 0;
text-decoration: none;
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.006em;
color: #969696;
border-radius: 8px;
transition: background-color 0.35s;
position: relative;
margin-left: 6px;
&:hover:not([aria-disabled="true"]),
&.active {
background-color: var(--codex-highlight-color);
color: #c7c7c7;
}
span:first-child {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
transition: color 1s;
}
span + span {
display: inline-block;
overflow: hidden;
min-width: 0;
}
&.active span:first-child {
color: var(--codex-color-primary);
}
}
}
} }

View File

@ -1,18 +1,20 @@
type Props = { type Props = {
online: boolean; className?: string;
}; };
export function NetworkFlashIcon({ online }: Props) { export function NetworkFlashIcon({ className }: Props) {
const color = online ? "#3EE089" : "var(--codex-color-error-hexa)";
return ( return (
<svg <svg
className={className}
width="20" width="20"
height="20" height="20"
viewBox="0 0 20 20" viewBox="0 0 20 20"
fill="none" fill="#none"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
<path d="M10.75 8.5H16L9.25 18.25V11.5H4L10.75 1.75V8.5Z" fill={color} /> <path
d="M10.75 8.5H16L9.25 18.25V11.5H4L10.75 1.75V8.5Z"
fill={"#3EE089"}
/>
</svg> </svg>
); );
} }

View File

@ -1,24 +0,0 @@
.network-indicator {
display: flex;
align-items: center;
gap: 16px;
}
.network-indicator-icon {
background-color: #141414;
border-radius: var(--codex-border-radius);
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.network-indicator-text {
font-family: Inter;
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.006em;
color: #8d8d8d;
}

View File

@ -1,28 +0,0 @@
import { useQueryClient } from "@tanstack/react-query";
import { useEffect } from "react";
import { useCodexConnection } from "../../hooks/useCodexConnection";
import "./NodeIndicator.css";
import { NodesIcon } from "../Menu/NodesIcon";
export function NodeIndicator() {
const queryClient = useQueryClient();
const codex = useCodexConnection();
useEffect(() => {
queryClient.invalidateQueries({
type: "active",
refetchType: "all",
});
}, [queryClient, codex.enabled]);
return (
<>
<div className="network-indicator">
<div className="network-indicator-icon">
<NodesIcon variant={codex.enabled ? "success" : "failure"} />
</div>
<span className="network-indicator-text">Node</span>
</div>
</>
);
}

View File

@ -1,5 +0,0 @@
.onboarding-healthCheckItem {
display: flex;
align-items: center;
gap: 16px;
}

View File

@ -1,21 +0,0 @@
import { classnames } from "../../utils/classnames";
import { OnBoardingStatusIcon } from "./OnBoardingStatusIcon";
import "./HealthCheckItem.css";
type Props = {
value: "success" | "failure" | "warning";
text: string;
};
export function HealthCheckItem({ value, text }: Props) {
return (
<div
data-testid="network"
className={classnames(["onboarding-healthCheckItem"])}>
<OnBoardingStatusIcon value={value} />
<div>
<p>{text}</p>
</div>
</div>
);
}

View File

@ -1,7 +0,0 @@
.onboarding-image {
position: absolute;
right: -40px;
max-height: 90%;
width: auto;
z-index: -1;
}

View File

@ -1,35 +0,0 @@
import "./OnBoardingImage.css";
export function OnBoardingImage() {
return (
<picture>
<source
srcSet="/img/onboarding@4x.webp 4x,
/img/onboarding@3x.webp 3x,
/img/onboarding@2x.webp 2x,
/img/onboarding@1.5x.webp 1.5x,
/img/onboarding.webp 1x"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
33vw"
type="image/webp"
/>
<source
srcSet="/img/onboarding@4x.png 4x,
/img/onboarding@3x.png 3x,
/img/onboarding@2x.png 2x,
/img/onboarding@1.5x.png 1.5x,
/img/onboarding.png 1x"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
33vw"
type="image/png"
/>
<img
src="/img/onboarding.png"
alt="Onboarding Image"
className="onboarding-image"
/>
</picture>
);
}

View File

@ -0,0 +1,217 @@
.onboarding {
width: 100%;
padding: 3rem 6rem;
display: flex;
> section {
display: flex;
flex-direction: column;
justify-content: space-between;
}
> section:first-child {
max-width: 500px;
}
section {
flex: 1;
}
&.onboarding--second .alpha {
flex: 0.3;
}
&.onboarding--third .alpha {
flex: 0.5;
}
section > *:first-child {
flex: 0.5;
}
h1 {
font-family: Inter;
font-size: 36px;
font-weight: 300;
line-height: 43.57px;
letter-spacing: 0.01em;
b {
font-weight: 400;
}
b + b {
font-weight: 900;
text-transform: uppercase;
}
}
footer {
display: flex;
align-items: center;
justify-content: space-between;
flex: 0;
ul {
display: flex;
gap: 8px;
li {
& {
width: 12px;
height: 12px;
background-color: white;
display: inline-block;
border-radius: 50%;
transition: opacity 0.35s;
}
&:hover {
animation-name: pulse;
animation-duration: 2.5s;
animation-iteration-count: infinite;
}
&:not([aria-selected]) {
opacity: 0.4;
}
&[aria-selected] {
box-shadow: 0px 0px 12px 0px #fff;
opacity: 1;
}
}
}
}
.alpha {
> div {
margin-top: 4px;
display: block;
}
b {
font-weight: 500;
opacity: 0.6;
display: block;
}
a {
text-decoration: underline;
font-family: Azeret Mono;
font-size: 12px;
font-weight: 400;
line-height: 14px;
margin-top: 16px;
cursor: pointer;
display: inline-flex;
align-items: center;
color: var(--codex-color-error-hexa);
&:hover {
animation-name: example;
animation-duration: 2.5s;
animation-iteration-count: infinite;
}
}
p {
margin-top: 1rem;
font-family: Azeret Mono;
font-size: 14px;
font-weight: 400;
line-height: 16.34px;
display: inline-block;
color: var(--codex-color-primary);
}
}
.main {
p {
font-family: Azeret Mono;
font-size: 14px;
font-weight: 400;
line-height: 16.34px;
max-width: 532px;
margin-top: 20px;
color: var(--codex-input-label-color);
}
label {
margin-top: 1rem;
}
}
.get-started {
a {
font-size: 24px;
font-weight: 600;
line-height: 29.05px;
letter-spacing: 0.01em;
margin-top: 32px;
font-family: Inter;
color: #7bfbaf;
gap: 4px;
text-decoration: none;
border-bottom: 2px solid #7bfbaf;
cursor: pointer;
display: inline-flex;
align-items: center;
}
}
.modal {
max-width: 600px;
margin: auto;
h1 {
margin-top: 0;
margin-bottom: 3rem;
}
p {
line-height: 1.5rem;
}
}
.navigation {
cursor: pointer;
position: absolute;
right: 6rem;
bottom: 3rem;
border-bottom: none;
text-decoration: none;
z-index: 1;
&:hover {
animation-name: example;
animation-duration: 2.5s;
animation-iteration-count: infinite;
}
&[aria-disabled="true"] {
cursor: not-allowed;
}
}
}
@keyframes rotate {
from {
transform: rotate(0deg); /* Start at 0 degrees */
}
to {
transform: rotate(360deg); /* End at 360 degrees */
}
}
@keyframes pulse {
0% {
opacity: 0.4;
}
30% {
opacity: 0.8;
}
100% {
opacity: 0.4;
}
}

View File

@ -0,0 +1,41 @@
import { ReactElement } from "react";
import { classnames } from "../../utils/classnames";
import { Logotype } from "../Logotype/Logotype";
import { attributes } from "../../utils/attributes";
import "./OnBoardingLayout.css";
import { BackgroundImage } from "../BackgroundImage/BackgroundImage";
type Props = {
children: ReactElement<{ onStepValid: (isValid: boolean) => void }>;
step: number;
defaultIsStepValid: boolean;
};
export function OnBoardingLayout({ children, step }: Props) {
return (
<div
className={classnames(
["onboarding"],
["onboarding--first", step === 0],
["onboarding--second", step === 1],
["onboarding--third", step === 2]
)}>
<section>
<div>
<Logotype width={111} />
</div>
{children}
<footer>
<ul>
<li {...attributes({ "aria-selected": step === 0 })}></li>
<li {...attributes({ "aria-selected": step === 1 })}></li>
<li {...attributes({ "aria-selected": step === 2 })}></li>
</ul>
</footer>
</section>
<BackgroundImage />
</div>
);
}

View File

@ -1,33 +0,0 @@
import { ErrorCircleIcon } from "../ErrorCircleIcon/ErrorCircleIcon";
import { SuccessCheckIcon } from "../SuccessCheckIcon/SuccessCheckIcon";
type Props = {
value: "success" | "failure" | "warning";
};
export function OnBoardingStatusIcon({ value }: Props) {
switch (value) {
case "success":
return <SuccessCheckIcon variant="primary"></SuccessCheckIcon>;
case "failure":
return <ErrorCircleIcon />;
case "warning":
return (
<span className="onboarding-healthCheck-icon">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M8 15.5C3.85775 15.5 0.5 12.1423 0.5 8C0.5 3.85775 3.85775 0.5 8 0.5C12.1423 0.5 15.5 3.85775 15.5 8C15.5 12.1423 12.1423 15.5 8 15.5ZM4.25 7.25V8.75H11.75V7.25H4.25Z"
fill="#FF8447"
/>
</svg>
</span>
);
}
}

View File

@ -1,100 +0,0 @@
import { AlphaIcon } from "./AlphaIcon";
import { AlphaText } from "../AlphaText/AlphaText";
import { Modal, SimpleText } from "@codex-storage/marketplace-ui-components";
import { useState } from "react";
import { ArrowRight } from "lucide-react";
type Props = {
onNextStep: () => void;
};
export function OnBoardingStepOne({ onNextStep }: Props) {
const [modal, setModal] = useState(false);
const onLegalDisclaimerOpen = () => setModal(true);
const onLegalDisclaimerClose = () => setModal(false);
return (
<>
<div className="index-column-section">
<div>
<AlphaIcon variant="error" />
</div>
<div className="index-alphaText">
<p>
<AlphaText variant="default"></AlphaText>
</p>
<p>
<SimpleText className="index-version" variant="normal">
{import.meta.env.PACKAGE_VERSION}
</SimpleText>
</p>
<p>
<SimpleText
className="index-disclaimer"
variant="error"
onClick={onLegalDisclaimerOpen}>
<a className="index-link">Legal Disclaimer</a>
</SimpleText>
</p>
</div>
</div>
<div className="index-column-section">
<h3 className="index-mainTitle">
Hello,
<br /> Welcome to <span className="index-codex">Codex</span>{" "}
<span className="index-vault">Vault</span>
</h3>
<p className="index-description">
<SimpleText variant="light">
Codex is a durable, decentralised data storage protocol, created so
the world community can preserve its most important knowledge
without risk of censorship.
</SimpleText>
</p>
</div>
<div className="index-column-section ">
<SimpleText variant="primary">
<a onClick={onNextStep} className="index-link index-getStarted">
Lets get started <ArrowRight></ArrowRight>
</a>
</SimpleText>
<Modal onClose={onLegalDisclaimerClose} open={modal}>
<h1 className="disclaimer-title" style={{ marginTop: 0 }}>
Disclaimer
</h1>
<p className="disclaimer-text">
The website and the content herein is not intended for public use
and is for informational and demonstration purposes only.
</p>
<br />
<p className="disclaimer-text">
The website and any associated functionalities are provided on an
as is basis without any guarantees, warranties, or representations
of any kind, either express or implied. The website and any
associated functionalities may not reflect the final version of the
project and is subject to changes, updates, or removal at any time
and without notice.
</p>
<br />
<p className="disclaimer-text">
By accessing and using this website, you agree that we, Logos
Collective Association and its affiliates, will not be liable for
any direct, indirect, incidental, or consequential damages arising
from the use of, or inability to use, this website. Any data,
content, or interactions on this site are non-binding and should not
be considered final or actionable. Your use of this website is at
your sole risk.
</p>
</Modal>
</div>
</>
);
}

View File

@ -1,118 +0,0 @@
.onboarding-check {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
.onboarding-check-icon--valid {
color: var(--codex-color-primary);
}
.onboarding-check-icon--invalid {
color: rgb(var(--codex-color-error));
}
.onboarding-check-icon--warning {
color: rgb(var(--codex-color-warning));
}
.onboarding-check-line {
display: flex;
align-items: center;
gap: 0.5rem;
}
.onboarding-check-refresh {
cursor: pointer;
}
.onboarding-group {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 0.5rem;
}
.onboarding-codex {
margin-top: 1rem;
padding-left: 1rem;
}
.onboarding-deviceCheck {
margin-top: 1rem;
margin-bottom: 1rem;
font-family: Azeret Mono;
font-size: 14px;
font-weight: 400;
line-height: 16.34px;
display: inline-block;
}
.onboarding--deviceCheck-block {
flex: 0.3;
}
.onboarding-displayName {
margin-bottom: 1.75rem;
}
.onboarding-healthCheck-item {
display: flex;
align-items: center;
padding: 16px 0;
gap: 16px;
border-top: 1px solid #3c3d3e;
border-bottom: 1px solid #3c3d3e;
}
.onboarding-healthCheck-itemText {
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
}
.onboarding-healthChecks {
margin-bottom: 32px;
}
.onboarding-healthCheck-icon {
display: flex;
align-items: center;
width: 20px;
height: 20px;
justify-content: center;
}
.onboarding-refresh {
position: relative;
top: 14px;
cursor: pointer;
}
.onboarding-refresh--fetching {
animation: rotate 2s linear infinite;
}
.onboarding-portContainer,
.onboarding-addressContainer {
position: relative;
}
.onboarding--addressSuccessIcon {
position: absolute;
top: 53px;
bottom: 0;
right: 18px;
}
@keyframes rotate {
from {
transform: rotate(0deg); /* Start at 0 degrees */
}
to {
transform: rotate(360deg); /* End at 360 degrees */
}
}

View File

@ -1,219 +0,0 @@
import { classnames } from "../../utils/classnames";
import "./OnBoardingStepThree.css";
import { usePortForwarding } from "../../hooks/usePortForwarding";
import { Input, SimpleText } from "@codex-storage/marketplace-ui-components";
import { ClipboardEvent, useEffect, useState } from "react";
import { CodexSdk } from "../../sdk/codex";
import { useQueryClient } from "@tanstack/react-query";
import { usePersistence } from "../../hooks/usePersistence";
import { useDebug } from "../../hooks/useDebug";
import { DebugUtils } from "../../utils/debug";
import { AlphaIcon } from "./AlphaIcon";
import { OnBoardingUtils } from "../../utils/onboarding";
import { RefreshIcon } from "../RefreshIcon/RefreshIcon";
import { HealthCheckIcon } from "./HealthCheckIcon";
import { HealthCheckItem } from "./HealthCheckItem";
import { Strings } from "../../utils/strings";
import { SuccessCheckIcon } from "../SuccessCheckIcon/SuccessCheckIcon";
import { ErrorCircleIcon } from "../ErrorCircleIcon/ErrorCircleIcon";
type Props = {
online: boolean;
onStepValid: (valid: boolean) => void;
};
const throwOnError = false;
const defaultPort = 8070;
export function OnBoardingStepThree({ online, onStepValid }: Props) {
const codex = useDebug(throwOnError);
const portForwarding = usePortForwarding(codex.data);
const persistence = usePersistence(codex.isSuccess);
const [url, setUrl] = useState(CodexSdk.url);
const queryClient = useQueryClient();
const [isInvalid, setIsInvalid] = useState(false);
useEffect(() => {
onStepValid(online && portForwarding.enabled && codex.isSuccess);
}, [portForwarding.enabled, codex.isSuccess, onStepValid, online]);
useEffect(() => {
if (codex.isSuccess) {
persistence.refetch();
portForwarding.refetch();
}
}, [persistence, portForwarding, codex.isSuccess]);
const onAddressChange = (e: React.FormEvent<HTMLInputElement>) => {
const [, port] = Strings.splitURLAndPort(url);
const element = e.currentTarget;
const value = e.currentTarget.value;
if (
value.startsWith("http://") === false &&
value.startsWith("https://") === false
) {
setIsInvalid(true);
return;
}
console.info("isInvalid", isInvalid);
setIsInvalid(!element.checkValidity());
setUrl(value + ":" + port);
};
const onPaste = (e: ClipboardEvent) => {
const text = e.clipboardData.getData("text");
try {
new URL(text);
setUrl(text);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_) {
// Nothing to do here
}
};
const onPortChange = (e: React.FormEvent<HTMLInputElement>) => {
const [address] = Strings.splitURLAndPort(url);
const element = e.currentTarget;
const value = element.value;
setUrl(address + ":" + value);
};
const onSave = () => {
if (isInvalid === true) {
return;
}
CodexSdk.updateURL(url)
.then(() => queryClient.invalidateQueries())
.then(() => codex.refetch());
};
let forwardingPortValue = defaultPort;
if (codex.isSuccess && codex.data) {
const port = DebugUtils.getTcpPort(codex.data);
if (!port.error) {
forwardingPortValue = port.data;
}
}
const displayName = OnBoardingUtils.getDisplayName();
const [address, port] = Strings.splitURLAndPort(url);
return (
<>
<div className="index-column-section onboarding--deviceCheck-block">
<div>
<AlphaIcon variant="primary" />
</div>
<SimpleText variant="primary" className="onboarding-deviceCheck">
<span>
Connection /<br />
Device Health Check
</span>
</SimpleText>
</div>
<div className="index-column-section">
<h3 className="index-mainTitle onboarding-displayName">
Nice to meet {displayName},<br />
Lets establish our connection.
</h3>
<div className="onboarding-group">
<div className="onboarding-addressAndPort">
<div className="onboarding-addressContainer">
<Input
onPaste={onPaste}
id="url"
type="url"
label="Address"
isInvalid={isInvalid}
onChange={onAddressChange}
value={address}
placeholder="127.0.0.1"></Input>
{isInvalid ? (
<ErrorCircleIcon className="onboarding--addressSuccessIcon" />
) : (
<SuccessCheckIcon
className="onboarding--addressSuccessIcon"
variant="default"
/>
)}
</div>
<div className="onboarding-portContainer">
<Input
inputClassName="onboarding-port"
id="port"
label="Port"
type="number"
onChange={onPortChange}
value={port}
placeholder="8080"></Input>
<SuccessCheckIcon
className="onboarding--addressSuccessIcon"
variant="default"></SuccessCheckIcon>
</div>
<RefreshIcon
onClick={onSave}
className={classnames(
["onboarding-refresh"],
[
"onboarding-refresh--fetching",
portForwarding.isFetching || codex.isPending,
]
)}></RefreshIcon>
</div>
</div>
<ul className="onboarding-portForwardingHelp">
<li>Port forwarding should be default {forwardingPortValue}.</li>
</ul>
<div className="onboarding-healthChecks">
<div className="onboarding-healthCheck-item">
<span className="onboarding-healthCheck-icon">
<HealthCheckIcon />
</span>
<span className="onboarding-healthCheck-itemText">
Health Check
</span>
</div>
<div className="onboarding-healthCheck-item">
<HealthCheckItem
value={online ? "success" : "failure"}
text="Internet connection"
/>
</div>
<div className="onboarding-healthCheck-item">
<HealthCheckItem
value={portForwarding.enabled ? "success" : "failure"}
text="Port forwarding"
/>
</div>
<div className="onboarding-healthCheck-item">
<HealthCheckItem
value={codex.isSuccess ? "success" : "failure"}
text="Codex connection"
/>
</div>
<div className="onboarding-healthCheck-item">
<HealthCheckItem
value={codex.isSuccess ? "success" : "warning"}
text="Marketplace"
/>
</div>
</div>
</div>
</>
);
}

View File

@ -1,44 +0,0 @@
.onboarding-personalization {
margin-top: 1rem;
font-family: Azeret Mono;
font-size: 14px;
font-weight: 400;
line-height: 16.34px;
display: inline-block;
}
.onboarding--personalization-block {
flex: 0.3;
}
.onboarding-addressAndPort {
display: flex;
gap: 1.5rem;
align-items: center;
}
.onboarding-port {
width: 150px;
}
.onboarding-port::-webkit-outer-spin-button,
.onboarding-port::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
.onboarding-port[type="number"] {
-moz-appearance: textfield;
}
.onboarding-portForwardingHelp {
font-family: Azeret Mono;
font-size: 12px;
font-weight: 400;
line-height: 14px;
color: #828282;
padding-left: 1.25rem;
margin-top: 1.75rem;
margin-bottom: 3rem;
}

View File

@ -1,46 +0,0 @@
import { Input, SimpleText } from "@codex-storage/marketplace-ui-components";
import { ChangeEvent, useState } from "react";
import { AlphaIcon } from "./AlphaIcon";
import "./OnBoardingStepTwo.css";
import { OnBoardingUtils } from "../../utils/onboarding";
type Props = {
onStepValid: (valid: boolean) => void;
};
export function OnBoardingStepTwo({ onStepValid }: Props) {
const [displayName, setDisplayName] = useState("");
const onDisplayNameChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.currentTarget.value;
OnBoardingUtils.setDisplayName(value);
setDisplayName(value);
onStepValid(!!value);
};
return (
<>
<div className="index-column-section onboarding--personalization-block">
<div>
<AlphaIcon variant="primary" />
</div>
<SimpleText variant="primary" className="onboarding-personalization">
<span>Personalization</span>
</SimpleText>
</div>
<div className="index-column-section">
<h3 className="index-mainTitle">
Lets get you setup. <br />
What do you want to be called?
</h3>
<div className="index-displayName">
<Input
onChange={onDisplayNameChange}
label="Display name"
id="displayName"
value={displayName}></Input>
</div>
</div>
</>
);
}

View File

@ -1,35 +0,0 @@
import { ReactNode, useState } from "react";
import { Menu, MenuItem } from "../Menu/Menu";
import { AppBar } from "../AppBar/AppBar";
import "./page.css";
type Props = {
children: ReactNode;
items: MenuItem[];
version?: string;
};
export function Page({ children, items, version = "" }: Props) {
const [open, setOpen] = useState(false);
const onClose = () => setOpen(false);
const onExpand = () => setOpen(true);
return (
<div className="page">
<Menu
expanded={open}
onClose={onClose}
items={items}
version={version}></Menu>
<main className="page-main">
<AppBar onExpand={onExpand} />
{children}
</main>
</div>
);
}

View File

@ -1,8 +0,0 @@
.page {
display: flex;
flex: 1;
}
.page-main {
flex: 1;
}

View File

@ -1,5 +1,11 @@
.peerCountry { .peer-country {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
span:first-child {
background-color: #141414;
border-radius: 50%;
padding: 12px;
}
} }

View File

@ -56,7 +56,7 @@ export function PeerCountryCell({ address, onPinAdd }: Props) {
return ( return (
<Cell> <Cell>
<div className="peerCountry"> <div className="peer-country">
{data ? ( {data ? (
<> <>
<span> {!!data && getFlagEmoji(data.country_iso)}</span> <span> {!!data && getFlagEmoji(data.country_iso)}</span>

View File

@ -1,13 +1,11 @@
type Props = { type Props = {
variant: "primary" | "default"; variant: "primary" | "default";
className?: string;
}; };
export function SuccessCheckIcon({ variant, className = "" }: Props) { export function SuccessCheckIcon({ variant }: Props) {
const color = variant === "primary" ? "#1FC16B" : "#444444"; const color = variant === "primary" ? "#1FC16B" : "#444444";
return ( return (
<span className={"onboarding-healthCheck-icon " + className}>
<svg <svg
width="16" width="16"
height="16" height="16"
@ -19,6 +17,5 @@ export function SuccessCheckIcon({ variant, className = "" }: Props) {
fill={color} fill={color}
/> />
</svg> </svg>
</span>
); );
} }

View File

@ -1,10 +1,9 @@
import { SimpleText } from "@codex-storage/marketplace-ui-components";
import { CircleCheck } from "lucide-react"; import { CircleCheck } from "lucide-react";
export function SuccessIcon() { export function SuccessIcon() {
return ( return (
<SimpleText variant="primary"> <span className="text--primary">
<CircleCheck size="4rem" className="successIcon" /> <CircleCheck size="4rem" className="successIcon" />
</SimpleText> </span>
); );
} }

View File

@ -0,0 +1,22 @@
.user-info {
display: flex;
align-items: center;
gap: 32px;
.emoji {
position: relative;
aside {
position: absolute;
top: -140px;
left: 116px;
z-index: 1;
}
.input input {
width: 64px;
text-align: center;
cursor: pointer;
}
}
}

View File

@ -0,0 +1,80 @@
import { ChangeEvent, useState } from "react";
import "./UserInfo.css";
import { OnBoardingUtils } from "../../utils/onboarding";
import { Input } from "@codex-storage/marketplace-ui-components";
import EmojiPicker, {
EmojiClickData,
EmojiStyle,
Theme,
} from "emoji-picker-react";
type Props = {
onNameChange?: (value: string) => void;
};
export function UserInfo({ onNameChange }: Props) {
const [displayName, setDisplayName] = useState(
OnBoardingUtils.getDisplayName()
);
const [emoji, setEmoji] = useState(OnBoardingUtils.getEmoji());
const [areEmojiVisible, setAreEmojiVisible] = useState(false);
const onDisplayNameChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.currentTarget.value;
OnBoardingUtils.setDisplayName(value);
setDisplayName(value);
onNameChange?.(value);
};
const onDisplayEmoji = () => setAreEmojiVisible(!areEmojiVisible);
const onEmojiClick = (emojiData: EmojiClickData) => {
setEmoji(emojiData.emoji);
OnBoardingUtils.setEmoji(emojiData.emoji);
setAreEmojiVisible(false);
};
return (
<div className="user-info">
<div className="emoji">
{areEmojiVisible && (
<EmojiPicker
emojiStyle={EmojiStyle.NATIVE}
theme={Theme.DARK}
lazyLoadEmojis={true}
onEmojiClick={onEmojiClick}
categories={
[
"smileys_people",
"animals_nature",
"food_drink",
"travel_places",
"activities",
"objects",
"symbols",
"flags",
] as any
}
/>
)}
<div>
<Input
onChange={onDisplayNameChange}
onClick={onDisplayEmoji}
label="Account Emoji"
readOnly={true}
id="emoji"
value={emoji}></Input>
</div>
</div>
<div>
<Input
onChange={onDisplayNameChange}
label="Preferred name"
id="displayName"
value={displayName}></Input>
</div>
</div>
);
}

View File

@ -0,0 +1,34 @@
.versions {
display: flex;
gap: 32px;
> svg {
position: relative;
top: 6px;
}
> div {
width: 50px;
text-align: right;
p {
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
color: #99a0ae;
}
small {
font-family: Inter;
font-size: 10px;
font-weight: 400;
line-height: 12.1px;
letter-spacing: 0.01em;
color: white;
text-transform: uppercase;
white-space: nowrap;
}
}
}

View File

@ -0,0 +1,28 @@
import { useDebug } from "../../hooks/useDebug";
import { AlphaText } from "../AlphaText/AlphaText";
import { AlphaIcon } from "../OnBoarding/AlphaIcon";
import "./Versions.css";
const throwOnError = true;
export function Versions() {
const debug = useDebug(throwOnError);
const parts = debug.data?.codex.version.split("\n") || [""];
const version = parts[parts.length - 1];
return (
<div className="versions">
<AlphaIcon variant="error" />
<div>
<p>Client</p>
<small>VER. {version}</small>
</div>
<div>
<p>Vault</p>
<small>VER. {import.meta.env.PACKAGE_VERSION}</small>
<AlphaText variant="failure" width={37}></AlphaText>
</div>
</div>
);
}

View File

@ -0,0 +1,15 @@
export function WarningIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M8 15.5C3.85775 15.5 0.5 12.1423 0.5 8C0.5 3.85775 3.85775 0.5 8 0.5C12.1423 0.5 15.5 3.85775 15.5 8C15.5 12.1423 12.1423 15.5 8 15.5ZM4.25 7.25V8.75H11.75V7.25H4.25Z"
fill="#FF8447"
/>
</svg>
);
}

View File

@ -1,4 +1,3 @@
import { SimpleText } from "@codex-storage/marketplace-ui-components";
import "./Welcome.css"; import "./Welcome.css";
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import { ChevronRight } from "lucide-react"; import { ChevronRight } from "lucide-react";
@ -8,12 +7,12 @@ export function Welcome() {
<div className="welcome"> <div className="welcome">
<p className="welcome-title">Welcome to Codex Marketplace</p> <p className="welcome-title">Welcome to Codex Marketplace</p>
<div className="welcome-body"> <div className="welcome-body">
<SimpleText variant="light"> <span>
Begin your journey with Codex by uploading new files for testing. Begin your journey with Codex by uploading new files for testing.
Experience the power of our decentralized data storage platform and Experience the power of our decentralized data storage platform and
explore its features. Your feedback is invaluable as we continue to explore its features. Your feedback is invaluable as we continue to
improve! improve!
</SimpleText> </span>
</div> </div>
<Link to="/dashboard/help" className="welcome-link"> <Link to="/dashboard/help" className="welcome-link">

View File

@ -46,6 +46,7 @@
--codex-input-border-color: #494949; --codex-input-border-color: #494949;
--codex-input-background: #232323; --codex-input-background: #232323;
--codex-input-color-error: #fb3748; --codex-input-color-error: #fb3748;
--codex-row-gap: 16px;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
@ -136,11 +137,6 @@ main {
max-width: 100%; max-width: 100%;
} }
hr {
border: 0.1px solid var(--codex-border-color);
width: 100%;
}
ul { ul {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -158,12 +154,6 @@ pre {
word-break: break-word; word-break: break-word;
} }
a {
text-decoration: inherit;
color: var(--codex-color);
transition: opacity 0.35s;
}
a[aria-disabled] { a[aria-disabled] {
opacity: 0.6; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
@ -175,10 +165,6 @@ a[aria-disabled] {
max-width: 100%; max-width: 100%;
} }
.page {
max-width: 100%;
}
input:-webkit-autofill, input:-webkit-autofill,
input:-webkit-autofill:hover, input:-webkit-autofill:hover,
input:-webkit-autofill:focus, input:-webkit-autofill:focus,
@ -186,3 +172,12 @@ input:-webkit-autofill:active {
-webkit-transition-delay: 9999s; -webkit-transition-delay: 9999s;
transition-delay: 9999s; transition-delay: 9999s;
} }
.row {
display: flex;
align-items: center;
}
.gap {
gap: var(--codex-row-gap);
}

View File

@ -11,6 +11,8 @@
// Import Routes // Import Routes
import { Route as rootRoute } from './routes/__root' import { Route as rootRoute } from './routes/__root'
import { Route as OnboardingNameImport } from './routes/onboarding-name'
import { Route as OnboardingChecksImport } from './routes/onboarding-checks'
import { Route as DashboardImport } from './routes/dashboard' import { Route as DashboardImport } from './routes/dashboard'
import { Route as IndexImport } from './routes/index' import { Route as IndexImport } from './routes/index'
import { Route as DashboardIndexImport } from './routes/dashboard/index' import { Route as DashboardIndexImport } from './routes/dashboard/index'
@ -32,6 +34,18 @@ import { Route as DashboardAboutImport } from './routes/dashboard/about'
// Create/Update Routes // Create/Update Routes
const OnboardingNameRoute = OnboardingNameImport.update({
id: '/onboarding-name',
path: '/onboarding-name',
getParentRoute: () => rootRoute,
} as any)
const OnboardingChecksRoute = OnboardingChecksImport.update({
id: '/onboarding-checks',
path: '/onboarding-checks',
getParentRoute: () => rootRoute,
} as any)
const DashboardRoute = DashboardImport.update({ const DashboardRoute = DashboardImport.update({
id: '/dashboard', id: '/dashboard',
path: '/dashboard', path: '/dashboard',
@ -158,6 +172,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardImport preLoaderRoute: typeof DashboardImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/onboarding-checks': {
id: '/onboarding-checks'
path: '/onboarding-checks'
fullPath: '/onboarding-checks'
preLoaderRoute: typeof OnboardingChecksImport
parentRoute: typeof rootRoute
}
'/onboarding-name': {
id: '/onboarding-name'
path: '/onboarding-name'
fullPath: '/onboarding-name'
preLoaderRoute: typeof OnboardingNameImport
parentRoute: typeof rootRoute
}
'/dashboard/about': { '/dashboard/about': {
id: '/dashboard/about' id: '/dashboard/about'
path: '/about' path: '/about'
@ -320,6 +348,8 @@ const DashboardRouteWithChildren = DashboardRoute._addFileChildren(
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/dashboard': typeof DashboardRouteWithChildren '/dashboard': typeof DashboardRouteWithChildren
'/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute '/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute '/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesRoute '/dashboard/availabilities': typeof DashboardAvailabilitiesRoute
@ -340,6 +370,8 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute '/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute '/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesRoute '/dashboard/availabilities': typeof DashboardAvailabilitiesRoute
@ -362,6 +394,8 @@ export interface FileRoutesById {
__root__: typeof rootRoute __root__: typeof rootRoute
'/': typeof IndexRoute '/': typeof IndexRoute
'/dashboard': typeof DashboardRouteWithChildren '/dashboard': typeof DashboardRouteWithChildren
'/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute '/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute '/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesRoute '/dashboard/availabilities': typeof DashboardAvailabilitiesRoute
@ -385,6 +419,8 @@ export interface FileRouteTypes {
fullPaths: fullPaths:
| '/' | '/'
| '/dashboard' | '/dashboard'
| '/onboarding-checks'
| '/onboarding-name'
| '/dashboard/about' | '/dashboard/about'
| '/dashboard/analytics' | '/dashboard/analytics'
| '/dashboard/availabilities' | '/dashboard/availabilities'
@ -404,6 +440,8 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: to:
| '/' | '/'
| '/onboarding-checks'
| '/onboarding-name'
| '/dashboard/about' | '/dashboard/about'
| '/dashboard/analytics' | '/dashboard/analytics'
| '/dashboard/availabilities' | '/dashboard/availabilities'
@ -424,6 +462,8 @@ export interface FileRouteTypes {
| '__root__' | '__root__'
| '/' | '/'
| '/dashboard' | '/dashboard'
| '/onboarding-checks'
| '/onboarding-name'
| '/dashboard/about' | '/dashboard/about'
| '/dashboard/analytics' | '/dashboard/analytics'
| '/dashboard/availabilities' | '/dashboard/availabilities'
@ -446,11 +486,15 @@ export interface FileRouteTypes {
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
DashboardRoute: typeof DashboardRouteWithChildren DashboardRoute: typeof DashboardRouteWithChildren
OnboardingChecksRoute: typeof OnboardingChecksRoute
OnboardingNameRoute: typeof OnboardingNameRoute
} }
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
DashboardRoute: DashboardRouteWithChildren, DashboardRoute: DashboardRouteWithChildren,
OnboardingChecksRoute: OnboardingChecksRoute,
OnboardingNameRoute: OnboardingNameRoute,
} }
export const routeTree = rootRoute export const routeTree = rootRoute
@ -466,7 +510,9 @@ export const routeTree = rootRoute
"filePath": "__root.tsx", "filePath": "__root.tsx",
"children": [ "children": [
"/", "/",
"/dashboard" "/dashboard",
"/onboarding-checks",
"/onboarding-name"
] ]
}, },
"/": { "/": {
@ -493,6 +539,12 @@ export const routeTree = rootRoute
"/dashboard/" "/dashboard/"
] ]
}, },
"/onboarding-checks": {
"filePath": "onboarding-checks.tsx"
},
"/onboarding-name": {
"filePath": "onboarding-name.tsx"
},
"/dashboard/about": { "/dashboard/about": {
"filePath": "dashboard/about.tsx", "filePath": "dashboard/about.tsx",
"parent": "/dashboard" "parent": "/dashboard"

View File

@ -1,200 +1,25 @@
import { createFileRoute, Link, Outlet } from "@tanstack/react-router"; import { createFileRoute, Outlet } from "@tanstack/react-router";
import "./dashboard.css"; import "./layout.css";
import { Page } from "../components/Page/Page"; import { Menu } from "../components/Menu/Menu";
import { HomeIcon } from "../components/Menu/HomeIcon"; import { useState } from "react";
import { WalletIcon } from "../components/Menu/WalletIcon"; import { AppBar } from "../components/AppBar/AppBar";
import { NodesIcon } from "../components/Menu/NodesIcon";
import { FilesIcon } from "../components/Menu/FilesIcon";
import { AnalyticsIcon } from "../components/Menu/AnalyticsIcon";
import { PurchaseIcon } from "../components/Menu/PurchaseIcon";
import { PeersIcon } from "../components/Menu/PeersIcon";
import { LogsIcon } from "../components/Menu/LogsIcon";
import { MenuItem, MenuItemComponentProps } from "../components/Menu/Menu";
import { HelpIcon } from "../components/Menu/HelpIcon";
import { DisclaimerIcon } from "../components/Menu/DisclaimerIcon";
import { SettingsIcon } from "../components/Menu/SettingsIcon";
import { HostIcon } from "../components/Menu/HostIcon";
import { DeviceIcon } from "../components/Menu/DeviceIcon";
const Layout = () => { const Layout = () => {
const items = [ const [open, setOpen] = useState(false);
{
type: "item", const onClose = () => setOpen(false);
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard" activeOptions={{ exact: true }} {...p}> const onExpand = () => setOpen(true);
<span className="menu-icon">
<HomeIcon />
</span>
<span className="menu-text">Dashboard</span>
</Link>
),
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard/wallet" {...p}>
<span className="menu-icon">
<WalletIcon />
</span>
<span className="menu-text">Wallet</span>
</Link>
),
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard/files" {...p}>
<span className="menu-icon">
<FilesIcon />
</span>
<span className="menu-text">Files</span>
</Link>
),
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link
to="/dashboard/nodes"
{...p}
disabled={true}
aria-disabled={true}
data-title="Coming soon">
<span className="menu-icon">
<NodesIcon variant="default" />
</span>
<span className="menu-text">Nodes</span>
</Link>
),
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link
to="/dashboard/analytics"
{...p}
disabled={true}
aria-disabled={true}
title="Coming soon"
data-title="Coming soon">
<span className="menu-icon">
<AnalyticsIcon />
</span>
<span className="menu-text">Analytics</span>
</Link>
),
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link
to="/dashboard/device"
{...p}
disabled={true}
aria-disabled={true}
title="Coming soon"
data-title="Coming soon">
<span className="menu-icon">
<DeviceIcon />
</span>
<span className="menu-text">Devices</span>
</Link>
),
},
{
type: "separator",
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard/purchases" {...p}>
<span className="menu-icon">
<PurchaseIcon />
</span>
<span className="menu-text">Purchases</span>
</Link>
),
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard/availabilities" {...p}>
<span className="menu-icon">
<HostIcon />
</span>
<span className="menu-text">Host</span>
</Link>
),
},
{
type: "separator",
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard/peers" {...p}>
<span className="menu-icon">
<PeersIcon />
</span>
<span className="menu-text">Peers</span>
</Link>
),
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard/logs" {...p}>
<span className="menu-icon">
<LogsIcon />
</span>
<span className="menu-text">Log</span>
</Link>
),
},
{
type: "space",
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard/settings" {...p}>
<span className="menu-icon">
<SettingsIcon />
</span>
<span className="menu-text">Settings</span>
</Link>
),
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard/help" {...p}>
<span className="menu-icon">
<HelpIcon />
</span>
<span className="menu-text">Help</span>
</Link>
),
},
{
type: "item",
Component: (p: MenuItemComponentProps) => (
<Link to="/dashboard/disclaimer" {...p}>
<span className="menu-icon">
<DisclaimerIcon />
</span>
<span className="menu-text">Disclaimer</span>
</Link>
),
},
] satisfies MenuItem[];
return ( return (
<Page <div className="layout">
children={<Outlet />} <Menu expanded={open} onClose={onClose}></Menu>
items={items}
version={import.meta.env.PACKAGE_VERSION} <main>
/> <AppBar onExpand={onExpand} />
<Outlet />
</main>
</div>
); );
}; };

View File

@ -1,15 +1,9 @@
.dashboard-welcomeContainer { .dashboard {
padding: 24px 48px; padding: 24px 48px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
}
.dashboard-welcome-avatarContainer { h3 {
display: flex;
gap: 16px;
}
.dashboard-welcome-avatarTitle {
font-family: Inter; font-family: Inter;
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
@ -17,18 +11,25 @@
letter-spacing: 0.01em; letter-spacing: 0.01em;
color: #969696cc; color: #969696cc;
text-transform: uppercase; text-transform: uppercase;
} }
.dashboard-welcome-avatarSubtitle { h4 {
font-family: Inter; font-family: Inter;
font-size: 32px; font-size: 32px;
font-weight: 400; font-weight: 400;
line-height: 38.73px; line-height: 38.73px;
letter-spacing: 0.01em; letter-spacing: 0.01em;
color: white; color: white;
} }
.dashboard-welcome-alpha { .emoji {
position: relative; border-radius: 50%;
top: 6px; width: 52px;
height: 52px;
background-color: #4a9a73;
display: flex;
align-items: center;
justify-content: center;
font-size: 26px;
}
} }

View File

@ -10,19 +10,14 @@ import { Download } from "../../components/Download/Download.tsx";
import { ManifestFetch } from "../../components/ManifestFetch/ManifestFetch.tsx"; import { ManifestFetch } from "../../components/ManifestFetch/ManifestFetch.tsx";
import { OnBoardingUtils } from "../../utils/onboarding.ts"; import { OnBoardingUtils } from "../../utils/onboarding.ts";
import "./index.css"; import "./index.css";
import { AlphaIcon } from "../../components/OnBoarding/AlphaIcon.tsx"; import { Versions } from "../../components/Versions/Versions.tsx";
import { AlphaText } from "../../components/AlphaText/AlphaText.tsx";
import { useDebug } from "../../hooks/useDebug.ts";
export const Route = createFileRoute("/dashboard/")({ export const Route = createFileRoute("/dashboard/")({
component: Dashboard, component: Dashboard,
}); });
const throwOnError = false;
function Dashboard() { function Dashboard() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const debug = useDebug(throwOnError);
const onSuccess = () => { const onSuccess = () => {
queryClient.invalidateQueries({ queryKey: ["cids"] }); queryClient.invalidateQueries({ queryKey: ["cids"] });
@ -30,62 +25,21 @@ function Dashboard() {
const username = OnBoardingUtils.getDisplayName(); const username = OnBoardingUtils.getDisplayName();
const parts = debug.data?.codex.version.split("\n") || [""]; const emoji = OnBoardingUtils.getEmoji();
const version = parts[parts.length - 1];
return ( return (
<> <>
<div className="dashboard-welcomeContainer">
<div className="dashboard-welcome-avatarContainer">
<picture>
<source
srcSet="/img/avatar@4x.webp 4x,
/img/avatar@3x.webp 3x,
/img/avatar@2x.webp 2x,
/img/avatar@1.5x.webp 1.5x,
/img/avatar.webp 1x"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
33vw"
type="image/webp"
/>
<source
srcSet="/img/avatar@4x.png 4x,
/img/avatar@3x.png 3x,
/img/avatar@2x.png 2x,
/img/avatar@1.5x.png 1.5x,
/img/avatar.png 1x"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
33vw"
type="image/png"
/>
<img src="/img/avatar.png" height={52} width={52} alt="Avatar" />
</picture>
<div>
<div className="dashboard-welcome-avatarTitle">Welcome back,</div>
<div className="dashboard-welcome-avatarSubtitle">{username}</div>
</div>
</div>
<div className="dashboard-welcome-versions">
<AlphaIcon variant="error" className="dashboard-welcome-alpha" />
<div className="dashboard-welcome-versionContainer">
<p className="dashboard-welcome-versionTitle">Client</p>
<p className="dashboard-welcome-versionValue">VER. {version}</p>
</div>
<div className="dashboard-welcome-versionContainer">
<p className="dashboard-welcome-versionTitle">Vault</p>
<p className="dashboard-welcome-versionValue">
VER. {import.meta.env.PACKAGE_VERSION}
</p>
<AlphaText
variant="failure"
className="dashboard-welcome-alphaText"
width={37}></AlphaText>
</div>
</div>
</div>
<div className="dashboard"> <div className="dashboard">
<div className="row gap">
<div className="emoji">{emoji}</div>
<div>
<h3>Welcome back,</h3>
<h4>{username}</h4>
</div>
</div>
<Versions />
</div>
<div>
<div> <div>
<ErrorBoundary <ErrorBoundary
fallback={({ error }) => ( fallback={({ error }) => (

View File

@ -1,41 +1,228 @@
.peers-map {
max-width: 1000px;
width: 100%;
}
.peers-table {
margin-top: 1rem;
width: calc(100% - 4rem);
max-width: calc(1000px - 4rem);
}
.peers { .peers {
display: flex; max-width: 1320px;
flex-direction: column; margin-left: auto;
align-items: center; margin-right: auto;
padding-bottom: 4rem; width: calc(100% - 2 * 24px);
padding-left: 2rem; padding: 24px;
padding-right: 2rem;
}
.peers circle[fill="#d6ff79"] { > div {
/* fill: yellow; */ max-width: 1320px;
animation: dash 3s linear infinite; }
stroke: white;
> div:first-child {
width: calc(100% - 128px - 16px);
border: 1px solid #96969633;
padding: 16px 16px 16px 128px;
border-radius: 16px;
position: relative;
height: 600px;
circle[fill="#d6ff79"] {
stroke: var(--codex-color-primary);
stroke-width: 0.6px; stroke-width: 0.6px;
stroke-dasharray: 0.3; fill: #141414;
animation: circle-pulse 1.5s infinite;
}
ul {
list-style-type: none;
width: 71px;
position: absolute;
left: 16px;
top: 16px;
li {
border-bottom: 1px solid #969696cc;
padding: 16px 0;
text-align: right;
}
li:first-child {
font-size: 20px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.006em;
text-align: left;
color: #7b7b7b;
}
li:not(:first-child) {
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
position: relative;
}
li:not(:first-child)::before {
content: " ";
border: 4px solid var(--codex-color-primary);
border-radius: 50%;
height: 8px;
width: 8px;
display: inline-block;
position: absolute;
left: 0;
top: 20px;
}
li:nth-child(3)::before {
border-width: 5px;
height: 11px;
width: 11px;
top: 18px;
}
li:nth-child(4)::before {
border-width: 6px;
height: 12px;
width: 12px;
top: 16px;
}
}
.connections {
background-color: #232323;
border: 1px solid #96969633;
border-radius: 16px;
position: absolute;
bottom: 16px;
left: 16px;
width: 280px;
padding: 16px;
header {
display: flex;
align-items: center;
gap: 8px;
color: #969696;
padding-bottom: 16px;
border-bottom: 1px solid #96969633;
span {
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
color: white;
}
}
main {
> div {
position: relative;
width: 350px;
height: 175px;
overflow: hidden;
transform: scale(0.73);
margin: auto;
left: -32px;
*,
&::before {
box-sizing: border-box;
}
&::before,
&::after {
position: absolute;
}
&::before {
content: "";
width: inherit;
height: inherit;
border: 45px solid #323232;
border-bottom: none;
border-top-left-radius: 175px;
border-top-right-radius: 175px;
}
div {
position: absolute;
top: 100%;
width: inherit;
height: inherit;
border: 45px solid #323232;
border-top: none;
border-bottom-left-radius: 175px;
border-bottom-right-radius: 175px;
transform-origin: 50% 0;
}
div:nth-child(1) {
border-color: var(--codex-color-primary);
transform: rotate(calc(var(--codex-peers-percent) * 1deg));
background-color: transparent;
}
}
span {
font-family: Inter;
font-size: 38.67px;
font-weight: 500;
line-height: 48.34px;
letter-spacing: -0.005em;
text-align: center;
position: absolute;
bottom: 0;
left: calc(50% - 24px);
}
}
footer {
border-top: 1px solid #96969633;
padding-top: 16px;
display: flex;
align-items: center;
gap: 8px;
font-family: Inter;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.006em;
}
}
}
> div:nth-child(2) {
width: calc(100% - 64px);
}
table {
td:last-child {
div {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 8px;
&.status--active {
color: #1daf61;
background-color: #6fcb9433;
}
&.status--inactive {
color: #fb3748;
background-color: #fb374833;
}
}
}
}
} }
@keyframes dash { @keyframes circle-pulse {
from { 0% {
stroke-dashoffset: 2; opacity: 1; /* Fully opaque */
} }
to { 50% {
stroke-dashoffset: 0; r: 2; /* Increased radius */
} opacity: 1; /* Slightly transparent */
} }
@keyframes circleAn { 100% {
to { opacity: 1; /* Fully opaque */
/* stroke-dashoffset: 100px; */
} }
} }

View File

@ -11,10 +11,17 @@ import "./peers.css";
import { CodexSdk } from "../../sdk/codex"; import { CodexSdk } from "../../sdk/codex";
import { ErrorBoundary } from "@sentry/react"; import { ErrorBoundary } from "@sentry/react";
import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder"; import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder";
import { PeersIcon } from "../../components/Menu/PeersIcon";
import { SuccessCheckIcon } from "../../components/SuccessCheckIcon/SuccessCheckIcon";
import { ErrorCircleIcon } from "../../components/ErrorCircleIcon/ErrorCircleIcon";
// This function accepts the same arguments as DottedMap in the example above. // This function accepts the same arguments as DottedMap in the example above.
const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" }); const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" });
type CustomCSSProperties = React.CSSProperties & {
"--codex-peers-percent": number;
};
const Peers = () => { const Peers = () => {
const [pins, setPins] = useState<[PeerPin, number][]>([]); const [pins, setPins] = useState<[PeerPin, number][]>([]);
const { data } = useQuery({ const { data } = useQuery({
@ -61,10 +68,10 @@ const Peers = () => {
); );
const svgMap = map.getSVG({ const svgMap = map.getSVG({
radius: 0.42, radius: 0.32,
color: "#423B38", color: "#969696",
shape: "circle", shape: "circle",
backgroundColor: "#020300", backgroundColor: "#141414",
}); });
const headers = ["Country", "PeerId", "Active"]; const headers = ["Country", "PeerId", "Active"];
@ -79,26 +86,55 @@ const Peers = () => {
<Cell>{node.peerId}</Cell>, <Cell>{node.peerId}</Cell>,
<Cell> <Cell>
{node.seen ? ( {node.seen ? (
<div className="networkIndicator-point networkIndicator-point--online"></div> <div className="status--active">
<SuccessCheckIcon variant="primary"></SuccessCheckIcon> Active
</div>
) : ( ) : (
<div className="networkIndicator-point networkIndicator-point--offline"></div> <div className="status--inactive">
<ErrorCircleIcon></ErrorCircleIcon> Inactive
</div>
)} )}
</Cell>, </Cell>,
]}></Row> ]}></Row>
)) || []; )) || [];
const actives =
data?.table.nodes.reduce((acc, cur) => acc + (cur.seen ? 1 : 0), 0) || 0;
const total = data?.table.nodes.length || 1;
const styles: CustomCSSProperties = {
"--codex-peers-percent": (actives / total) * 180,
};
return ( return (
<div className="peers"> <div className="peers">
{/* <img <div>
src={`data:image/svg+xml;utf8,${encodeURIComponent(svgMap)}`} <ul>
className="peers-map" <li>Legend</li>
/> */} <li>1-3</li>
<li>3-5</li>
<li>5 +</li>
</ul>
<div className="connections">
<header>
<PeersIcon></PeersIcon>
<span>Connections</span>
</header>
<main style={styles}>
<div>
<div></div>
<span>{actives}</span>
</div>
</main>
<footer>
<SuccessCheckIcon variant="primary"></SuccessCheckIcon>{" "}
<span>Peer connections in Good standing. </span>
</footer>
</div>
<div dangerouslySetInnerHTML={{ __html: svgMap }}></div>
</div>
<div <Table headers={headers} rows={rows} />
className="peers-map"
dangerouslySetInnerHTML={{ __html: svgMap }}></div>
<Table headers={headers} rows={rows} className="peers-table" />
</div> </div>
); );
}; };

View File

@ -1,11 +1,49 @@
.settings { .settings {
border-radius: var(--codex-border-radius); padding: 24px 48px;
border: 1px solid var(--codex-border-color);
background-color: var(--codex-background-secondary);
padding: 1rem 1.5rem;
margin: 1rem 1.5rem;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
}
main {
margin-top: 16px;
padding: 16px;
max-width: 800px;
z-index: 1;
position: relative;
h3 {
font-family: Inter;
font-size: 18px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.015em;
margin-bottom: 16px;
}
.user-info {
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid #96969633;
}
}
.background-img {
top: 200px;
right: -400px;
}
.refresh {
top: 17px;
}
.address svg {
top: 55px;
}
}
/*
.settings-title { .settings-title {
font-weight: bold; font-weight: bold;
font-size: 1.125rem; font-size: 1.125rem;
@ -19,4 +57,4 @@
.settings-debug-loader { .settings-debug-loader {
margin: auto; margin: auto;
} } */

View File

@ -1,16 +1,26 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import "./settings.css"; import "./settings.css";
import { LogLevel } from "../../components/LogLevel/LogLevel";
import { Debug } from "../../components/Debug/Debug";
import { CodexUrlSettings } from "../../components/CodexUrllSettings/CodexUrlSettings";
import { ErrorBoundary } from "@sentry/react"; import { ErrorBoundary } from "@sentry/react";
import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder"; import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder";
import { useEffect } from "react"; import { UserInfo } from "../../components/UserInfo/UserInfo";
import { HealthChecks } from "../../components/HealthChecks/HealthChecks";
import { Logotype } from "../../components/Logotype/Logotype";
import { Versions } from "../../components/Versions/Versions";
import { Logo } from "../../components/Logo/Logo";
import { BackgroundImage } from "../../components/BackgroundImage/BackgroundImage";
export const Route = createFileRoute("/dashboard/settings")({ export const Route = createFileRoute("/dashboard/settings")({
component: () => ( component: () => (
<>
<div className="settings"> <div className="settings">
<header>
<div className="row gap">
<Logo height={48}></Logo>
<Logotype height={46}></Logotype>
</div>
<Versions></Versions>
</header>
<main>
<h3>Personalization</h3>
<ErrorBoundary <ErrorBoundary
fallback={({ error }) => ( fallback={({ error }) => (
<ErrorPlaceholder <ErrorPlaceholder
@ -18,11 +28,25 @@ export const Route = createFileRoute("/dashboard/settings")({
subtitle="Cannot retrieve the data." subtitle="Cannot retrieve the data."
/> />
)}> )}>
<LogLevel /> <UserInfo />
</ErrorBoundary> </ErrorBoundary>
</div>
<div className="settings"> <h3>Connection</h3>
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
)}>
<HealthChecks online={true} onStepValid={() => {}} />
</ErrorBoundary>
</main>
<BackgroundImage />
{/* <div className="settings">
<ErrorBoundary <ErrorBoundary
fallback={({ error }) => ( fallback={({ error }) => (
<ErrorPlaceholder <ErrorPlaceholder
@ -57,7 +81,7 @@ export const Route = createFileRoute("/dashboard/settings")({
}}> }}>
<Debug /> <Debug />
</ErrorBoundary> </ErrorBoundary>
</div> </div> */}
{/* <div className="input-floating"> {/* <div className="input-floating">
<input <input
@ -83,6 +107,6 @@ export const Route = createFileRoute("/dashboard/settings")({
Floating Floating
</label> </label>
</div> */} </div> */}
</> </div>
), ),
}); });

View File

@ -1,202 +0,0 @@
.index {
width: 100%;
padding: 3rem 6rem;
}
.index-logo {
position: relative;
}
.index-network {
display: flex;
position: absolute;
top: -19px;
left: -161px;
gap: 4px;
width: 200px;
justify-content: flex-end;
}
.index-network-text {
font-family: Inter;
font-size: 11px;
font-weight: 500;
line-height: 12px;
letter-spacing: 0.02em;
text-transform: uppercase;
color: var(--text-soft-400, #99a0ae);
text-wrap: nowrap;
}
.index-container {
display: flex;
justify-content: space-between;
height: 100%;
}
.index-alphaText {
margin-top: 4px;
}
.index-title {
font-weight: 600;
font-size: 36px;
line-height: 43.2px;
}
.index-version {
font-weight: 500;
opacity: 0.6;
}
.index-link {
text-decoration: underline;
font-family: Azeret Mono;
font-size: 12px;
font-weight: 400;
line-height: 14px;
color: inherit;
margin-top: 16px;
cursor: pointer;
display: inline-flex;
align-items: center;
}
.index-link2:hover,
.index-link:hover {
animation-name: example;
animation-duration: 2.5s;
animation-iteration-count: infinite;
}
@keyframes example {
0% {
filter: brightness(1);
-webkit-filter: brightness(1);
}
30% {
filter: brightness(0.7);
-webkit-filter: brightness(0.7);
}
100% {
filter: brightness(1);
-webkit-filter: brightness(1);
}
}
.index-mainTitle {
font-family: Inter;
font-size: 36px;
font-weight: 300;
line-height: 43.57px;
letter-spacing: 0.01em;
}
.index-codex {
font-weight: 400;
}
.index-vault {
font-weight: 900;
text-transform: uppercase;
}
.index-description {
font-family: Azeret Mono;
font-size: 14px;
font-weight: 400;
line-height: 16.34px;
max-width: 532px;
margin-top: 20px;
}
.index-subtitle {
font-weight: 900;
font-size: 16px;
text-transform: uppercase;
line-height: 19.36px;
color: var(--codex-color-primary);
}
.index-getStarted {
font-size: 24px;
font-weight: 600;
line-height: 29.05px;
letter-spacing: 0.01em;
margin-top: 32px;
font-family: Inter;
color: #7bfbaf;
gap: 4px;
text-decoration: none;
border-bottom: 2px solid #7bfbaf;
}
.index-columnRight,
.index-column {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.index-column-section {
max-width: 500px;
}
.index-column-section {
flex: 1;
}
.index-column-section:first-child {
flex: 0.5;
}
.index-column-section:last-child {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.index-dots {
display: flex;
gap: 8px;
}
.index-dot {
width: 12px;
height: 12px;
background-color: white;
display: inline-block;
border-radius: 50%;
transition: opacity 0.35s;
}
.index-dot:not(.index-dot--active) {
opacity: 0.4;
}
.index-dot:hover {
animation-name: pulse;
animation-duration: 2.5s;
animation-iteration-count: infinite;
}
.index-dot--active {
box-shadow: 0px 0px 12px 0px #fff;
opacity: 1;
}
.index-displayName {
margin-top: 1rem;
}
@keyframes pulse {
0% {
opacity: 0.4;
}
30% {
opacity: 0.8;
}
100% {
opacity: 0.4;
}
}

View File

@ -1,16 +1,11 @@
import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { createFileRoute, useNavigate } from "@tanstack/react-router";
import "./index.css";
import { ArrowRightCircle } from "../components/ArrowRightCircle/ArrowRightCircle";
import { useNetwork } from "../network/useNetwork";
import { Logotype } from "../components/Logotype/Logotype";
import { useState } from "react"; import { useState } from "react";
import { OnBoardingStepOne } from "../components/OnBoarding/OnBoardingStepOne"; import { Modal } from "@codex-storage/marketplace-ui-components";
import { OnBoardingStepTwo } from "../components/OnBoarding/OnBoardingStepTwo"; import { ArrowRight } from "lucide-react";
import { classnames } from "../utils/classnames"; import { AlphaText } from "../components/AlphaText/AlphaText";
import { OnBoardingStepThree } from "../components/OnBoarding/OnBoardingStepThree"; import { AlphaIcon } from "../components/OnBoarding/AlphaIcon";
import { attributes } from "../utils/attributes"; import { OnBoardingLayout } from "../components/OnBoarding/OnBoardingLayout";
import { OnBoardingImage } from "../components/OnBoarding/OnBoardingImage"; import { ArrowRightCircle } from "../components/ArrowRightCircle/ArrowRightCircle";
import { OnBoardingUtils } from "../utils/onboarding";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
component: Index, component: Index,
@ -22,87 +17,80 @@ export const Route = createFileRoute("/")({
}); });
function Index() { function Index() {
const [isStepValid, setIsStepValid] = useState(true); const [modal, setModal] = useState(false);
// const [step, setStep] = useState(OnBoardingUtils.getStep());
const [step, setStep] = useState(0);
const online = useNetwork();
const navigate = useNavigate({ from: "/" }); const navigate = useNavigate({ from: "/" });
const onStepValid = (valid: boolean) => setIsStepValid(valid);
const onNextStep = () => { const onLegalDisclaimerOpen = () => setModal(true);
if (!isStepValid) {
return;
}
if (step === 2) { const onLegalDisclaimerClose = () => setModal(false);
navigate({ to: "/dashboard" });
return;
}
OnBoardingUtils.setStep(step + 1); const onNextStep = () => navigate({ to: "/onboarding-name" });
setStep(step + 1);
setIsStepValid(false);
};
const components = [
<OnBoardingStepOne onNextStep={onNextStep} />,
<OnBoardingStepTwo onStepValid={onStepValid} />,
<OnBoardingStepThree online={online} onStepValid={onStepValid} />,
];
// const text = online ? "Network connected" : "Network disconnected";
return ( return (
<div className="index"> <>
<div className="index-container"> <OnBoardingLayout defaultIsStepValid={false} step={0}>
<div className="index-column"> <>
<div className="index-column-section"> <section className="alpha">
<Logotype width={111} /> <AlphaIcon variant="error" />
<div>
<AlphaText variant="default"></AlphaText>
<b>{import.meta.env.PACKAGE_VERSION}</b>
<a onClick={onLegalDisclaimerOpen}>Legal Disclaimer</a>
</div> </div>
</section>
<section className="main">
<h1>
Hello,
<br /> Welcome to <b>Codex</b> <b>Vault</b>
</h1>
<p>
Codex is a durable, decentralised data storage protocol, created
so the world community can preserve its most important knowledge
without risk of censorship.
</p>
</section>
<section className="get-started">
<a onClick={onNextStep}>
Lets get started <ArrowRight></ArrowRight>
</a>
{components[step]} <Modal onClose={onLegalDisclaimerClose} open={modal}>
<h1>Disclaimer</h1>
<div className=" "> <p>
<div className="index-dots"> The website and the content herein is not intended for public
<span use and is for informational and demonstration purposes only.
className={classnames( </p>
["index-dot"],
["index-dot--active", step === 0] <br />
)}></span>
<span <p>
className={classnames( The website and any associated functionalities are provided on
["index-dot"], an as is basis without any guarantees, warranties, or
["index-dot--active", step === 1] representations of any kind, either express or implied. The
)}></span> website and any associated functionalities may not reflect the
<span final version of the project and is subject to changes, updates,
className={classnames( or removal at any time and without notice.
["index-dot"], </p>
["index-dot--active", step === 2]
)}></span> <br />
</div>
</div> <p>
</div> By accessing and using this website, you agree that we, Logos
<div className="index-column"> Collective Association and its affiliates, will not be liable
<OnBoardingImage /> for any direct, indirect, incidental, or consequential damages
</div> arising from the use of, or inability to use, this website. Any
<div className="index-columnRight"> data, content, or interactions on this site are non-binding and
<div></div> should not be considered final or actionable. Your use of this
{/* <div className="index-logo"> website is at your sole risk.
<div className="index-network"> </p>
<p className="index-network-text">{text}</p> </Modal>
<NetworkIcon active={online}></NetworkIcon> </section>
</div> <a className="navigation" onClick={onNextStep}>
<CodexLogo></CodexLogo>
</div> */}
<a
className="index-link2"
{...attributes({ "aria-disabled": !isStepValid })}
onClick={onNextStep}>
<ArrowRightCircle></ArrowRightCircle> <ArrowRightCircle></ArrowRightCircle>
</a> </a>
</div> </>
</div> </OnBoardingLayout>
</div> </>
); );
} }

View File

@ -1,8 +1,12 @@
.dashboard { .layout {
padding: 1.5rem; display: flex;
display: grid; flex: 1;
grid-template-columns: 1fr; max-width: 100%;
gap: 0.75rem;
> main {
flex: 1;
background-color: #141414;
}
} }
.dashboard-download { .dashboard-download {
@ -51,9 +55,3 @@
text-transform: uppercase; text-transform: uppercase;
white-space: nowrap; white-space: nowrap;
} }
@media (min-width: 1000px) {
.dashboard {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}

View File

@ -0,0 +1,61 @@
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import { AlphaIcon } from "../components/OnBoarding/AlphaIcon";
import { OnBoardingUtils } from "../utils/onboarding";
import { attributes } from "../utils/attributes";
import { ArrowRightCircle } from "../components/ArrowRightCircle/ArrowRightCircle";
import { OnBoardingLayout } from "../components/OnBoarding/OnBoardingLayout";
import { HealthChecks } from "../components/HealthChecks/HealthChecks";
import { useNetwork } from "../network/useNetwork";
const OnBoardingChecks = () => {
const online = useNetwork();
const displayName = OnBoardingUtils.getDisplayName();
const [isStepValid, setIsStepValid] = useState(false);
const navigate = useNavigate({ from: "/onboarding-checks" });
const onNextStep = () => {
if (isStepValid) {
navigate({ to: "/onboarding-checks" });
}
};
const onStepValid = (valid: boolean) => setIsStepValid(valid);
return (
<OnBoardingLayout defaultIsStepValid={false} step={2}>
<>
<section className="alpha">
<div>
<AlphaIcon variant="primary" />
</div>
<p>
Connection /<br />
Device Health Check
</p>
</section>
<section className="main">
<h1>
Nice to meet {displayName},<br />
Lets establish our connection.
</h1>
<HealthChecks
online={online}
onStepValid={onStepValid}></HealthChecks>
</section>
<a
className="navigation"
onClick={onNextStep}
{...attributes({ "aria-disabled": !isStepValid })}>
<ArrowRightCircle></ArrowRightCircle>
</a>
</>
</OnBoardingLayout>
);
};
export const Route = createFileRoute("/onboarding-checks")({
component: OnBoardingChecks,
});

View File

@ -0,0 +1,55 @@
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import { AlphaIcon } from "../components/OnBoarding/AlphaIcon";
import { OnBoardingLayout } from "../components/OnBoarding/OnBoardingLayout";
import { OnBoardingUtils } from "../utils/onboarding";
import { attributes } from "../utils/attributes";
import { ArrowRightCircle } from "../components/ArrowRightCircle/ArrowRightCircle";
import { UserInfo } from "../components/UserInfo/UserInfo";
const OnBoardingName = () => {
const [isStepValid, setIsStepValid] = useState(
!!OnBoardingUtils.getDisplayName()
);
const navigate = useNavigate({ from: "/onboarding-name" });
const onNameChange = (value: string) => setIsStepValid(!!value);
const onNextStep = () => {
if (isStepValid) {
navigate({ to: "/onboarding-checks" });
}
};
return (
<OnBoardingLayout defaultIsStepValid={false} step={1}>
<>
<section className="alpha">
<div>
<AlphaIcon variant="primary" />
</div>
<p>Personalization</p>
</section>
<section className="main">
<h1>
Lets get you setup. <br />
What do you want to be called?
</h1>
<UserInfo onNameChange={onNameChange} />
</section>
<a
className="navigation"
onClick={onNextStep}
{...attributes({ "aria-disabled": !isStepValid })}>
<ArrowRightCircle></ArrowRightCircle>
</a>
</>
</OnBoardingLayout>
);
};
export const Route = createFileRoute("/onboarding-name")({
component: OnBoardingName,
});

View File

@ -1,3 +1,4 @@
export const OnBoardingUtils = { export const OnBoardingUtils = {
getStep() { getStep() {
return parseInt(localStorage.getItem("onboarding-step") || "0", 10) return parseInt(localStorage.getItem("onboarding-step") || "0", 10)
@ -13,5 +14,13 @@ export const OnBoardingUtils = {
getDisplayName() { getDisplayName() {
return localStorage.getItem("display-name") || "" return localStorage.getItem("display-name") || ""
} },
setEmoji(emoji: string) {
localStorage.setItem("emoji", emoji)
},
getEmoji() {
return localStorage.getItem("emoji") || "🤖"
},
} }

View File

@ -19,7 +19,13 @@ export default defineConfig({
defaultHandler(warning); defaultHandler(warning);
}, },
output: {
manualChunks: {
"emoji-picker-react": ["emoji-picker-react"]
}
}
}, },
}, },
resolve: { resolve: {
alias: { alias: {