diff --git a/.prettierrc b/.prettierrc index 3cae53df..0060a5d8 100644 --- a/.prettierrc +++ b/.prettierrc @@ -8,16 +8,7 @@ "endOfLine": "lf", "bracketSpacing": true, "bracketSameLine": true, - "importOrder": [ - "^@layouts/(.*)$", - "^@pages/(.*)$", - "^@components/(.*)$", - "^@utils/(.*)$", - "^@stores/(.*)$", - "^@assets/(.*)$", - "", - "^[./]" - ], + "importOrder": ["^@layouts/(.*)$", "^@pages/(.*)$", "^@components/(.*)$", "^@utils/(.*)$", "^@stores/(.*)$", "", "^[./]"], "importOrderSeparation": true, "importOrderSortSpecifiers": true, "plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"], diff --git a/package.json b/package.json index 2816c16c..b366fbeb 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,10 @@ "**/*": "prettier --write --ignore-unknown" }, "dependencies": { - "@nanostores/persistent": "^0.7.0", - "@nanostores/react": "^0.4.1", "@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-dropdown-menu": "^2.0.3", "@radix-ui/react-icons": "^1.2.0", + "@rehooks/local-storage": "^2.4.4", "@tauri-apps/api": "^1.2.0", "@uiw/react-markdown-preview": "^4.1.9", "@uiw/react-md-editor": "^3.20.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 505384a9..32cc97c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,11 +1,10 @@ lockfileVersion: 5.4 specifiers: - '@nanostores/persistent': ^0.7.0 - '@nanostores/react': ^0.4.1 '@radix-ui/react-dialog': ^1.0.2 '@radix-ui/react-dropdown-menu': ^2.0.3 '@radix-ui/react-icons': ^1.2.0 + '@rehooks/local-storage': ^2.4.4 '@tailwindcss/typography': ^0.5.9 '@tauri-apps/api': ^1.2.0 '@tauri-apps/cli': ^1.2.3 @@ -54,11 +53,10 @@ specifiers: ws: ^8.12.1 dependencies: - '@nanostores/persistent': 0.7.0_nanostores@0.7.4 - '@nanostores/react': 0.4.1_nkfnbc2tpc77iht7asm3uqwau4 '@radix-ui/react-dialog': 1.0.2_zula6vjvt3wdocc4mwcxqa6nzi '@radix-ui/react-dropdown-menu': 2.0.3_zula6vjvt3wdocc4mwcxqa6nzi '@radix-ui/react-icons': 1.2.0_react@18.2.0 + '@rehooks/local-storage': 2.4.4_react@18.2.0 '@tauri-apps/api': 1.2.0 '@uiw/react-markdown-preview': 4.1.9_zula6vjvt3wdocc4mwcxqa6nzi '@uiw/react-md-editor': 3.20.5_zula6vjvt3wdocc4mwcxqa6nzi @@ -467,27 +465,6 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: false - /@nanostores/persistent/0.7.0_nanostores@0.7.4: - resolution: { integrity: sha512-4PAInL/T1hbftZUJ0cmgdFHBMalUoq7BUXFBy7QfyMv/8X3LPTYNh/yxspL7+J+XM3UNvVI7IFRMMs6FBasjhQ== } - engines: { node: ^14.0.0 || ^16.0.0 || >=18.0.0 } - peerDependencies: - nanostores: ^0.7.0 - dependencies: - nanostores: 0.7.4 - dev: false - - /@nanostores/react/0.4.1_nkfnbc2tpc77iht7asm3uqwau4: - resolution: { integrity: sha512-lsv0CYrMxczbXtoV/mxFVEoL/uVjEjseoP89srO/5yNAOkJka+dSFS7LYyWEbuvCPO7EgbtkvRpO5V+OztKQOw== } - engines: { node: ^14.0.0 || ^16.0.0 || >=18.0.0 } - peerDependencies: - nanostores: ^0.7.0 - react: '>=18.0.0' - dependencies: - nanostores: 0.7.4 - react: 18.2.0 - use-sync-external-store: 1.2.0_react@18.2.0 - dev: false - /@next/env/13.2.1: resolution: { integrity: sha512-Hq+6QZ6kgmloCg8Kgrix+4F0HtvLqVK3FZAnlAoS0eonaDemHe1Km4kwjSWRE3JNpJNcKxFHF+jsZrYo0SxWoQ== } dev: false @@ -1009,6 +986,14 @@ packages: '@babel/runtime': 7.21.0 dev: false + /@rehooks/local-storage/2.4.4_react@18.2.0: + resolution: { integrity: sha512-zE+kfOkG59n/1UTxdmbwktIosclr67Nlbf2MzUJ9mNtCSypVscNHeD1qT6JCSo5Pjj8DO893IKWNLJqKKzDL/Q== } + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + /@rushstack/eslint-patch/1.2.0: resolution: { integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== } dev: true @@ -5374,14 +5359,6 @@ packages: tslib: 2.5.0 dev: false - /use-sync-external-store/1.2.0_react@18.2.0: - resolution: { integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== } - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - /util-deprecate/1.0.2: resolution: { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== } dev: true diff --git a/src-tauri/migrations/20230226004139_create_tables.sql b/src-tauri/migrations/20230226004139_create_tables.sql index a35c977e..7e923494 100644 --- a/src-tauri/migrations/20230226004139_create_tables.sql +++ b/src-tauri/migrations/20230226004139_create_tables.sql @@ -1,4 +1,28 @@ -- Add migration script here +-- create relays +CREATE TABLE + relays ( + id INTEGER PRIMARY KEY, + relay_url TEXT NOT NULL, + relay_status INTEGER NOT NULL DEFAULT 1, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + +INSERT INTO + relays (relay_url, relay_status) +VALUES + ("wss://relay.damus.io", "1"), + ("wss://relay.uselume.xyz", "0"), + ("wss://nostr-pub.wellorder.net", "1"), + ("wss://nostr.bongbong.com", "1"), + ("wss://nostr.zebedee.cloud", "1"), + ("wss://nostr.fmt.wiz.biz", "1"), + ("wss://nostr.walletofsatoshi.com", "1"), + ("wss://relay.snort.social", "1"), + ("wss://offchain.pub", "1"), + ("wss://nos.lol", "1"); + -- create accounts CREATE TABLE accounts ( @@ -6,6 +30,7 @@ CREATE TABLE privkey TEXT NOT NULL, npub TEXT NOT NULL, nsec TEXT NOT NULL, + is_active INTEGER NOT NULL DEFAULT 0, metadata JSON ); @@ -40,5 +65,6 @@ CREATE TABLE kind INTEGER NOT NULL DEFAULT 1, tags TEXT NOT NULL, content TEXT NOT NULL, + relay TEXT, is_multi BOOLEAN DEFAULT 0 ); \ No newline at end of file diff --git a/src/components/accountBar/index.tsx b/src/components/accountBar/index.tsx index d32cc447..515a2af4 100644 --- a/src/components/accountBar/index.tsx +++ b/src/components/accountBar/index.tsx @@ -1,19 +1,16 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Account } from '@components/accountBar/account'; -import { currentUser } from '@stores/currentUser'; - import LumeSymbol from '@assets/icons/Lume'; - -import { useStore } from '@nanostores/react'; import { PlusIcon } from '@radix-ui/react-icons'; +import { useLocalStorage } from '@rehooks/local-storage'; import Link from 'next/link'; import { useCallback, useEffect, useState } from 'react'; import Database from 'tauri-plugin-sql-api'; export default function AccountBar() { const [users, setUsers] = useState([]); - const $currentUser: any = useStore(currentUser); + const [currentUser]: any = useLocalStorage('current-user'); const getAccounts = useCallback(async () => { const db = await Database.load('sqlite:lume.db'); @@ -30,7 +27,7 @@ export default function AccountBar() {
{users.map((user, index) => ( - + ))} { @@ -35,11 +32,11 @@ export const NoteConnector = memo(function NoteConnector() { [ { kinds: [1], - authors: $follows, + authors: follows, since: dateToUnix(hoursAgo(12, now.current)), }, ], - $relays, + relays, (event: any) => { insertDB(event).catch(console.error); }, diff --git a/src/components/contexts/database.tsx b/src/components/contexts/database.tsx index c4059a99..45f3032b 100644 --- a/src/components/contexts/database.tsx +++ b/src/components/contexts/database.tsx @@ -1,13 +1,56 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { createContext } from 'react'; +import { writeStorage } from '@rehooks/local-storage'; +import { createContext, useEffect, useMemo } from 'react'; import Database from 'tauri-plugin-sql-api'; export const DatabaseContext = createContext({}); -const db = typeof window !== 'undefined' ? await Database.load('sqlite:lume.db') : null; +const initDB = typeof window !== 'undefined' ? await Database.load('sqlite:lume.db') : null; export default function DatabaseProvider({ children }: { children: React.ReactNode }) { - const value = db; + const db = useMemo(() => initDB, []); - return {children}; + useEffect(() => { + const getRelays = async () => { + const arr = []; + const result: any[] = await db.select('SELECT relay_url FROM relays WHERE relay_status = "1"'); + + result.forEach((item: { relay_url: string }) => { + arr.push(item.relay_url); + }); + + writeStorage('relays', arr); + }; + + const getAccount = async () => { + const result = await db.select(`SELECT * FROM accounts LIMIT 1`); + writeStorage('current-user', result[0]); + + return result[0]; + }; + + const getFollows = async (id: string) => { + const arr = []; + const result: any[] = await db.select(`SELECT pubkey FROM follows WHERE account = "${id}"`); + + result.forEach((item: { pubkey: string }) => { + arr.push(item.pubkey); + }); + + writeStorage('follows', arr); + }; + + if (db !== null) { + getRelays().catch(console.error); + getAccount() + .then((res) => { + if (res) { + getFollows(res.id).catch(console.error); + } + }) + .catch(console.error); + } + }, [db]); + + return {children}; } diff --git a/src/components/navigatorBar/createPost.tsx b/src/components/navigatorBar/createPost.tsx index 2ce53f11..957355b2 100644 --- a/src/components/navigatorBar/createPost.tsx +++ b/src/components/navigatorBar/createPost.tsx @@ -1,8 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { currentUser } from '@stores/currentUser'; - -import { useStore } from '@nanostores/react'; import * as Dialog from '@radix-ui/react-dialog'; +import { useLocalStorage } from '@rehooks/local-storage'; import * as commands from '@uiw/react-md-editor/lib/commands'; import dynamic from 'next/dynamic'; import { dateToUnix, useNostr } from 'nostr-react'; @@ -17,9 +15,9 @@ export default function CreatePost() { const { publish } = useNostr(); const [value, setValue] = useState(''); - const $currentUser: any = useStore(currentUser); - const pubkey = $currentUser.pubkey; - const privkey = $currentUser.privkey; + const [currentUser]: any = useLocalStorage('current-user'); + const pubkey = currentUser.pubkey; + const privkey = currentUser.privkey; const postButton = { name: 'post', @@ -27,9 +25,7 @@ export default function CreatePost() { buttonProps: { className: 'cta-btn', 'aria-label': 'Post a message' }, icon: (
- - Post - + Post
), diff --git a/src/components/navigatorBar/index.tsx b/src/components/navigatorBar/index.tsx index 6b62181f..904fd70e 100644 --- a/src/components/navigatorBar/index.tsx +++ b/src/components/navigatorBar/index.tsx @@ -4,14 +4,12 @@ import { NoteConnector } from '@components/connectors/note'; import CreatePost from '@components/navigatorBar/createPost'; import { ProfileMenu } from '@components/navigatorBar/profileMenu'; -import { currentUser } from '@stores/currentUser'; - -import { useStore } from '@nanostores/react'; import { PlusIcon } from '@radix-ui/react-icons'; +import { useLocalStorage } from '@rehooks/local-storage'; export default function NavigatorBar() { - const $currentUser: any = useStore(currentUser); - const profile = $currentUser.metadata !== undefined ? JSON.parse($currentUser.metadata) : { display_name: null, username: null }; + const [currentUser]: any = useLocalStorage('current-user'); + const profile = currentUser.metadata !== undefined ? JSON.parse(currentUser.metadata) : { display_name: null, username: null }; return (
@@ -25,7 +23,7 @@ export default function NavigatorBar() {
{profile.display_name || ''}
- +
@{profile.username || ''}
diff --git a/src/components/note/atoms/reaction.tsx b/src/components/note/atoms/reaction.tsx index 8665fc78..7c2a36cd 100644 --- a/src/components/note/atoms/reaction.tsx +++ b/src/components/note/atoms/reaction.tsx @@ -1,26 +1,18 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { currentUser } from '@stores/currentUser'; - -import { useStore } from '@nanostores/react'; import { HeartFilledIcon, HeartIcon } from '@radix-ui/react-icons'; +import { useLocalStorage } from '@rehooks/local-storage'; import { dateToUnix, useNostr, useNostrEvents } from 'nostr-react'; import { getEventHash, signEvent } from 'nostr-tools'; import { useState } from 'react'; -export default function Reaction({ - eventID, - eventPubkey, -}: { - eventID: string; - eventPubkey: string; -}) { +export default function Reaction({ eventID, eventPubkey }: { eventID: string; eventPubkey: string }) { const { publish } = useNostr(); const [reaction, setReaction] = useState(0); const [isReact, setIsReact] = useState(false); - const $currentUser: any = useStore(currentUser); - const pubkey = $currentUser.pubkey; - const privkey = $currentUser.privkey; + const [currentUser]: any = useLocalStorage('current-user'); + const pubkey = currentUser.pubkey; + const privkey = currentUser.privkey; const { onEvent } = useNostrEvents({ filter: { @@ -65,15 +57,9 @@ export default function Reaction({ }; return ( - diff --git a/src/layouts/userLayout.tsx b/src/layouts/userLayout.tsx index b04a159b..814472e1 100644 --- a/src/layouts/userLayout.tsx +++ b/src/layouts/userLayout.tsx @@ -2,12 +2,10 @@ import AccountBar from '@components/accountBar'; import ActiveLink from '@components/activeLink'; -import { currentUser } from '@stores/currentUser'; - -import { useStore } from '@nanostores/react'; +import { useLocalStorage } from '@rehooks/local-storage'; export default function UserLayout({ children }: { children: React.ReactNode }) { - const $currentUser: any = useStore(currentUser); + const [currentUser]: any = useLocalStorage('current-user'); return (
@@ -27,13 +25,13 @@ export default function UserLayout({ children }: { children: React.ReactNode })
Personal Page Update Profile diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index ff4c5514..b72c3c50 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -2,9 +2,7 @@ import DatabaseProvider from '@components/contexts/database'; import RelayProvider from '@components/contexts/relay'; -import { relays } from '@stores/relays'; - -import { useStore } from '@nanostores/react'; +import { useLocalStorage } from '@rehooks/local-storage'; import type { NextPage } from 'next'; import type { AppProps } from 'next/app'; import { ReactElement, ReactNode } from 'react'; @@ -23,12 +21,12 @@ type AppPropsWithLayout = AppProps & { export default function MyApp({ Component, pageProps }: AppPropsWithLayout) { // Use the layout defined at the page level, if available const getLayout = Component.getLayout ?? ((page) => page); - // Get all relays - const $relays = useStore(relays); + // Get relays from localstorage + const [relays] = useLocalStorage('relays'); return ( - {getLayout()} + {getLayout()} ); } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 688b4954..661ef1f2 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -2,90 +2,32 @@ import BaseLayout from '@layouts/baseLayout'; import FullLayout from '@layouts/fullLayout'; -import { DatabaseContext } from '@components/contexts/database'; - -import { currentUser } from '@stores/currentUser'; -import { follows } from '@stores/follows'; - import LumeSymbol from '@assets/icons/Lume'; - -import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/api/notification'; +import { useLocalStorage } from '@rehooks/local-storage'; import { motion } from 'framer-motion'; import { useRouter } from 'next/router'; -import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useEffect, useState } from 'react'; +import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useEffect, useState } from 'react'; export default function Page() { - const db: any = useContext(DatabaseContext); - const router = useRouter(); + + const [currentUser]: any = useLocalStorage('current-user'); const [loading, setLoading] = useState(true); - const requestNotification = useCallback(async () => { - // NOTE: notification don't work in dev mode (only affect MacOS) - // ref: https://github.com/tauri-apps/tauri/issues/4965 - let permissionGranted = await isPermissionGranted(); - if (!permissionGranted) { - const permission = await requestPermission(); - permissionGranted = permission === 'granted'; - } - if (permissionGranted) { - sendNotification({ title: 'Lume', body: 'Nostr is awesome' }); - } - - return permissionGranted; - }, []); - - const getAccount = useCallback(async () => { - const result = await db.select(`SELECT * FROM accounts ASC LIMIT 1`); - return result; - }, [db]); - - const getFollows = useCallback( - async (account: { id: string }) => { - const arr = []; - const result: any = await db.select(`SELECT pubkey FROM follows WHERE account = "${account.id}"`); - - result.forEach((item: { pubkey: string }) => { - arr.push(item.pubkey); - }); - - return arr; - }, - [db] - ); - - // Explain: - // Step 1: request allow notification from system - // Step 2: get first account. #TODO: get last used account instead (part of multi account feature) - // Step 3: get follows by account useEffect(() => { - requestNotification().then(() => { - getAccount() - .then((res: any) => { - if (res.length === 0) { - setTimeout(() => { - setLoading(false); - router.push('/onboarding'); - }, 1500); - } else { - // store current user in localstorage - currentUser.set(res[0]); - getFollows(res[0]) - .then(async (res) => { - // store follows in localstorage - follows.set(res); - // redirect to newsfeed - setTimeout(() => { - setLoading(false); - router.push('/feed/following'); - }, 1500); - }) - .catch(console.error); - } - }) - .catch(console.error); - }); - }, [requestNotification, getAccount, getFollows, router]); + console.log(currentUser); + if (!currentUser) { + setTimeout(() => { + setLoading(false); + router.push('/onboarding'); + }, 1500); + } else { + setTimeout(() => { + setLoading(false); + router.push('/feed/following'); + }, 1500); + } + }, [currentUser, router]); return (
diff --git a/src/pages/onboarding/create.tsx b/src/pages/onboarding/create/index.tsx similarity index 88% rename from src/pages/onboarding/create.tsx rename to src/pages/onboarding/create/index.tsx index c836db7f..7958e985 100644 --- a/src/pages/onboarding/create.tsx +++ b/src/pages/onboarding/create/index.tsx @@ -5,16 +5,13 @@ import OnboardingLayout from '@layouts/onboardingLayout'; import { DatabaseContext } from '@components/contexts/database'; import { RelayContext } from '@components/contexts/relay'; -import { currentUser } from '@stores/currentUser'; -import { relays } from '@stores/relays'; - -import { useStore } from '@nanostores/react'; import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons'; +import { useLocalStorage, writeStorage } from '@rehooks/local-storage'; import { motion } from 'framer-motion'; import Image from 'next/image'; import { useRouter } from 'next/router'; import { generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent } from 'nostr-tools'; -import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useState } from 'react'; +import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useMemo, useState } from 'react'; import { Config, names, uniqueNamesGenerator } from 'unique-names-generator'; const config: Config = { @@ -22,11 +19,12 @@ const config: Config = { }; export default function Page() { - const db: any = useContext(DatabaseContext); const router = useRouter(); + const { db }: any = useContext(DatabaseContext); const relayPool: any = useContext(RelayContext); - const $relays = useStore(relays); + + const [relays] = useLocalStorage('relays'); const [type, setType] = useState('password'); const [loading, setLoading] = useState(false); @@ -47,13 +45,22 @@ export default function Page() { }; // auto-generated profile - const data = { - display_name: name, - name: name, - username: name.toLowerCase(), - picture: 'https://bafybeidfsbrzqbvontmucteomoz2rkrxugu462l5hyhh6uioslkfzzs4oq.ipfs.w3s.link/avatar-11.png', - banner: 'https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg', - }; + const data = useMemo( + () => ({ + display_name: name, + name: name, + username: name.toLowerCase(), + picture: 'https://bafybeidfsbrzqbvontmucteomoz2rkrxugu462l5hyhh6uioslkfzzs4oq.ipfs.w3s.link/avatar-11.png', + banner: 'https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg', + }), + [name] + ); + + const insertDB = useCallback(async () => { + await db.execute( + `INSERT INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubKey}", "${privKey}", "${npub}", "${nsec}", '${JSON.stringify(data)}')` + ); + }, [data, db, npub, nsec, privKey, pubKey]); const createAccount = async () => { setLoading(true); @@ -68,27 +75,25 @@ export default function Page() { }; event.id = getEventHash(event); event.sig = signEvent(event, privKey); - // publish to relays - relayPool.publish(event, $relays); - // save account to database - await db.execute( - `INSERT INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubKey}", "${privKey}", "${npub}", "${nsec}", '${JSON.stringify(data)}')` - ); - - // set currentUser in global state - currentUser.set({ - metadata: JSON.stringify(data), - npub: npub, - privkey: privKey, - pubkey: pubKey, - }); - - // redirect to pre-follow - setTimeout(() => { - setLoading(false); - router.push('/onboarding/following'); - }, 1500); + insertDB() + .then(() => { + // publish to relays + relayPool.publish(event, relays); + // set currentUser in global state + writeStorage('current-user', { + metadata: JSON.stringify(data), + npub: npub, + privkey: privKey, + pubkey: pubKey, + }); + // redirect to pre-follow + setTimeout(() => { + setLoading(false); + router.push('/onboarding/create/pre-follows'); + }, 1500); + }) + .catch(console.error); }; return ( diff --git a/src/pages/onboarding/following.tsx b/src/pages/onboarding/create/pre-follows.tsx similarity index 93% rename from src/pages/onboarding/following.tsx rename to src/pages/onboarding/create/pre-follows.tsx index 7ced300c..762a70f3 100644 --- a/src/pages/onboarding/following.tsx +++ b/src/pages/onboarding/create/pre-follows.tsx @@ -6,28 +6,25 @@ import { DatabaseContext } from '@components/contexts/database'; import { truncate } from '@utils/truncate'; -import { currentUser } from '@stores/currentUser'; - import data from '@assets/directory.json'; - -import { useStore } from '@nanostores/react'; import { CheckCircledIcon } from '@radix-ui/react-icons'; +import { useLocalStorage } from '@rehooks/local-storage'; import { motion } from 'framer-motion'; import Image from 'next/image'; import { useRouter } from 'next/router'; import { nip19 } from 'nostr-tools'; import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useState } from 'react'; +const shuffle = (arr: { name: string; avatar: string; npub: string }[]) => [...arr].sort(() => Math.random() - 0.5); + export default function Page() { const db: any = useContext(DatabaseContext); const router = useRouter(); - const shuffle = (arr) => [...arr].sort(() => Math.random() - 0.5); - const [follow, setFollow] = useState([]); const [loading, setLoading] = useState(false); const [list] = useState(shuffle(data)); - const $currentUser: any = useStore(currentUser); + const [currentUser]: any = useLocalStorage('current-user'); const followUser = (e) => { const npub = e.currentTarget.getAttribute('data-npub'); @@ -36,11 +33,11 @@ export default function Page() { const insertDB = async () => { // self follow - await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${$currentUser.pubkey}", "${$currentUser.pubkey}", "0")`); + await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${currentUser.pubkey}", "${currentUser.pubkey}", "0")`); // follow selected follow.forEach(async (npub) => { const { data } = nip19.decode(npub); - await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${data}", "${$currentUser.pubkey}", "0")`); + await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${data}", "${currentUser.pubkey}", "0")`); }); }; diff --git a/src/pages/onboarding/fetch-profile.tsx b/src/pages/onboarding/fetch-profile.tsx deleted file mode 100644 index a5ecd28c..00000000 --- a/src/pages/onboarding/fetch-profile.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import BaseLayout from '@layouts/baseLayout'; -import OnboardingLayout from '@layouts/onboardingLayout'; - -import { DatabaseContext } from '@components/contexts/database'; -import { RelayContext } from '@components/contexts/relay'; - -import { relays } from '@stores/relays'; - -import { useStore } from '@nanostores/react'; -import { motion } from 'framer-motion'; -import { useRouter } from 'next/router'; -import { getPublicKey, nip19 } from 'nostr-tools'; -import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useEffect, useState } from 'react'; - -export default function Page() { - const db: any = useContext(DatabaseContext); - const relayPool: any = useContext(RelayContext); - const $relays = useStore(relays); - - const router = useRouter(); - const { privkey }: any = router.query; - - const [account, setAccount] = useState(null); - const [loading, setLoading] = useState(false); - - const pubkey = privkey ? getPublicKey(privkey) : null; - const npub = privkey ? nip19.npubEncode(pubkey) : null; - const nsec = privkey ? nip19.nsecEncode(privkey) : null; - - relayPool.subscribe( - [ - { - authors: [pubkey], - kinds: [0], - }, - ], - $relays, - (event: any) => { - const metadata = JSON.parse(event.content); - setAccount(metadata); - }, - undefined, - (events: any, relayURL: any) => { - console.log(events, relayURL); - } - ); - - const insertDB = useCallback(async () => { - // save account to database - const metadata = JSON.stringify(account); - await db.execute( - `INSERT INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubkey}", "${privkey}", "${npub}", "${nsec}", '${metadata}')` - ); - await db.close(); - }, [account, db, npub, nsec, privkey, pubkey]); - - useEffect(() => { - setLoading(true); - - if (account !== null) { - insertDB() - .then(() => { - setTimeout(() => { - setLoading(false); - router.push({ - pathname: '/onboarding/fetch-follows', - query: { pubkey: pubkey }, - }); - }, 1500); - }) - .catch(console.error); - } - }, [account, insertDB, npub, nsec, privkey, pubkey, router]); - - return ( -
-
{/* spacer */}
- -
- - Fetching your profile... - - - As long as you have private key, you alway can sync your profile on every nostr client, so please keep your key safely - -
-
- -
- {loading === true ? ( - - - - - ) : ( - <> - )} -
-
-
- ); -} - -Page.getLayout = function getLayout( - page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal -) { - return ( - - {page} - - ); -}; diff --git a/src/pages/onboarding/index.tsx b/src/pages/onboarding/index.tsx index 0075558d..718357f0 100644 --- a/src/pages/onboarding/index.tsx +++ b/src/pages/onboarding/index.tsx @@ -10,9 +10,7 @@ export default function Page() {
{/* spacer */}
- + Other social network require email/password
nostr use{' '} @@ -21,8 +19,8 @@ export default function Page() {
- If you have used nostr before, you can import your own private key. Otherwise, you can - create a new key or use auto-generated account created by system. + If you have used nostr before, you can import your own private key. Otherwise, you can create a new key or use auto-generated account + created by system. @@ -32,7 +30,7 @@ export default function Page() { Create new key Login with private key @@ -44,13 +42,7 @@ export default function Page() { } Page.getLayout = function getLayout( - page: - | string - | number - | boolean - | ReactElement> - | ReactFragment - | ReactPortal + page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal ) { return ( diff --git a/src/pages/onboarding/fetch-follows.tsx b/src/pages/onboarding/login/fetch.tsx similarity index 57% rename from src/pages/onboarding/fetch-follows.tsx rename to src/pages/onboarding/login/fetch.tsx index b9406b9a..97daf505 100644 --- a/src/pages/onboarding/fetch-follows.tsx +++ b/src/pages/onboarding/login/fetch.tsx @@ -5,35 +5,69 @@ import OnboardingLayout from '@layouts/onboardingLayout'; import { DatabaseContext } from '@components/contexts/database'; import { RelayContext } from '@components/contexts/relay'; -import { relays } from '@stores/relays'; - -import { useStore } from '@nanostores/react'; +import { useLocalStorage } from '@rehooks/local-storage'; import { motion } from 'framer-motion'; +import Link from 'next/link'; import { useRouter } from 'next/router'; -import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useEffect, useState } from 'react'; +import { getPublicKey, nip19 } from 'nostr-tools'; +import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useMemo, useState } from 'react'; export default function Page() { - const db: any = useContext(DatabaseContext); + const { db }: any = useContext(DatabaseContext); const relayPool: any = useContext(RelayContext); - const $relays = useStore(relays); + + const [loading, setLoading] = useState(false); + const [relays] = useLocalStorage('relays'); const router = useRouter(); - const { pubkey }: any = router.query; + const { privkey }: any = router.query; - const [follows, setFollows] = useState([null]); - const [loading, setLoading] = useState(false); + const pubkey = useMemo(() => (privkey ? getPublicKey(privkey) : null), [privkey]); + + // save account to database + const insertAccount = useCallback( + async (metadata) => { + if (loading === false) { + const npub = privkey ? nip19.npubEncode(pubkey) : null; + const nsec = privkey ? nip19.nsecEncode(privkey) : null; + await db.execute( + `INSERT OR IGNORE INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubkey}", "${privkey}", "${npub}", "${nsec}", '${metadata}')` + ); + setLoading(true); + } + }, + [db, privkey, pubkey, loading] + ); + + // save follows to database + const insertFollows = useCallback( + async (follows) => { + follows.forEach(async (item) => { + if (item) { + await db.execute(`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${item[1]}", "${pubkey}", "0")`); + } + }); + }, + [db, pubkey] + ); relayPool.subscribe( [ { authors: [pubkey], - kinds: [0], + kinds: [0, 3], since: 0, }, ], - $relays, + relays, (event: any) => { - setFollows(event.tags); + if (event.kind === 0) { + insertAccount(event.content); + } else { + if (event.tags.length > 0) { + insertFollows(event.tags); + } + } }, undefined, (events: any, relayURL: any) => { @@ -41,39 +75,16 @@ export default function Page() { } ); - useEffect(() => { - setLoading(true); - - const insertDB = async () => { - follows.forEach(async (item) => { - if (item) { - await db.execute(`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${item[1]}", "${pubkey}", "0")`); - } - }); - }; - - if (follows !== null && follows.length > 0) { - insertDB() - .then(() => { - setTimeout(() => { - setLoading(false); - router.push('/'); - }, 1500); - }) - .catch(console.error); - } - }, [db, follows, pubkey, router]); - return (
{/* spacer */}
- Fetching your follows... + Fetching your profile... - Not only profile, every nostr client can sync your follows list when you move to a new client, so please keep your key safely (again) + As long as you have private key, you alway can sync your profile and follows list on every nostr client, so please keep your key safely
@@ -88,7 +99,11 @@ export default function Page() { d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"> ) : ( - <> + + Finish + )}
diff --git a/src/pages/onboarding/import.tsx b/src/pages/onboarding/login/index.tsx similarity index 98% rename from src/pages/onboarding/import.tsx rename to src/pages/onboarding/login/index.tsx index 454fae06..3f883224 100644 --- a/src/pages/onboarding/import.tsx +++ b/src/pages/onboarding/login/index.tsx @@ -44,7 +44,7 @@ export default function Page() { try { router.push({ - pathname: '/onboarding/fetch-profile', + pathname: '/onboarding/login/fetch', query: { privkey: privkey }, }); } catch (error) { diff --git a/src/pages/profile/update.tsx b/src/pages/profile/update.tsx index fa0996bd..4b018bf2 100644 --- a/src/pages/profile/update.tsx +++ b/src/pages/profile/update.tsx @@ -2,9 +2,7 @@ import BaseLayout from '@layouts/baseLayout'; import UserLayout from '@layouts/userLayout'; -import { currentUser } from '@stores/currentUser'; - -import { useStore } from '@nanostores/react'; +import { useLocalStorage } from '@rehooks/local-storage'; import { useRouter } from 'next/router'; import { dateToUnix, useNostr } from 'nostr-react'; import { getEventHash, signEvent } from 'nostr-tools'; @@ -28,11 +26,8 @@ export default function Page() { const { publish } = useNostr(); const [loading, setLoading] = useState(false); - const $currentUser: any = useStore(currentUser); - const profile = - $currentUser.metadata !== undefined - ? JSON.parse($currentUser.metadata) - : { display_name: null, username: null }; + const [currentUser]: any = useLocalStorage('current-user'); + const profile = currentUser.metadata !== undefined ? JSON.parse(currentUser.metadata) : { display_name: null, username: null }; const { register, @@ -48,28 +43,24 @@ export default function Page() { content: JSON.stringify(data), created_at: dateToUnix(), kind: 0, - pubkey: $currentUser.pubkey, + pubkey: currentUser.pubkey, tags: [], }; event.id = getEventHash(event); - event.sig = signEvent(event, $currentUser.privkey); + event.sig = signEvent(event, currentUser.privkey); publish(event); // save account to database const db = await Database.load('sqlite:lume.db'); - await db.execute( - `UPDATE accounts SET metadata = '${JSON.stringify(data)}' WHERE pubkey = "${ - $currentUser.pubkey - }"` - ); + await db.execute(`UPDATE accounts SET metadata = '${JSON.stringify(data)}' WHERE pubkey = "${currentUser.pubkey}"`); await db.close(); // set currentUser in global state currentUser.set({ metadata: JSON.stringify(data), - npub: $currentUser.npub, - privkey: $currentUser.privkey, - pubkey: $currentUser.pubkey, + npub: currentUser.npub, + privkey: currentUser.privkey, + pubkey: currentUser.pubkey, }); // redirect to newsfeed @@ -80,16 +71,11 @@ export default function Page() { }; return ( -
+
-

- Update profile -

+

Update profile

- Your profile will be published to all relays, as long as you have the private key, you - always can recover your profile in any client + Your profile will be published to all relays, as long as you have the private key, you always can recover your profile in any client

@@ -105,9 +91,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.display_name &&

{errors.display_name.message}

} -
+ {errors.display_name &&

{errors.display_name.message}

}
@@ -122,9 +106,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.name &&

{errors.name.message}

} -
+ {errors.name &&

{errors.name.message}

}
@@ -139,9 +121,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.username &&

{errors.username.message}

} -
+ {errors.username &&

{errors.username.message}

}
@@ -156,9 +136,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.picture &&

{errors.picture.message}

} -
+ {errors.picture &&

{errors.picture.message}

}
@@ -173,9 +151,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.banner &&

{errors.banner.message}

} -
+ {errors.banner &&

{errors.banner.message}

}
@@ -190,27 +166,15 @@ export default function Page() { className="relative h-24 w-full resize-none rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.about &&

{errors.about.message}

} -
+ {errors.about &&

{errors.about.message}

}
{loading === true ? ( - - + + > - | ReactFragment - | ReactPortal + page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal ) { return ( diff --git a/src/stores/currentUser.tsx b/src/stores/currentUser.tsx deleted file mode 100644 index ddfc4912..00000000 --- a/src/stores/currentUser.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { persistentAtom } from '@nanostores/persistent'; - -export const currentUser = persistentAtom( - 'currentUser', - {}, - { - encode: JSON.stringify, - decode: JSON.parse, - } -); diff --git a/src/stores/follows.tsx b/src/stores/follows.tsx deleted file mode 100644 index 8016660d..00000000 --- a/src/stores/follows.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { persistentAtom } from '@nanostores/persistent'; - -export const follows = persistentAtom('follows', [], { - encode(value) { - return JSON.stringify(value); - }, - decode(value) { - return JSON.parse(value); - }, -}); diff --git a/src/stores/relays.tsx b/src/stores/relays.tsx deleted file mode 100644 index 27344318..00000000 --- a/src/stores/relays.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { persistentAtom } from '@nanostores/persistent'; - -export const relays = persistentAtom( - 'relays', - [ - 'wss://relay.uselume.xyz', - 'wss://nostr-pub.wellorder.net', - 'wss://nostr.bongbong.com', - 'wss://nostr.zebedee.cloud', - 'wss://nostr.fmt.wiz.biz', - 'wss://nostr.walletofsatoshi.com', - 'wss://relay.snort.social', - 'wss://offchain.pub', - 'wss://nos.lol', - 'wss://relay.damus.io', - ], - { - encode(value) { - return JSON.stringify(value); - }, - decode(value) { - return JSON.parse(value); - }, - } -); diff --git a/tsconfig.json b/tsconfig.json index 99c5f7e8..db64c2f8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,6 @@ "@layouts/*": ["src/layouts/*"], "@components/*": ["src/components/*"], "@utils/*": ["src/utils/*"], - "@stores/*": ["src/stores/*"], "@assets/*": ["src/assets/*"] }, "target": "es2017",