diff --git a/bun.lockb b/bun.lockb
index 78029912..d25561ea 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 30586057..f7163016 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,6 @@
"**/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
},
"dependencies": {
- "@ctrl/magnet-link": "^3.1.2",
"@getalby/sdk": "^2.4.0",
"@nostr-dev-kit/ndk": "^1.0.0",
"@nostr-fetch/adapter-ndk": "^0.12.2",
@@ -38,7 +37,6 @@
"@tiptap/react": "^2.1.8",
"@tiptap/starter-kit": "^2.1.8",
"@tiptap/suggestion": "^2.1.8",
- "@void-cat/api": "^1.0.7",
"dayjs": "^1.11.9",
"destr": "^2.0.1",
"get-urls": "^12.1.0",
@@ -53,7 +51,6 @@
"react-currency-input-field": "^3.6.11",
"react-dom": "^18.2.0",
"react-hook-form": "^7.46.1",
- "react-hotkeys-hook": "^4.4.1",
"react-markdown": "^8.0.7",
"react-player": "^2.13.0",
"react-router-dom": "^6.15.0",
@@ -64,7 +61,6 @@
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1",
"tauri-plugin-stronghold-api": "github:tauri-apps/tauri-plugin-stronghold#v1",
"tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload#v1",
- "tippy.js": "^6.3.7",
"zustand": "^4.4.1"
},
"devDependencies": {
diff --git a/src/app.tsx b/src/app.tsx
index 9c09fa2f..cd9c8897 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -205,15 +205,15 @@ const router = createBrowserRouter([
return { Component: OnboardStep2Screen };
},
},
- {
- path: 'step-3',
- async lazy() {
- const { OnboardStep3Screen } = await import('@app/auth/onboarding/step-3');
- return { Component: OnboardStep3Screen };
- },
- },
],
},
+ {
+ path: 'complete',
+ async lazy() {
+ const { CompleteScreen } = await import('@app/auth/complete');
+ return { Component: CompleteScreen };
+ },
+ },
{
path: 'unlock',
async lazy() {
@@ -235,13 +235,6 @@ const router = createBrowserRouter([
return { Component: ResetScreen };
},
},
- {
- path: 'hard-reset',
- async lazy() {
- const { HardResetScreen } = await import('@app/auth/hardReset');
- return { Component: HardResetScreen };
- },
- },
],
},
{
diff --git a/src/app/auth/complete.tsx b/src/app/auth/complete.tsx
new file mode 100644
index 00000000..5a3a3291
--- /dev/null
+++ b/src/app/auth/complete.tsx
@@ -0,0 +1,43 @@
+import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+export function CompleteScreen() {
+ const navigate = useNavigate();
+ const [count, setCount] = useState(3);
+
+ useEffect(() => {
+ let counter: NodeJS.Timeout;
+
+ if (count > 0) {
+ counter = setTimeout(() => setCount(count - 1), 1000);
+ }
+
+ if (count === 0) {
+ navigate('/', { replace: true });
+ }
+
+ return () => {
+ clearTimeout(counter);
+ };
+ }, [count]);
+
+ return (
+
+
+
+ You're ready , redirecting in {count}
+ ...
+
+
+ Thank you for using Lume. Lume doesn't use telemetry. If you encounter any
+ problems, please submit a report via the "Report Issue" button.
+
+ You can find it while using the application.
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/auth/components/user.tsx b/src/app/auth/components/user.tsx
index 66175688..bfded070 100644
--- a/src/app/auth/components/user.tsx
+++ b/src/app/auth/components/user.tsx
@@ -1,7 +1,9 @@
+import { Link } from 'react-router-dom';
+
+import { WorldIcon } from '@shared/icons';
import { Image } from '@shared/image';
import { useProfile } from '@utils/hooks/useProfile';
-import { displayNpub } from '@utils/shortenKey';
export function User({ pubkey, fallback }: { pubkey: string; fallback?: string }) {
const { status, user } = useProfile(pubkey, fallback);
@@ -9,7 +11,7 @@ export function User({ pubkey, fallback }: { pubkey: string; fallback?: string }
if (status === 'loading') {
return (
-
+
@@ -19,19 +21,33 @@ export function User({ pubkey, fallback }: { pubkey: string; fallback?: string }
}
return (
-
+
-
-
- {user?.name || user?.display_name}
-
-
- {displayNpub(pubkey, 16)}
-
+
+
+
+ {user?.name || user?.display_name}
+
+
+ {user?.about || user?.bio || 'No bio'}
+
+
+
+ {user?.website ? (
+
+
+
{user.website}
+
+ ) : null}
+
);
diff --git a/src/app/auth/components/userImport.tsx b/src/app/auth/components/userImport.tsx
new file mode 100644
index 00000000..8801f8fe
--- /dev/null
+++ b/src/app/auth/components/userImport.tsx
@@ -0,0 +1,38 @@
+import { Image } from '@shared/image';
+
+import { useProfile } from '@utils/hooks/useProfile';
+import { displayNpub } from '@utils/shortenKey';
+
+export function UserImport({ pubkey }: { pubkey: string }) {
+ const { status, user } = useProfile(pubkey);
+
+ if (status === 'loading') {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+ {user?.name || user?.display_name}
+
+
+ {user?.nip05 || user?.username || displayNpub(pubkey, 16)}
+
+
+
+ );
+}
diff --git a/src/app/auth/create/step-1.tsx b/src/app/auth/create/step-1.tsx
index 1276cec6..48c39bef 100644
--- a/src/app/auth/create/step-1.tsx
+++ b/src/app/auth/create/step-1.tsx
@@ -1,13 +1,14 @@
-import { BaseDirectory, writeTextFile } from '@tauri-apps/api/fs';
+import { writeText } from '@tauri-apps/api/clipboard';
+import { message, save } from '@tauri-apps/api/dialog';
+import { writeTextFile } from '@tauri-apps/api/fs';
+import { downloadDir } from '@tauri-apps/api/path';
import { generatePrivateKey, getPublicKey, nip19 } from 'nostr-tools';
import { useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useStorage } from '@libs/storage/provider';
-import { Button } from '@shared/button';
-import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons';
-import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
+import { CopyIcon } from '@shared/icons';
import { useOnboarding } from '@stores/onboarding';
import { useStronghold } from '@stores/stronghold';
@@ -21,8 +22,8 @@ export function CreateStep1Screen() {
const setPubkey = useOnboarding((state) => state.setPubkey);
const setStep = useOnboarding((state) => state.setStep);
- const [privkeyInput, setPrivkeyInput] = useState('password');
const [loading, setLoading] = useState(false);
+ const [copied, setCopied] = useState(false);
const [downloaded, setDownloaded] = useState(false);
const privkey = useMemo(() => generatePrivateKey(), []);
@@ -30,27 +31,39 @@ export function CreateStep1Screen() {
const npub = nip19.npubEncode(pubkey);
const nsec = nip19.nsecEncode(privkey);
- // toggle private key
- const showPrivateKey = () => {
- if (privkeyInput === 'password') {
- setPrivkeyInput('text');
- } else {
- setPrivkeyInput('password');
+ const download = async () => {
+ try {
+ const downloadPath = await downloadDir();
+ const fileName = `nostr_keys_${new Date().toISOString()}.txt`;
+ const filePath = await save({
+ defaultPath: downloadPath + '/' + fileName,
+ });
+
+ if (filePath) {
+ await writeTextFile(
+ filePath,
+ `Generated by Lume (lume.nu)\nPublic key: ${npub}\nPrivate key: ${nsec}`
+ );
+
+ setDownloaded(true);
+ } // else { user cancel action }
+ } catch (e) {
+ await message(e, { title: 'Cannot download account keys', type: 'error' });
}
};
- const download = async () => {
- await writeTextFile(
- `nostr_keys_${new Date().toISOString().slice(0, 10)}.txt`,
- `Generated by Lume (lume.nu)\nPublic key: ${npub}\nPrivate key: ${nsec}`,
- {
- dir: BaseDirectory.Download,
- }
- );
- setDownloaded(true);
+ 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 = () => {
+ const submit = async () => {
setLoading(true);
// update state
@@ -59,7 +72,7 @@ export function CreateStep1Screen() {
setPubkey(pubkey);
// save to database
- db.createAccount(npub, pubkey);
+ await db.createAccount(npub, pubkey);
// redirect to next step
navigate('/auth/create/step-2', { replace: true });
@@ -72,76 +85,68 @@ export function CreateStep1Screen() {
return (
-
-
Save your access 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.
+
-
-
- Public Key
-
-
-
-
Private Key
-
+
+
+
+
Private Key
+
+
+ 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"
+ >
+
+ {copied ? 'Copied' : 'Copy'}
+
+
+
+
+ Public Key
- showPrivateKey()}
- className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 backdrop-blur-xl hover:bg-white/10"
- >
- {privkeyInput === 'password' ? (
-
- ) : (
-
- )}
-
-
-
-
- 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.
-
submit()}
- className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
+ onClick={() => download()}
+ className="inline-flex h-12 w-full items-center justify-center rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
>
- {loading ? (
- <>
-
- Creating...
-
- >
- ) : (
- <>
-
- I have saved my key, continue
-
- >
- )}
+ {downloaded ? 'Downloaded' : 'Download account keys'}
- {downloaded ? (
-
- Saved in Download folder
-
- ) : (
-
download()}>
- Download
-
- )}
+
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"
+ >
+ {loading ? 'Creating...' : 'Continue'}
+
+
+ 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/create/step-2.tsx b/src/app/auth/create/step-2.tsx
index 34586420..84e5dfab 100644
--- a/src/app/auth/create/step-2.tsx
+++ b/src/app/auth/create/step-2.tsx
@@ -86,10 +86,16 @@ export function CreateStep2Screen() {
return (
-
-
+
+
Set password to secure your key
+
+ Password is not related to your Nostr account. It is only used to secure your
+ keys stored on your local machine and to unlock the app (like unlocking your
+ phone with a passcode). When you move to other Nostr clients, you just need to
+ copy your private key.
+
-
-
- Password is use to secure your key store in local machine, when you move
- to other clients, you just need to copy your private key as nsec or
- hexstring
-
-
{errors.password && {errors.password.message}
}
@@ -127,12 +127,12 @@ export function CreateStep2Screen() {
{loading ? (
<>
- Creating...
+ Securing your account...
>
) : (
diff --git a/src/app/auth/create/step-3.tsx b/src/app/auth/create/step-3.tsx
index e22364b0..e444cf17 100644
--- a/src/app/auth/create/step-3.tsx
+++ b/src/app/auth/create/step-3.tsx
@@ -3,6 +3,8 @@ import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
+import { useStorage } from '@libs/storage/provider';
+
import { AvatarUploader } from '@shared/avatarUploader';
import { BannerUploader } from '@shared/bannerUploader';
import { LoaderIcon } from '@shared/icons';
@@ -10,6 +12,7 @@ import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
import { Image } from '@shared/image';
import { useOnboarding } from '@stores/onboarding';
+import { WidgetKinds } from '@stores/widgets';
import { useNostr } from '@utils/hooks/useNostr';
@@ -21,6 +24,7 @@ export function CreateStep3Screen() {
const [picture, setPicture] = useState('https://void.cat/d/5VKmKyuHyxrNMf9bWSVPih');
const [banner, setBanner] = useState('');
+ const { db } = useStorage();
const { publish } = useNostr();
const {
register,
@@ -45,6 +49,9 @@ export function CreateStep3Screen() {
tags: [],
});
+ // create default widget
+ await db.createWidget(WidgetKinds.other.learnNostr, 'Learn Nostr', '');
+
if (event) {
navigate('/auth/onboarding', { replace: true });
}
@@ -61,15 +68,22 @@ export function CreateStep3Screen() {
return (
-
-
Create your profile
+
+
+ Personalize your Nostr profile
+
+
+ Nostr profile is synchronous across all Nostr clients. If you create a profile
+ on Lume, it will also work well with other Nostr clients. If you update your
+ profile on another Nostr client, it will also sync to Lume.
+