mirror of
https://github.com/luminous-devs/lume.git
synced 2024-10-02 09:50:47 +00:00
wip: new import account
This commit is contained in:
parent
620e763380
commit
cd3b9ada5a
@ -20,8 +20,8 @@
|
||||
"dependencies": {
|
||||
"@evilmartians/harmony": "^1.1.0",
|
||||
"@getalby/sdk": "^2.4.0",
|
||||
"@nostr-dev-kit/ndk": "^2.0.1",
|
||||
"@nostr-dev-kit/ndk-cache-dexie": "^2.0.1",
|
||||
"@nostr-dev-kit/ndk": "^2.0.2",
|
||||
"@nostr-dev-kit/ndk-cache-dexie": "^2.0.2",
|
||||
"@nostr-fetch/adapter-ndk": "^0.12.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
@ -56,6 +56,7 @@
|
||||
"@tiptap/suggestion": "^2.1.12",
|
||||
"dayjs": "^1.11.10",
|
||||
"destr": "^2.0.1",
|
||||
"framer-motion": "^10.16.4",
|
||||
"html-to-text": "^9.0.5",
|
||||
"light-bolt11-decoder": "^3.0.0",
|
||||
"lru-cache": "^10.0.1",
|
||||
@ -75,6 +76,7 @@
|
||||
"react-string-replace": "^1.1.1",
|
||||
"reactflow": "^11.9.3",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sonner": "^1.0.3",
|
||||
"tailwind-scrollbar": "^3.0.5",
|
||||
"tauri-controls": "^0.2.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
|
@ -12,14 +12,14 @@ dependencies:
|
||||
specifier: ^2.4.0
|
||||
version: 2.4.0
|
||||
'@nostr-dev-kit/ndk':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1(typescript@5.2.2)
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2(typescript@5.2.2)
|
||||
'@nostr-dev-kit/ndk-cache-dexie':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1(typescript@5.2.2)
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2(typescript@5.2.2)
|
||||
'@nostr-fetch/adapter-ndk':
|
||||
specifier: ^0.12.2
|
||||
version: 0.12.2(@nostr-dev-kit/ndk@2.0.1)(nostr-fetch@0.13.0)
|
||||
version: 0.12.2(@nostr-dev-kit/ndk@2.0.2)(nostr-fetch@0.13.0)
|
||||
'@radix-ui/react-alert-dialog':
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5(@types/react-dom@18.2.13)(@types/react@18.2.28)(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -119,6 +119,9 @@ dependencies:
|
||||
destr:
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
framer-motion:
|
||||
specifier: ^10.16.4
|
||||
version: 10.16.4(react-dom@18.2.0)(react@18.2.0)
|
||||
html-to-text:
|
||||
specifier: ^9.0.5
|
||||
version: 9.0.5
|
||||
@ -176,6 +179,9 @@ dependencies:
|
||||
remark-gfm:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
sonner:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3(react-dom@18.2.0)(react@18.2.0)
|
||||
tailwind-scrollbar:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5(tailwindcss@3.3.3)
|
||||
@ -551,6 +557,20 @@ packages:
|
||||
'@babel/helper-validator-identifier': 7.22.20
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
/@emotion/is-prop-valid@0.8.8:
|
||||
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@emotion/memoize': 0.7.4
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@emotion/memoize@0.7.4:
|
||||
resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@esbuild/android-arm64@0.18.20:
|
||||
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
|
||||
engines: {node: '>=12'}
|
||||
@ -920,10 +940,10 @@ packages:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.15.0
|
||||
|
||||
/@nostr-dev-kit/ndk-cache-dexie@2.0.1(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-O1ngV95yuZPhV0PB6JQAMHQkZvGtcW6qEY1jawvrZCfYLf2vdHWuzMN2rXYiSdrx6mMsnqB17bq5Lg3r8Coslw==}
|
||||
/@nostr-dev-kit/ndk-cache-dexie@2.0.2(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-v6dq82Gzw/AoDMtkjCeTg+gx9n6sX3xReaMpIUbKL5W+E7z1lqnR0RmYkeNxUhd7Tlg0FQ+Ywqq7nZs+UmGDEA==}
|
||||
dependencies:
|
||||
'@nostr-dev-kit/ndk': 2.0.1(typescript@5.2.2)
|
||||
'@nostr-dev-kit/ndk': 2.0.2(typescript@5.2.2)
|
||||
debug: 4.3.4
|
||||
dexie: 3.2.4
|
||||
nostr-tools: 1.16.0(typescript@5.2.2)
|
||||
@ -933,8 +953,8 @@ packages:
|
||||
- typescript
|
||||
dev: false
|
||||
|
||||
/@nostr-dev-kit/ndk@2.0.1(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-LZ7h4HL2B0Yek3Pr276OMaiVzr6WYXSWExZKn8bdpZ5lIzt5t1j4bi8kxwfUZti1Z/nIY7Hq7tIguty39YBs/g==}
|
||||
/@nostr-dev-kit/ndk@2.0.2(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-EwaOJVS0FOCXlIffiVceKrK+QtbaRTG6QYdoQchMAe+ag2C3jl7nAoDTWlixv/WgJOFl4KPQkS8r0sEkGmXsjQ==}
|
||||
dependencies:
|
||||
'@noble/hashes': 1.3.2
|
||||
'@noble/secp256k1': 2.0.0
|
||||
@ -952,13 +972,13 @@ packages:
|
||||
- typescript
|
||||
dev: false
|
||||
|
||||
/@nostr-fetch/adapter-ndk@0.12.2(@nostr-dev-kit/ndk@2.0.1)(nostr-fetch@0.13.0):
|
||||
/@nostr-fetch/adapter-ndk@0.12.2(@nostr-dev-kit/ndk@2.0.2)(nostr-fetch@0.13.0):
|
||||
resolution: {integrity: sha512-+7EVuxS5DDZvNo6qbfFp7xRHwIyjyi36hYkiQFDjbQ4gX5LKo9RIPB1P+1XGkOSDFshypTbovZCaFunscJ/zhQ==}
|
||||
peerDependencies:
|
||||
'@nostr-dev-kit/ndk': ^0.7.5
|
||||
nostr-fetch: ^0.12.2
|
||||
dependencies:
|
||||
'@nostr-dev-kit/ndk': 2.0.1(typescript@5.2.2)
|
||||
'@nostr-dev-kit/ndk': 2.0.2(typescript@5.2.2)
|
||||
'@nostr-fetch/kernel': 0.12.2
|
||||
nostr-fetch: 0.13.0
|
||||
dev: false
|
||||
@ -3082,7 +3102,7 @@ packages:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.22.1
|
||||
caniuse-lite: 1.0.30001547
|
||||
caniuse-lite: 1.0.30001549
|
||||
fraction.js: 4.3.7
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.0
|
||||
@ -3134,7 +3154,7 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001547
|
||||
caniuse-lite: 1.0.30001549
|
||||
electron-to-chromium: 1.4.554
|
||||
node-releases: 2.0.13
|
||||
update-browserslist-db: 1.0.13(browserslist@4.22.1)
|
||||
@ -3163,8 +3183,8 @@ packages:
|
||||
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
/caniuse-lite@1.0.30001547:
|
||||
resolution: {integrity: sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==}
|
||||
/caniuse-lite@1.0.30001549:
|
||||
resolution: {integrity: sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==}
|
||||
|
||||
/case-anything@2.1.13:
|
||||
resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==}
|
||||
@ -3997,6 +4017,24 @@ packages:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
dev: true
|
||||
|
||||
/framer-motion@10.16.4(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-p9V9nGomS3m6/CALXqv6nFGMuFOxbWsmaOrdmhyQimMIlLl3LC7h7l86wge/Js/8cRu5ktutS/zlzgR7eBOtFA==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
tslib: 2.6.2
|
||||
optionalDependencies:
|
||||
'@emotion/is-prop-valid': 0.8.8
|
||||
dev: false
|
||||
|
||||
/fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
@ -5464,7 +5502,7 @@ packages:
|
||||
dependencies:
|
||||
lilconfig: 2.1.0
|
||||
postcss: 8.4.31
|
||||
yaml: 2.3.2
|
||||
yaml: 2.3.3
|
||||
|
||||
/postcss-nested@6.0.1(postcss@8.4.31):
|
||||
resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==}
|
||||
@ -5801,7 +5839,7 @@ packages:
|
||||
remark-parse: 10.0.2
|
||||
remark-rehype: 10.1.0
|
||||
space-separated-tokens: 2.0.2
|
||||
style-to-object: 0.4.2
|
||||
style-to-object: 0.4.3
|
||||
unified: 10.1.2
|
||||
unist-util-visit: 4.1.2
|
||||
vfile: 5.3.7
|
||||
@ -6140,6 +6178,16 @@ packages:
|
||||
is-fullwidth-code-point: 4.0.0
|
||||
dev: true
|
||||
|
||||
/sonner@1.0.3(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-hBoA2zKuYW3lUnpx4K0vAn8j77YuYiwvP9sLQfieNS2pd5FkT20sMyPTDJnl9S+5T27ZJbwQRPiujwvDBwhZQg==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/source-map-js@1.0.2:
|
||||
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -6230,8 +6278,8 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/style-to-object@0.4.2:
|
||||
resolution: {integrity: sha512-1JGpfPB3lo42ZX8cuPrheZbfQ6kqPPnPHlKMyeRYtfKD+0jG+QsXgXN57O/dvJlzlB2elI6dGmrPnl5VPQFPaA==}
|
||||
/style-to-object@0.4.3:
|
||||
resolution: {integrity: sha512-RP9icVx0g3Pt0CyNiC2qvBkqMTHD5uBVC2XYcSr/ag8QWKApx/oXEh2ehMGSyzkjK0+ySkukMuO+mz+DNQq57Q==}
|
||||
dependencies:
|
||||
inline-style-parser: 0.1.1
|
||||
dev: false
|
||||
@ -6850,8 +6898,8 @@ packages:
|
||||
engines: {node: '>= 14'}
|
||||
dev: true
|
||||
|
||||
/yaml@2.3.2:
|
||||
resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==}
|
||||
/yaml@2.3.3:
|
||||
resolution: {integrity: sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
/yocto-queue@0.1.0:
|
||||
|
30
src-tauri/Cargo.lock
generated
30
src-tauri/Cargo.lock
generated
@ -1087,6 +1087,7 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@ -3445,6 +3446,12 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@ -3681,14 +3688,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87"
|
||||
checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.1",
|
||||
"regex-syntax 0.8.1",
|
||||
"regex-automata 0.4.2",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3702,13 +3709,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b"
|
||||
checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.1",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3719,9 +3726,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
@ -5322,12 +5329,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe"
|
||||
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa 1.0.9",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
|
43
src/app.tsx
43
src/app.tsx
@ -3,8 +3,7 @@ import { fetch } from '@tauri-apps/plugin-http';
|
||||
import { RouterProvider, createBrowserRouter, defer, redirect } from 'react-router-dom';
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
|
||||
import { AuthCreateScreen } from '@app/auth/create';
|
||||
import { AuthImportScreen } from '@app/auth/import';
|
||||
import { CreateAccountScreen } from '@app/auth/create';
|
||||
import { OnboardingScreen } from '@app/auth/onboarding';
|
||||
import { ChatsScreen } from '@app/chats';
|
||||
import { ErrorScreen } from '@app/error';
|
||||
@ -28,7 +27,7 @@ export default function App() {
|
||||
const totalAccount = await db.checkAccount();
|
||||
|
||||
const onboarding = localStorage.getItem('onboarding');
|
||||
const step = JSON.parse(onboarding).state.step || null;
|
||||
const step = onboarding ? JSON.parse(onboarding).state.step : null;
|
||||
|
||||
// redirect to welcome screen if none user exist
|
||||
if (totalAccount === 0) return redirect('/auth/welcome');
|
||||
@ -168,48 +167,18 @@ export default function App() {
|
||||
return { Component: WelcomeScreen };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'import',
|
||||
element: <AuthImportScreen />,
|
||||
errorElement: <ErrorScreen />,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
async lazy() {
|
||||
const { ImportStep1Screen } = await import('@app/auth/import/step-1');
|
||||
return { Component: ImportStep1Screen };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'step-2',
|
||||
async lazy() {
|
||||
const { ImportStep2Screen } = await import('@app/auth/import/step-2');
|
||||
return { Component: ImportStep2Screen };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
element: <AuthCreateScreen />,
|
||||
element: <CreateAccountScreen />,
|
||||
errorElement: <ErrorScreen />,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
async lazy() {
|
||||
const { CreateStep1Screen } = await import('@app/auth/create/step-1');
|
||||
return { Component: CreateStep1Screen };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'step-2',
|
||||
path: 'import',
|
||||
async lazy() {
|
||||
const { CreateStep2Screen } = await import('@app/auth/create/step-2');
|
||||
return { Component: CreateStep2Screen };
|
||||
const { ImportAccountScreen } = await import('@app/auth/import');
|
||||
return { Component: ImportAccountScreen };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'onboarding',
|
||||
element: <OnboardingScreen />,
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
export function AuthCreateScreen() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
export function CreateAccountScreen() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ export function CreateStep1Screen() {
|
||||
const { db } = useStorage();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const setTempPrivkey = useOnboarding((state) => state.setTempPrivkey);
|
||||
const setPubkey = useOnboarding((state) => state.setPubkey);
|
||||
const setStep = useOnboarding((state) => state.setStep);
|
||||
|
||||
@ -29,6 +28,17 @@ export function CreateStep1Screen() {
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
const nsec = nip19.nsecEncode(privkey);
|
||||
|
||||
const copyPrivkey = async () => {
|
||||
try {
|
||||
await writeText(nsec);
|
||||
setCopied(true);
|
||||
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (e) {
|
||||
await message(e, { title: 'Cannot copy private key', type: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const download = async () => {
|
||||
try {
|
||||
const downloadPath = await downloadDir();
|
||||
@ -50,29 +60,22 @@ export function CreateStep1Screen() {
|
||||
}
|
||||
};
|
||||
|
||||
const copyPrivkey = async () => {
|
||||
try {
|
||||
await writeText(nsec);
|
||||
setCopied(true);
|
||||
|
||||
setTimeout(() => setCopied(false), 3000);
|
||||
} catch (e) {
|
||||
await message(e, { title: 'Cannot copy private key', type: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// update state
|
||||
setTempPrivkey(privkey); // only use if user close app and reopen it
|
||||
setPubkey(pubkey);
|
||||
|
||||
// save privkey
|
||||
await db.secureSave(privkey, pubkey);
|
||||
|
||||
// save to database
|
||||
await db.createAccount(npub, pubkey);
|
||||
|
||||
// redirect to next step
|
||||
navigate('/auth/create/step-2', { replace: true });
|
||||
} catch (e) {
|
||||
await message(e, { title: 'Something went wrong!', type: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -81,71 +84,87 @@ export function CreateStep1Screen() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-4 border-b border-white/10 pb-4">
|
||||
<h1 className="mb-2 text-center text-2xl font-semibold text-white">
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="mx-auto flex w-full max-w-md flex-col gap-10">
|
||||
<div>
|
||||
<h1 className="mb-2 text-center text-2xl font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
This is your new Nostr account
|
||||
</h1>
|
||||
<p className="mb-2 text-white/70">
|
||||
Your private key is your password. If you lose this key, you will lose access to
|
||||
your account! Copy it and keep it in a safe place. There is no way to reset your
|
||||
private key.
|
||||
<p className="mb-2 select-text text-neutral-600 dark:text-neutral-300">
|
||||
Your private key is your password. If you lose this key, you will lose access
|
||||
to your account! Copy it and keep it in a safe place.{' '}
|
||||
<span className="text-red-500">
|
||||
There is no way to reset your private key.
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-white/70">
|
||||
Public key is used for sharing with other people so that they can find you using
|
||||
the public key.
|
||||
<p className="select-text text-neutral-600 dark:text-neutral-300">
|
||||
Public key is used for sharing with other people so that they can find you
|
||||
using the public key.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium text-white">Private Key</span>
|
||||
<label
|
||||
htmlFor="nsec"
|
||||
className="font-medium text-neutral-900 dark:text-neutral-100"
|
||||
>
|
||||
Private Key
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
readOnly
|
||||
name="nsec"
|
||||
value={nsec.substring(0, 5) + '**************************************'}
|
||||
className="relative h-12 w-full rounded-lg border-t border-white/10 bg-white/20 py-1 pl-3.5 pr-11 text-white !outline-none backdrop-blur-xl placeholder:text-white/70"
|
||||
className="relative h-12 w-full rounded-lg bg-neutral-200 py-1 pl-3.5 pr-11 text-neutral-900 !outline-none dark:bg-neutral-800 dark:text-neutral-100"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyPrivkey()}
|
||||
className="group absolute right-2 top-1/2 inline-flex h-7 -translate-y-1/2 transform items-center gap-1.5 rounded-md bg-white/20 px-2.5 text-sm hover:bg-white/30"
|
||||
className="group absolute right-2 top-1/2 inline-flex h-7 -translate-y-1/2 transform items-center gap-1.5 rounded-md bg-neutral-300 px-2.5 text-sm text-neutral-800 hover:bg-neutral-400 hover:text-neutral-900 dark:bg-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-600 dark:hover:text-neutral-100"
|
||||
>
|
||||
<CopyIcon className="h-4 w-4 text-white/70 group-hover:text-white" />
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
{copied ? 'Copied' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium text-white">Public Key</span>
|
||||
<label
|
||||
htmlFor="npub"
|
||||
className="font-medium text-neutral-900 dark:text-neutral-100"
|
||||
>
|
||||
Public Key
|
||||
</label>
|
||||
<input
|
||||
readOnly
|
||||
name="npub"
|
||||
value={npub}
|
||||
className="relative h-12 w-full rounded-lg border-t border-white/10 bg-white/20 px-3.5 py-1 text-white !outline-none backdrop-blur-xl placeholder:text-white/70"
|
||||
className="relative h-12 w-full rounded-lg bg-neutral-200 px-3.5 py-1 text-neutral-900 !outline-none dark:bg-neutral-800 dark:text-neutral-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => download()}
|
||||
className="inline-flex h-12 w-full items-center justify-center rounded-lg bg-blue-500 px-6 font-medium leading-none text-white hover:bg-blue-600 focus:outline-none"
|
||||
className="inline-flex h-10 w-full items-center justify-center rounded-lg bg-blue-500 px-6 font-medium leading-none text-white hover:bg-blue-600 focus:outline-none"
|
||||
>
|
||||
{downloaded ? 'Downloaded' : 'Download account keys'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
className="inline-flex h-12 w-full items-center justify-center rounded-lg border-t border-white/10 bg-white/20 px-6 font-medium leading-none text-white hover:bg-white/30 focus:outline-none"
|
||||
className="inline-flex h-10 w-full items-center justify-center rounded-lg bg-neutral-200 px-6 font-medium leading-none text-neutral-900 hover:bg-neutral-300 focus:outline-none dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-neutral-700"
|
||||
>
|
||||
{loading ? 'Creating...' : 'Continue'}
|
||||
</button>
|
||||
<span className="text-center text-sm text-white/50">
|
||||
By clicking 'Continue', you are ensuring that your keys are saved in
|
||||
a safe place. You cannot recover these keys if they are lost.
|
||||
<span className="select-text text-center text-sm text-neutral-400 dark:text-neutral-600">
|
||||
By clicking 'Continue', you are ensuring that your keys are saved
|
||||
in a safe place. You cannot recover these keys if they are lost.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
225
src/app/auth/import.tsx
Normal file
225
src/app/auth/import.tsx
Normal file
@ -0,0 +1,225 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'sonner';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { User } from '@shared/user';
|
||||
|
||||
export function ImportAccountScreen() {
|
||||
const { db } = useStorage();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [npub, setNpub] = useState<string>('');
|
||||
const [nsec, setNsec] = useState<string>('');
|
||||
const [pubkey, setPubkey] = useState<undefined | string>(undefined);
|
||||
const [created, setCreated] = useState(false);
|
||||
const [savedPrivkey, setSavedPrivkey] = useState(false);
|
||||
|
||||
const submitNpub = async () => {
|
||||
if (npub.length < 6) return toast('You must enter valid npub');
|
||||
if (!npub.startsWith('npub1')) return toast('npub must be starts with npub1');
|
||||
|
||||
try {
|
||||
const pubkey = nip19.decode(npub).data as string;
|
||||
setPubkey(pubkey);
|
||||
} catch (e) {
|
||||
return toast(`npub invalid: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
const changeAccount = async () => {
|
||||
setNpub('');
|
||||
setPubkey('');
|
||||
};
|
||||
|
||||
const createAccount = async () => {
|
||||
try {
|
||||
await db.createAccount(npub, pubkey);
|
||||
setCreated(true);
|
||||
} catch (e) {
|
||||
return toast(`Create account failed: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
const submitNsec = async () => {
|
||||
if (savedPrivkey) return;
|
||||
if (nsec.length > 50 && nsec.startsWith('nsec1')) {
|
||||
try {
|
||||
const privkey = nip19.decode(nsec).data as string;
|
||||
await db.secureSave(pubkey, privkey);
|
||||
setSavedPrivkey(true);
|
||||
} catch (e) {
|
||||
return toast(`nsec invalid: ${e}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const finish = async () => {
|
||||
navigate('/auth/onboarding');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="mx-auto flex w-full max-w-md flex-col gap-10">
|
||||
<h1 className="text-center text-2xl font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
Import your Nostr account
|
||||
</h1>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="rounded-xl bg-neutral-100 p-3 text-neutral-800 dark:bg-neutral-900 dark:text-neutral-200">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="npub" className="font-semibold">
|
||||
Enter your nostr npub:
|
||||
</label>
|
||||
<div className="inline-flex w-full items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={npub}
|
||||
onChange={(e) => setNpub(e.target.value)}
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
placeholder="npub1"
|
||||
className="h-11 flex-1 rounded-lg bg-neutral-200 px-3 placeholder:text-neutral-500 dark:bg-neutral-800 dark:placeholder:text-neutral-400"
|
||||
/>
|
||||
{!pubkey ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={submitNpub}
|
||||
className="h-11 w-24 shrink-0 rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{pubkey ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
}}
|
||||
transition={{ y: { velocity: -100 } }}
|
||||
className="rounded-xl bg-neutral-100 p-3 text-neutral-800 dark:bg-neutral-900 dark:text-neutral-200"
|
||||
>
|
||||
<h5 className="mb-1.5 font-semibold">Account found</h5>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<div className="inline-flex h-full flex-1 items-center rounded-lg bg-neutral-200 p-2">
|
||||
<User pubkey={pubkey} variant="simple" />
|
||||
</div>
|
||||
{!created ? (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={changeAccount}
|
||||
className="h-9 flex-1 shrink-0 rounded-lg bg-neutral-200 font-semibold text-neutral-800 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-200 dark:hover:bg-neutral-700"
|
||||
>
|
||||
Change account
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={createAccount}
|
||||
className="h-9 flex-1 shrink-0 rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</motion.div>
|
||||
) : null}
|
||||
{created ? (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
}}
|
||||
transition={{ y: { velocity: -100 } }}
|
||||
className="rounded-lg bg-neutral-100 p-3 text-neutral-800 dark:bg-neutral-900 dark:text-neutral-200"
|
||||
>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="npub" className="font-semibold">
|
||||
Enter your nostr nsec (optional):
|
||||
</label>
|
||||
<div className="inline-flex w-full items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={nsec}
|
||||
onChange={(e) => setNsec(e.target.value)}
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
placeholder="nsec1"
|
||||
className="h-11 flex-1 rounded-lg bg-neutral-200 px-3 placeholder:text-neutral-500 dark:bg-neutral-800 dark:placeholder:text-neutral-400"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={submitNsec}
|
||||
className={twMerge(
|
||||
'h-11 w-24 shrink-0 rounded-lg font-semibold text-white',
|
||||
!savedPrivkey
|
||||
? 'bg-blue-500 hover:bg-blue-600'
|
||||
: 'bg-teal-500 hover:bg-teal-600'
|
||||
)}
|
||||
>
|
||||
{savedPrivkey ? 'Saved' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 select-text">
|
||||
<p className="text-sm">
|
||||
<b>nsec</b> is used to sign your event. For example, if you want to
|
||||
make a new post or send a message to your contact, you need to use
|
||||
nsec to sign this event.
|
||||
</p>
|
||||
<h5 className="mt-2 font-semibold">
|
||||
1. In case you store nsec in Lume
|
||||
</h5>
|
||||
<p className="text-sm">
|
||||
Lume will put your nsec to{' '}
|
||||
{db.platform === 'macos'
|
||||
? 'Apple Keychain (macOS)'
|
||||
: db.platform === 'windows'
|
||||
? 'Credential Manager (Windows)'
|
||||
: 'Secret Service (Linux)'}
|
||||
, it will be secured by your OS
|
||||
</p>
|
||||
<h5 className="mt-2 font-semibold">
|
||||
2. In case you do not store nsec in Lume
|
||||
</h5>
|
||||
<p className="text-sm">
|
||||
When you make an event that requires a sign by your nsec, Lume will
|
||||
show a prompt popup for you to enter nsec. It will be cleared after
|
||||
signing and not stored anywhere.
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
<motion.button
|
||||
type="button"
|
||||
onClick={finish}
|
||||
initial={{ opacity: 0, y: 80 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
}}
|
||||
transition={{ y: { velocity: -130 } }}
|
||||
className="h-9 w-full shrink-0 rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600"
|
||||
>
|
||||
Finish
|
||||
</motion.button>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
export function AuthImportScreen() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Resolver, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons';
|
||||
import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
|
||||
|
||||
import { useOnboarding } from '@stores/onboarding';
|
||||
|
||||
type FormValues = {
|
||||
privkey: string;
|
||||
};
|
||||
|
||||
const resolver: Resolver<FormValues> = async (values) => {
|
||||
return {
|
||||
values: values.privkey ? values : {},
|
||||
errors: !values.privkey
|
||||
? {
|
||||
privkey: {
|
||||
type: 'required',
|
||||
message: 'This is required.',
|
||||
},
|
||||
}
|
||||
: {},
|
||||
};
|
||||
};
|
||||
|
||||
export function ImportStep1Screen() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [passwordInput, setPasswordInput] = useState('password');
|
||||
const [setStep, setPubkey, setTempPrivkey] = useOnboarding((state) => [
|
||||
state.setStep,
|
||||
state.setPubkey,
|
||||
state.setTempPrivkey,
|
||||
]);
|
||||
|
||||
const { db } = useStorage();
|
||||
const {
|
||||
register,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors, isDirty, isValid },
|
||||
} = useForm<FormValues>({ resolver });
|
||||
|
||||
const onSubmit = async (data: { [x: string]: string }) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
let privkey = data['privkey'];
|
||||
if (privkey.substring(0, 4) === 'nsec') {
|
||||
privkey = nip19.decode(privkey).data as string;
|
||||
}
|
||||
|
||||
if (typeof getPublicKey(privkey) === 'string') {
|
||||
const pubkey = getPublicKey(privkey);
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
|
||||
setTempPrivkey(privkey);
|
||||
setPubkey(pubkey);
|
||||
|
||||
// add account to local database
|
||||
await db.createAccount(npub, pubkey);
|
||||
|
||||
// redirect to step 2 with delay 1.2s
|
||||
setTimeout(() => navigate('/auth/import/step-2', { replace: true }), 1200);
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
setError('privkey', {
|
||||
type: 'custom',
|
||||
message: 'Private key is invalid, please check again',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// toggle private key
|
||||
const showPassword = () => {
|
||||
if (passwordInput === 'password') {
|
||||
setPasswordInput('text');
|
||||
} else {
|
||||
setPasswordInput('password');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// save current step, if user close app and reopen it
|
||||
setStep('/auth/import');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-4 pb-4">
|
||||
<h1 className="text-center text-2xl font-semibold text-white">
|
||||
Import your Nostr key
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="privkey" className="font-medium text-white">
|
||||
Insert your nostr private key, in nsec or hex format
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
{...register('privkey', { required: true, minLength: 32 })}
|
||||
type={passwordInput}
|
||||
placeholder="nsec1..."
|
||||
className="relative h-12 w-full rounded-lg border-t border-white/10 bg-white/20 px-3 py-1 text-white backdrop-blur-xl placeholder:text-white/70 focus:outline-none"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => showPassword()}
|
||||
className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 backdrop-blur-xl hover:bg-white/20"
|
||||
>
|
||||
{passwordInput === 'password' ? (
|
||||
<EyeOffIcon className="h-4 w-4 text-white/50 group-hover:text-white" />
|
||||
) : (
|
||||
<EyeOnIcon className="h-4 w-4 text-white/50 group-hover:text-white" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-sm text-red-500">
|
||||
{errors.privkey && <p>{errors.privkey.message}</p>}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isDirty || !isValid}
|
||||
className="inline-flex h-12 w-full items-center justify-between gap-2 rounded-lg bg-blue-500 px-6 font-medium leading-none text-white hover:bg-blue-600 focus:outline-none"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Importing...</span>
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Continue</span>
|
||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { useOnboarding } from '@stores/onboarding';
|
||||
import { WidgetKinds } from '@stores/widgets';
|
||||
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
|
||||
export function ImportStep2Screen() {
|
||||
const navigate = useNavigate();
|
||||
const setStep = useOnboarding((state) => state.setStep);
|
||||
|
||||
const { db } = useStorage();
|
||||
const { fetchUserData } = useNostr();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
// show loading indicator
|
||||
setLoading(true);
|
||||
|
||||
// prefetch data
|
||||
const user = await fetchUserData();
|
||||
|
||||
// create default widget
|
||||
await db.createWidget(WidgetKinds.other.learnNostr, 'Learn Nostr', '');
|
||||
|
||||
// redirect to next step
|
||||
if (user.status === 'ok') {
|
||||
navigate('/auth/onboarding/step-2', { replace: true });
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('error: ', e);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// save current step, if user close app and reopen it
|
||||
setStep('/auth/import/step-3');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
<div className="mb-4 pb-4">
|
||||
<h1 className="text-center text-2xl font-semibold text-white">
|
||||
{loading ? 'Downloading...' : 'Your Nostr profile'}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="rounded-lg border-t border-white/10 bg-white/20 px-3 py-3">
|
||||
<User pubkey={db.account.pubkey} variant="simple" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-12 w-full items-center justify-between gap-2 rounded-lg bg-blue-500 px-6 font-medium leading-none text-white hover:bg-blue-600 focus:outline-none"
|
||||
onClick={() => submit()}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>It might take a bit, please patient...</span>
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Continue</span>
|
||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<span className="text-center text-sm text-white/50">
|
||||
By clicking 'Continue', Lume will download your old relay list and
|
||||
metadata. It may take a bit
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -19,13 +19,11 @@ export class LumeStorage {
|
||||
}
|
||||
|
||||
public async secureSave(value: string, key?: string) {
|
||||
return await invoke('secure_save', { key: this.account.pubkey ?? key, value });
|
||||
return await invoke('secure_save', { key, value });
|
||||
}
|
||||
|
||||
public async secureLoad(key?: string) {
|
||||
const value: string = await invoke('secure_load', {
|
||||
key: this.account.pubkey ?? key,
|
||||
});
|
||||
const value: string = await invoke('secure_load', { key });
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { Toaster } from 'sonner';
|
||||
|
||||
import { NDKProvider } from '@libs/ndk/provider';
|
||||
import { StorageProvider } from '@libs/storage/provider';
|
||||
@ -15,6 +16,7 @@ root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StorageProvider>
|
||||
<NDKProvider>
|
||||
<Toaster />
|
||||
<App />
|
||||
</NDKProvider>
|
||||
</StorageProvider>
|
||||
|
@ -162,18 +162,18 @@ export const User = memo(function User({
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: 'auto' }}
|
||||
className="h-10 w-10 rounded-lg"
|
||||
className="h-11 w-11 rounded-lg"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={svgURI}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 rounded-lg bg-black dark:bg-white"
|
||||
className="h-11 w-11 rounded-lg bg-black dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<h3 className="max-w-[15rem] truncate font-medium text-neutral-900 dark:text-neutral-100">
|
||||
<h3 className="max-w-[15rem] truncate text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{user?.name || user?.display_name || user?.displayName}
|
||||
</h3>
|
||||
<p className="max-w-[10rem] truncate text-sm text-neutral-900 dark:text-neutral-100/70">
|
||||
|
Loading…
Reference in New Issue
Block a user