diff --git a/package.json b/package.json index 2fb07e2f..4e6d7577 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a14f5b6f..599c558e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index bbd2c7ba..79541e78 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -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", diff --git a/src/app.tsx b/src/app.tsx index d6984047..c8d6e80d 100644 --- a/src/app.tsx +++ b/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'); @@ -169,46 +168,16 @@ export default function App() { }, }, { - path: 'import', - element: , + path: 'create', + element: , errorElement: , - 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: , - errorElement: , - children: [ - { - path: '', - async lazy() { - const { CreateStep1Screen } = await import('@app/auth/create/step-1'); - return { Component: CreateStep1Screen }; - }, - }, - { - path: 'step-2', - async lazy() { - const { CreateStep2Screen } = await import('@app/auth/create/step-2'); - return { Component: CreateStep2Screen }; - }, - }, - ], + path: 'import', + async lazy() { + const { ImportAccountScreen } = await import('@app/auth/import'); + return { Component: ImportAccountScreen }; + }, }, { path: 'onboarding', diff --git a/src/app/auth/create/index.tsx b/src/app/auth/create/index.tsx index 15be94d7..c7581843 100644 --- a/src/app/auth/create/index.tsx +++ b/src/app/auth/create/index.tsx @@ -1,9 +1,5 @@ import { Outlet } from 'react-router-dom'; -export function AuthCreateScreen() { - return ( -
- -
- ); +export function CreateAccountScreen() { + return ; } diff --git a/src/app/auth/create/step-1.tsx b/src/app/auth/create/step-1.tsx index 0bdd06a4..4955776f 100644 --- a/src/app/auth/create/step-1.tsx +++ b/src/app/auth/create/step-1.tsx @@ -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 () => { - setLoading(true); + try { + setLoading(true); + setPubkey(pubkey); - // 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); + // save to database + await db.createAccount(npub, pubkey); - // redirect to next step - navigate('/auth/create/step-2', { replace: true }); + // redirect to next step + navigate('/auth/create/step-2', { replace: true }); + } catch (e) { + await message(e, { title: 'Something went wrong!', type: 'error' }); + } }; useEffect(() => { @@ -81,70 +84,86 @@ export function CreateStep1Screen() { }, []); return ( -
-
-

- This is your new Nostr account -

-

- 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. -

-

- Public key is used for sharing with other people so that they can find you using - the public key. -

-
-
+
+
+
+

+ This is your new Nostr account +

+

+ 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. + +

+

+ Public key is used for sharing with other people so that they can find you + using the public key. +

+
-
- Private Key -
+
+
+ +
+ + +
+
+
+ -
- Public Key - + + + + By clicking 'Continue', you are ensuring that your keys are saved + in a safe place. You cannot recover these keys if they are lost. +
-
- - - - By clicking 'Continue', you are ensuring that your keys are saved in - a safe place. You cannot recover these keys if they are lost. - -
); diff --git a/src/app/auth/import.tsx b/src/app/auth/import.tsx new file mode 100644 index 00000000..25a7ecf6 --- /dev/null +++ b/src/app/auth/import.tsx @@ -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(''); + const [nsec, setNsec] = useState(''); + const [pubkey, setPubkey] = useState(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 ( +
+
+

+ Import your Nostr account +

+
+
+
+ +
+ 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 ? ( + + ) : null} +
+
+
+ {pubkey ? ( + +
Account found
+
+
+ +
+ {!created ? ( +
+ + +
+ ) : null} +
+
+ ) : null} + {created ? ( + <> + +
+ +
+ 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" + /> + +
+
+
+

+ nsec 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. +

+
+ 1. In case you store nsec in Lume +
+

+ 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 +

+
+ 2. In case you do not store nsec in Lume +
+

+ 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. +

+
+
+ + Finish + + + ) : null} +
+
+
+ ); +} diff --git a/src/app/auth/import/index.tsx b/src/app/auth/import/index.tsx deleted file mode 100644 index bf8983ff..00000000 --- a/src/app/auth/import/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Outlet } from 'react-router-dom'; - -export function AuthImportScreen() { - return ( -
- -
- ); -} diff --git a/src/app/auth/import/step-1.tsx b/src/app/auth/import/step-1.tsx deleted file mode 100644 index 5d642878..00000000 --- a/src/app/auth/import/step-1.tsx +++ /dev/null @@ -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 = 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({ 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 ( -
-
-

- Import your Nostr key -

-
-
-
-
- -
- - -
- - {errors.privkey &&

{errors.privkey.message}

} -
-
-
- -
-
-
-
- ); -} diff --git a/src/app/auth/import/step-2.tsx b/src/app/auth/import/step-2.tsx deleted file mode 100644 index 38df3069..00000000 --- a/src/app/auth/import/step-2.tsx +++ /dev/null @@ -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 ( -
-
-

- {loading ? 'Downloading...' : 'Your Nostr profile'} -

-
-
-
- -
-
- - - By clicking 'Continue', Lume will download your old relay list and - metadata. It may take a bit - -
-
-
- ); -} diff --git a/src/libs/storage/instance.ts b/src/libs/storage/instance.ts index b3ca6acf..37cbb0d3 100644 --- a/src/libs/storage/instance.ts +++ b/src/libs/storage/instance.ts @@ -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; } diff --git a/src/main.jsx b/src/main.jsx index c82bc34f..e5eb2f24 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -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( + diff --git a/src/shared/user.tsx b/src/shared/user.tsx index 3d71680c..17c32f63 100644 --- a/src/shared/user.tsx +++ b/src/shared/user.tsx @@ -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" /> {pubkey}
-

+

{user?.name || user?.display_name || user?.displayName}