From 87099c63885f2cac43e3393555b270f20bb1311a Mon Sep 17 00:00:00 2001 From: reya Date: Sun, 7 Jan 2024 16:39:05 +0700 Subject: [PATCH] feat: update create account flow --- apps/desktop/package.json | 1 + apps/desktop/src/router.tsx | 46 +-- .../src/routes/auth/create-profile.tsx | 5 - apps/desktop/src/routes/auth/create.tsx | 244 ++++++++------ apps/desktop/src/routes/auth/create_old.tsx | 313 ------------------ apps/desktop/src/routes/auth/finish.tsx | 34 -- apps/desktop/src/routes/auth/follow.tsx | 277 ---------------- apps/desktop/src/routes/auth/follows.tsx | 260 +++++++++++++++ apps/desktop/src/routes/auth/onboarding.tsx | 67 ++-- apps/desktop/src/routes/auth/welcome.tsx | 30 +- packages/ark/src/ark.ts | 26 +- packages/ui/src/layouts/auth.tsx | 2 +- packages/ui/src/user.tsx | 6 +- pnpm-lock.yaml | 8 + 14 files changed, 486 insertions(+), 833 deletions(-) delete mode 100644 apps/desktop/src/routes/auth/create-profile.tsx delete mode 100644 apps/desktop/src/routes/auth/create_old.tsx delete mode 100644 apps/desktop/src/routes/auth/finish.tsx delete mode 100644 apps/desktop/src/routes/auth/follow.tsx create mode 100644 apps/desktop/src/routes/auth/follows.tsx diff --git a/apps/desktop/package.json b/apps/desktop/package.json index ad2263a2..1cd02ced 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -59,6 +59,7 @@ "react-router-dom": "^6.21.1", "smol-toml": "^1.1.3", "sonner": "^1.3.1", + "unique-names-generator": "^4.7.1", "virtua": "^0.18.1" }, "devDependencies": { diff --git a/apps/desktop/src/router.tsx b/apps/desktop/src/router.tsx index 8a10a20a..b934ee9b 100644 --- a/apps/desktop/src/router.tsx +++ b/apps/desktop/src/router.tsx @@ -1,7 +1,6 @@ import { useArk, useStorage } from "@lume/ark"; import { LoaderIcon } from "@lume/icons"; import { AppLayout, AuthLayout, HomeLayout, SettingsLayout } from "@lume/ui"; -import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk"; import { fetch } from "@tauri-apps/plugin-http"; import { RouterProvider, @@ -24,7 +23,7 @@ export default function Router() { element: , errorElement: , loader: async () => { - if (!storage.account) return redirect("auth/welcome"); + if (!storage.account) return redirect("auth"); return null; }, children: [ @@ -171,7 +170,7 @@ export default function Router() { errorElement: , children: [ { - path: "welcome", + index: true, async lazy() { const { WelcomeScreen } = await import("./routes/auth/welcome"); return { Component: WelcomeScreen }; @@ -180,23 +179,7 @@ export default function Router() { { path: "create", loader: async () => { - const trusted: NDKEvent[] = []; - - const services = await ark.ndk.fetchEvents({ - kinds: [NDKKind.AppHandler], - "#k": ["24133"], - }); - - for (const service of services) { - const nip05 = JSON.parse(service.content).nip05; - const validate = await ark.validateNIP05({ - pubkey: service.pubkey, - nip05, - }); - if (validate) trusted.push(service); - } - - return trusted; + return await ark.getOAuthServices(); }, async lazy() { const { CreateAccountScreen } = await import( @@ -205,15 +188,6 @@ export default function Router() { return { Component: CreateAccountScreen }; }, }, - { - path: "create-profile", - async lazy() { - const { CreateProfileScreen } = await import( - "./routes/auth/create-profile" - ); - return { Component: CreateProfileScreen }; - }, - }, { path: "import", async lazy() { @@ -232,20 +206,6 @@ export default function Router() { return { Component: OnboardingScreen }; }, }, - { - path: "follow", - async lazy() { - const { FollowScreen } = await import("./routes/auth/follow"); - return { Component: FollowScreen }; - }, - }, - { - path: "finish", - async lazy() { - const { FinishScreen } = await import("./routes/auth/finish"); - return { Component: FinishScreen }; - }, - }, { path: "tutorials/note", async lazy() { diff --git a/apps/desktop/src/routes/auth/create-profile.tsx b/apps/desktop/src/routes/auth/create-profile.tsx deleted file mode 100644 index 33dc23ef..00000000 --- a/apps/desktop/src/routes/auth/create-profile.tsx +++ /dev/null @@ -1,5 +0,0 @@ -export function CreateProfileScreen() { - return ( -
WIP
- ); -} diff --git a/apps/desktop/src/routes/auth/create.tsx b/apps/desktop/src/routes/auth/create.tsx index f429785d..2da7ec88 100644 --- a/apps/desktop/src/routes/auth/create.tsx +++ b/apps/desktop/src/routes/auth/create.tsx @@ -1,12 +1,17 @@ -import { useStorage } from "@lume/ark"; +import { useArk, useStorage } from "@lume/ark"; import { CheckIcon, ChevronDownIcon, LoaderIcon } from "@lume/icons"; import NDK, { NDKEvent, + NDKKind, NDKNip46Signer, NDKPrivateKeySigner, } from "@nostr-dev-kit/ndk"; import * as Select from "@radix-ui/react-select"; +import { downloadDir } from "@tauri-apps/api/path"; import { Window } from "@tauri-apps/api/window"; +import { save } from "@tauri-apps/plugin-dialog"; +import { writeTextFile } from "@tauri-apps/plugin-fs"; +import { getPublicKey, nip19 } from "nostr-tools"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { useLoaderData, useNavigate } from "react-router-dom"; @@ -29,17 +34,18 @@ const Item = ({ event }: { event: NDKEvent }) => { }; export function CreateAccountScreen() { + const ark = useArk(); const storage = useStorage(); - const services = useLoaderData() as NDKEvent[]; const navigate = useNavigate(); + const services = useLoaderData() as NDKEvent[]; - const [serviceId, setServiceId] = useState(services[0].id); + const [serviceId, setServiceId] = useState(services?.[0]?.id); const [loading, setIsLoading] = useState(false); const { register, handleSubmit, - formState: { isDirty, isValid }, + formState: { isValid }, } = useForm(); const getDomainName = (id: string) => { @@ -47,6 +53,31 @@ export function CreateAccountScreen() { return JSON.parse(event.content).nip05.replace("_@", "") as string; }; + const generateNostrKeys = async () => { + const signer = NDKPrivateKeySigner.generate(); + const pubkey = getPublicKey(signer.privateKey); + + const npub = nip19.npubEncode(pubkey); + const nsec = nip19.nsecEncode(signer.privateKey); + + ark.updateNostrSigner({ signer }); + + const downloadPath = await downloadDir(); + const fileName = `nostr_keys_${new Date().getTime().toString(36)}.txt`; + const filePath = await save({ + defaultPath: `${downloadPath}/${fileName}`, + }); + + if (filePath) { + await writeTextFile( + filePath, + `Nostr account, generated by Lume (lume.nu)\nPublic key: ${npub}\nPrivate key: ${nsec}`, + ); + } // else { user cancel action } + + return navigate("/auth/onboarding"); + }; + const onSubmit = async (data: { username: string; email: string }) => { setIsLoading(true); @@ -71,19 +102,18 @@ export function CreateAccountScreen() { localSigner, ); + let authWindow: Window; + remoteSigner.addListener("authUrl", (authUrl: string) => { - const authWindow = new Window("auth", { + authWindow = new Window(`auth-${serviceId}`, { url: authUrl, title: domain, titleBarStyle: "overlay", width: 415, height: 600, - center: true + center: true, + closable: false, }); - authWindow.listen( - "tauri://close-requested", - async () => await authWindow.close(), - ); }); const account = await remoteSigner.createAccount( @@ -93,7 +123,9 @@ export function CreateAccountScreen() { ); if (!account) { + authWindow.close(); setIsLoading(false); + return toast.error("Failed to create new account, try again later"); } @@ -103,15 +135,19 @@ export function CreateAccountScreen() { privkey: localSigner.privateKey, }); + ark.updateNostrSigner({ signer: remoteSigner }); + + authWindow.close(); setIsLoading(false); - return navigate("/auth/create-profile"); + + return navigate("/auth/onboarding"); }; return (
-
-

+
+

Let's get you set up on Nostr.

@@ -119,101 +155,109 @@ export function CreateAccountScreen() { you want.

-
-
-
-
- -
+ {!services ? ( +
+ +
+ ) : ( +
+ +
+
+ +
+ + + + @{getDomainName(serviceId)} + + + + + + + + + + Public handles + + {services.map((service) => ( + + ))} + + + + + +
+
+
+ - - - @{getDomainName(serviceId)} - - - - - - - - - - Public services - - {services.map((service) => ( - - ))} - - - - -
-
- - + + +
+
+
+
+
+
+ + Or + +
+
- - -
-
-
-
-
-
- - Or - -
-
-
-
+ )}
); diff --git a/apps/desktop/src/routes/auth/create_old.tsx b/apps/desktop/src/routes/auth/create_old.tsx deleted file mode 100644 index df68ba64..00000000 --- a/apps/desktop/src/routes/auth/create_old.tsx +++ /dev/null @@ -1,313 +0,0 @@ -import { useArk, useStorage } from "@lume/ark"; -import { ArrowLeftIcon, InfoIcon, LoaderIcon } from "@lume/icons"; -import { User } from "@lume/ui"; -import { NDKKind, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; -import { downloadDir } from "@tauri-apps/api/path"; -import { writeText } from "@tauri-apps/plugin-clipboard-manager"; -import { save } from "@tauri-apps/plugin-dialog"; -import { writeTextFile } from "@tauri-apps/plugin-fs"; -import { motion } from "framer-motion"; -import { minidenticon } from "minidenticons"; -import { generatePrivateKey, getPublicKey, nip19 } from "nostr-tools"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { useNavigate } from "react-router-dom"; -import { toast } from "sonner"; -import { AvatarUploader } from "./components/avatarUploader"; - -export function CreateAccountScreen() { - const [picture, setPicture] = useState(""); - const [downloaded, setDownloaded] = useState(false); - const [loading, setLoading] = useState(false); - const [keys, setKeys] = useState(null); - - const { - register, - handleSubmit, - formState: { isDirty, isValid }, - } = useForm(); - - const ark = useArk(); - const storage = useStorage(); - const navigate = useNavigate(); - - const svgURI = `data:image/svg+xml;utf8,${encodeURIComponent( - minidenticon("lume new account", 90, 50), - )}`; - - const onSubmit = async (data: { name: string; about: string }) => { - try { - setLoading(true); - - const profile = { - ...data, - name: data.name, - display_name: data.name, - bio: data.about, - picture: picture, - avatar: picture, - }; - - const userPrivkey = generatePrivateKey(); - const userPubkey = getPublicKey(userPrivkey); - const userNpub = nip19.npubEncode(userPubkey); - const userNsec = nip19.nsecEncode(userPrivkey); - - const signer = new NDKPrivateKeySigner(userPrivkey); - ark.updateNostrSigner({ signer }); - - const publish = await ark.createEvent({ - content: JSON.stringify(profile), - kind: NDKKind.Metadata, - tags: [], - }); - - if (publish) { - await storage.createAccount({ - id: userNpub, - pubkey: userPubkey, - privkey: userPrivkey, - }); - - setKeys({ npub: userNpub, nsec: userNsec }); - setLoading(false); - } else { - toast.error("Cannot publish user profile, please try again later."); - setLoading(false); - } - } catch (e) { - return toast.error(e); - } - }; - - const copyNsec = async () => { - await writeText(keys.nsec); - }; - - 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, - `Nostr account, generated by Lume (lume.nu)\nPublic key: ${keys.npub}\nPrivate key: ${keys.nsec}`, - ); - - setDownloaded(true); - } // else { user cancel action } - } catch (e) { - return toast.error(e); - } - }; - - return ( -
-
- {!keys ? ( - - ) : null} -
-
-

- Let's set up your account. -

-
- {!keys ? ( -
-
- -
-
- Avatar -
- {picture.length > 0 ? ( - user's avatar - ) : ( - user's avatar - )} - -
-
-
- - -
-
- -