From 3aa4f294f9932913a9be1adfde23305f6c015afa Mon Sep 17 00:00:00 2001 From: reya Date: Mon, 16 Oct 2023 14:42:19 +0700 Subject: [PATCH] wip: new onboarding --- package.json | 1 + pnpm-lock.yaml | 7 + src-tauri/src/main.rs | 2 +- src/app.tsx | 44 ++- .../auth/components/features/customRelay.tsx | 21 ++ .../components/features/favoriteHashtag.tsx | 21 ++ .../auth/components/features/followList.tsx | 3 + src/app/auth/components/features/linkList.tsx | 21 ++ src/app/auth/components/features/nip04.tsx | 21 ++ .../components/features/suggestFollow.tsx | 23 ++ src/app/auth/create.tsx | 281 ++++++++++++++++++ src/app/auth/create/index.tsx | 5 - src/app/auth/create/step-1.tsx | 170 ----------- src/app/auth/create/step-2.tsx | 174 ----------- src/app/auth/import.tsx | 41 ++- .../onboarding/{step-1.tsx => enrich.tsx} | 93 +++--- .../onboarding/{step-2.tsx => hashtag.tsx} | 20 +- src/app/auth/onboarding/index.tsx | 6 +- src/app/auth/onboarding/list.tsx | 41 +++ .../onboarding/{step-3.tsx => relays.tsx} | 12 +- src/libs/ndk/provider.tsx | 7 +- src/libs/storage/instance.ts | 2 +- src/libs/storage/provider.tsx | 2 +- src/shared/avatarUploader.tsx | 7 +- src/shared/bannerUploader.tsx | 10 +- src/shared/layouts/auth.tsx | 6 +- src/shared/navigation.tsx | 30 +- src/stores/onboarding.ts | 38 --- 28 files changed, 550 insertions(+), 559 deletions(-) create mode 100644 src/app/auth/components/features/customRelay.tsx create mode 100644 src/app/auth/components/features/favoriteHashtag.tsx create mode 100644 src/app/auth/components/features/followList.tsx create mode 100644 src/app/auth/components/features/linkList.tsx create mode 100644 src/app/auth/components/features/nip04.tsx create mode 100644 src/app/auth/components/features/suggestFollow.tsx create mode 100644 src/app/auth/create.tsx delete mode 100644 src/app/auth/create/index.tsx delete mode 100644 src/app/auth/create/step-1.tsx delete mode 100644 src/app/auth/create/step-2.tsx rename src/app/auth/onboarding/{step-1.tsx => enrich.tsx} (50%) rename src/app/auth/onboarding/{step-2.tsx => hashtag.tsx} (90%) create mode 100644 src/app/auth/onboarding/list.tsx rename src/app/auth/onboarding/{step-3.tsx => relays.tsx} (94%) delete mode 100644 src/stores/onboarding.ts diff --git a/package.json b/package.json index 4e6d7577..d1a1a22a 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@evilmartians/harmony": "^1.1.0", + "@formkit/auto-animate": "^0.8.0", "@getalby/sdk": "^2.4.0", "@nostr-dev-kit/ndk": "^2.0.2", "@nostr-dev-kit/ndk-cache-dexie": "^2.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 599c558e..fbf4a139 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: '@evilmartians/harmony': specifier: ^1.1.0 version: 1.1.0 + '@formkit/auto-animate': + specifier: ^0.8.0 + version: 0.8.0 '@getalby/sdk': specifier: ^2.4.0 version: 2.4.0 @@ -838,6 +841,10 @@ packages: resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} dev: false + /@formkit/auto-animate@0.8.0: + resolution: {integrity: sha512-G8f7489ka0mWyi+1IEZT+xgIwcpWtRMmE2x+IrVoQ+KM1cP6VDj/TbujZjwxdb0P8w8b16/qBfViRmydbYHwMw==} + dev: false + /@getalby/sdk@2.4.0: resolution: {integrity: sha512-aIGNwLRF9coj6koxfq7P4GtFZbFjQbnIheix39x9176PwFw4dXOdGXHPXnqioJTmeq80y+vX1yd+u/f03YGoeg==} engines: {node: '>=14'} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index cd5e9525..3ad278f5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -115,7 +115,7 @@ fn main() { .plugin( tauri_plugin_sql::Builder::default() .add_migrations( - "sqlite:lume.db", + "sqlite:lume_v2.db", vec![ Migration { version: 20230418013219, diff --git a/src/app.tsx b/src/app.tsx index c8d6e80d..e9ed5db0 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -3,7 +3,6 @@ import { fetch } from '@tauri-apps/plugin-http'; import { RouterProvider, createBrowserRouter, defer, redirect } from 'react-router-dom'; import { ReactFlowProvider } from 'reactflow'; -import { CreateAccountScreen } from '@app/auth/create'; import { OnboardingScreen } from '@app/auth/onboarding'; import { ChatsScreen } from '@app/chats'; import { ErrorScreen } from '@app/error'; @@ -24,17 +23,10 @@ export default function App() { const accountLoader = async () => { try { - const totalAccount = await db.checkAccount(); - - const onboarding = localStorage.getItem('onboarding'); - const step = onboarding ? JSON.parse(onboarding).state.step : null; - // redirect to welcome screen if none user exist + const totalAccount = await db.checkAccount(); if (totalAccount === 0) return redirect('/auth/welcome'); - // restart onboarding process - if (step) return redirect(step); - return null; } catch (e) { await message(e, { title: 'An unexpected error has occurred', type: 'error' }); @@ -169,8 +161,10 @@ export default function App() { }, { path: 'create', - element: , - errorElement: , + async lazy() { + const { CreateAccountScreen } = await import('@app/auth/create'); + return { Component: CreateAccountScreen }; + }, }, { path: 'import', @@ -179,6 +173,13 @@ export default function App() { return { Component: ImportAccountScreen }; }, }, + { + path: 'complete', + async lazy() { + const { CompleteScreen } = await import('@app/auth/complete'); + return { Component: CompleteScreen }; + }, + }, { path: 'onboarding', element: , @@ -187,30 +188,23 @@ export default function App() { { path: '', async lazy() { - const { OnboardStep1Screen } = await import( - '@app/auth/onboarding/step-1' + const { OnboardingListScreen } = await import( + '@app/auth/onboarding/list' ); - return { Component: OnboardStep1Screen }; + return { Component: OnboardingListScreen }; }, }, { - path: 'step-2', + path: 'enrich', async lazy() { - const { OnboardStep2Screen } = await import( - '@app/auth/onboarding/step-2' + const { OnboardEnrichScreen } = await import( + '@app/auth/onboarding/enrich' ); - return { Component: OnboardStep2Screen }; + return { Component: OnboardEnrichScreen }; }, }, ], }, - { - path: 'complete', - async lazy() { - const { CompleteScreen } = await import('@app/auth/complete'); - return { Component: CompleteScreen }; - }, - }, ], }, { diff --git a/src/app/auth/components/features/customRelay.tsx b/src/app/auth/components/features/customRelay.tsx new file mode 100644 index 00000000..9e9660bb --- /dev/null +++ b/src/app/auth/components/features/customRelay.tsx @@ -0,0 +1,21 @@ +export function CustomRelay() { + return ( +
+
+
+
Personalize relay list
+

+ Lume offers some default relays for users who are not familiar with Nostr, but + you can consider adding more relays to discover more content. +

+
+ +
+
+ ); +} diff --git a/src/app/auth/components/features/favoriteHashtag.tsx b/src/app/auth/components/features/favoriteHashtag.tsx new file mode 100644 index 00000000..6756b933 --- /dev/null +++ b/src/app/auth/components/features/favoriteHashtag.tsx @@ -0,0 +1,21 @@ +export function FavoriteHashtag() { + return ( +
+
+
+
Favorite hashtag
+

+ By adding favorite hashtag, Lume will display all contents related to this + hashtag as a column +

+
+ +
+
+ ); +} diff --git a/src/app/auth/components/features/followList.tsx b/src/app/auth/components/features/followList.tsx new file mode 100644 index 00000000..0b52715c --- /dev/null +++ b/src/app/auth/components/features/followList.tsx @@ -0,0 +1,3 @@ +export function FollowList() { + return
; +} diff --git a/src/app/auth/components/features/linkList.tsx b/src/app/auth/components/features/linkList.tsx new file mode 100644 index 00000000..87429b6e --- /dev/null +++ b/src/app/auth/components/features/linkList.tsx @@ -0,0 +1,21 @@ +export function LinkList() { + return ( +
+
+
+
Enable Links
+

+ Beside newsfeed from your follows, you will see more content from all people + that followed by your follows. +

+
+ +
+
+ ); +} diff --git a/src/app/auth/components/features/nip04.tsx b/src/app/auth/components/features/nip04.tsx new file mode 100644 index 00000000..79b4a108 --- /dev/null +++ b/src/app/auth/components/features/nip04.tsx @@ -0,0 +1,21 @@ +export function NIP04() { + return ( +
+
+
+
Enable direct message (Deprecated)
+

+ Send direct message to other user (NIP-04), all messages will be encrypted, + but your metadata will be leaked. +

+
+ +
+
+ ); +} diff --git a/src/app/auth/components/features/suggestFollow.tsx b/src/app/auth/components/features/suggestFollow.tsx new file mode 100644 index 00000000..b731038a --- /dev/null +++ b/src/app/auth/components/features/suggestFollow.tsx @@ -0,0 +1,23 @@ +import { Link } from 'react-router-dom'; + +export function SuggestFollow() { + return ( +
+
+
+
Enrich your network
+

+ Follow more people to stay up to date with everything happening around the + world. +

+
+ + Check + +
+
+ ); +} diff --git a/src/app/auth/create.tsx b/src/app/auth/create.tsx new file mode 100644 index 00000000..47d1f145 --- /dev/null +++ b/src/app/auth/create.tsx @@ -0,0 +1,281 @@ +import { NDKEvent, NDKKind, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'; +import { downloadDir } from '@tauri-apps/api/path'; +import { message, 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 { useNDK } from '@libs/ndk/provider'; +import { useStorage } from '@libs/storage/provider'; + +import { AvatarUploader } from '@shared/avatarUploader'; +import { ArrowLeftIcon, LoaderIcon } from '@shared/icons'; +import { User } from '@shared/user'; + +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 { db } = useStorage(); + const { ndk } = useNDK(); + + const navigate = useNavigate(); + + const svgURI = + 'data:image/svg+xml;utf8,' + + encodeURIComponent(minidenticon('lume new account', 90, 50)); + + const onSubmit = async (data: { + name: string; + display_name: string; + about: string; + }) => { + try { + setLoading(true); + + const profile = { + ...data, + name: data.name, + display_name: data.display_name, + bio: data.about, + }; + + const userPrivkey = generatePrivateKey(); + const userPubkey = getPublicKey(userPrivkey); + const userNpub = nip19.npubEncode(userPubkey); + const userNsec = nip19.nsecEncode(userPrivkey); + + const event = new NDKEvent(ndk); + const signer = new NDKPrivateKeySigner(userPrivkey); + + event.content = JSON.stringify(profile); + event.kind = NDKKind.Metadata; + event.created_at = Math.floor(Date.now() / 1000); + event.pubkey = userPubkey; + event.tags = []; + + await event.sign(signer); + const publish = await event.publish(); + + if (publish) { + await db.createAccount(userNpub, userPubkey); + await db.secureSave(userPubkey, userPrivkey); + setKeys({ + npub: userNpub, + nsec: userNsec, + pubkey: userPubkey, + privkey: userPrivkey, + }); + setLoading(false); + } else { + toast('Create account failed'); + setLoading(false); + } + } catch (e) { + return toast(e); + } + }; + + 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: ${keys.npub}\nPrivate key: ${keys.nsec}` + ); + + setDownloaded(true); + } // else { user cancel action } + } catch (e) { + await message(e, { title: 'Cannot download account keys', type: 'error' }); + } + }; + + return ( +
+
+ {!keys ? ( + + ) : null} +
+
+

+ Let's set up your Nostr account. +

+
+ {!keys ? ( +
+
+ +
+
+ Avatar +
+ user's avatar +
+ +
+
+
+
+ + +
+
+ +