refactor initial database and state management

This commit is contained in:
Ren Amamiya 2023-03-01 15:35:10 +07:00
parent 964343ccc8
commit 458f826958
24 changed files with 259 additions and 506 deletions

View File

@ -8,16 +8,7 @@
"endOfLine": "lf", "endOfLine": "lf",
"bracketSpacing": true, "bracketSpacing": true,
"bracketSameLine": true, "bracketSameLine": true,
"importOrder": [ "importOrder": ["^@layouts/(.*)$", "^@pages/(.*)$", "^@components/(.*)$", "^@utils/(.*)$", "^@stores/(.*)$", "<THIRD_PARTY_MODULES>", "^[./]"],
"^@layouts/(.*)$",
"^@pages/(.*)$",
"^@components/(.*)$",
"^@utils/(.*)$",
"^@stores/(.*)$",
"^@assets/(.*)$",
"<THIRD_PARTY_MODULES>",
"^[./]"
],
"importOrderSeparation": true, "importOrderSeparation": true,
"importOrderSortSpecifiers": true, "importOrderSortSpecifiers": true,
"plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"], "plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],

View File

@ -12,11 +12,10 @@
"**/*": "prettier --write --ignore-unknown" "**/*": "prettier --write --ignore-unknown"
}, },
"dependencies": { "dependencies": {
"@nanostores/persistent": "^0.7.0",
"@nanostores/react": "^0.4.1",
"@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.3", "@radix-ui/react-dropdown-menu": "^2.0.3",
"@radix-ui/react-icons": "^1.2.0", "@radix-ui/react-icons": "^1.2.0",
"@rehooks/local-storage": "^2.4.4",
"@tauri-apps/api": "^1.2.0", "@tauri-apps/api": "^1.2.0",
"@uiw/react-markdown-preview": "^4.1.9", "@uiw/react-markdown-preview": "^4.1.9",
"@uiw/react-md-editor": "^3.20.5", "@uiw/react-md-editor": "^3.20.5",

View File

@ -1,11 +1,10 @@
lockfileVersion: 5.4 lockfileVersion: 5.4
specifiers: specifiers:
'@nanostores/persistent': ^0.7.0
'@nanostores/react': ^0.4.1
'@radix-ui/react-dialog': ^1.0.2 '@radix-ui/react-dialog': ^1.0.2
'@radix-ui/react-dropdown-menu': ^2.0.3 '@radix-ui/react-dropdown-menu': ^2.0.3
'@radix-ui/react-icons': ^1.2.0 '@radix-ui/react-icons': ^1.2.0
'@rehooks/local-storage': ^2.4.4
'@tailwindcss/typography': ^0.5.9 '@tailwindcss/typography': ^0.5.9
'@tauri-apps/api': ^1.2.0 '@tauri-apps/api': ^1.2.0
'@tauri-apps/cli': ^1.2.3 '@tauri-apps/cli': ^1.2.3
@ -54,11 +53,10 @@ specifiers:
ws: ^8.12.1 ws: ^8.12.1
dependencies: 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-dialog': 1.0.2_zula6vjvt3wdocc4mwcxqa6nzi
'@radix-ui/react-dropdown-menu': 2.0.3_zula6vjvt3wdocc4mwcxqa6nzi '@radix-ui/react-dropdown-menu': 2.0.3_zula6vjvt3wdocc4mwcxqa6nzi
'@radix-ui/react-icons': 1.2.0_react@18.2.0 '@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 '@tauri-apps/api': 1.2.0
'@uiw/react-markdown-preview': 4.1.9_zula6vjvt3wdocc4mwcxqa6nzi '@uiw/react-markdown-preview': 4.1.9_zula6vjvt3wdocc4mwcxqa6nzi
'@uiw/react-md-editor': 3.20.5_zula6vjvt3wdocc4mwcxqa6nzi '@uiw/react-md-editor': 3.20.5_zula6vjvt3wdocc4mwcxqa6nzi
@ -467,27 +465,6 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14 '@jridgewell/sourcemap-codec': 1.4.14
dev: false 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: /@next/env/13.2.1:
resolution: { integrity: sha512-Hq+6QZ6kgmloCg8Kgrix+4F0HtvLqVK3FZAnlAoS0eonaDemHe1Km4kwjSWRE3JNpJNcKxFHF+jsZrYo0SxWoQ== } resolution: { integrity: sha512-Hq+6QZ6kgmloCg8Kgrix+4F0HtvLqVK3FZAnlAoS0eonaDemHe1Km4kwjSWRE3JNpJNcKxFHF+jsZrYo0SxWoQ== }
dev: false dev: false
@ -1009,6 +986,14 @@ packages:
'@babel/runtime': 7.21.0 '@babel/runtime': 7.21.0
dev: false 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: /@rushstack/eslint-patch/1.2.0:
resolution: { integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== } resolution: { integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== }
dev: true dev: true
@ -5374,14 +5359,6 @@ packages:
tslib: 2.5.0 tslib: 2.5.0
dev: false 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: /util-deprecate/1.0.2:
resolution: { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== } resolution: { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== }
dev: true dev: true

View File

@ -1,4 +1,28 @@
-- Add migration script here -- 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 accounts
CREATE TABLE CREATE TABLE
accounts ( accounts (
@ -6,6 +30,7 @@ CREATE TABLE
privkey TEXT NOT NULL, privkey TEXT NOT NULL,
npub TEXT NOT NULL, npub TEXT NOT NULL,
nsec TEXT NOT NULL, nsec TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 0,
metadata JSON metadata JSON
); );
@ -40,5 +65,6 @@ CREATE TABLE
kind INTEGER NOT NULL DEFAULT 1, kind INTEGER NOT NULL DEFAULT 1,
tags TEXT NOT NULL, tags TEXT NOT NULL,
content TEXT NOT NULL, content TEXT NOT NULL,
relay TEXT,
is_multi BOOLEAN DEFAULT 0 is_multi BOOLEAN DEFAULT 0
); );

View File

@ -1,19 +1,16 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { Account } from '@components/accountBar/account'; import { Account } from '@components/accountBar/account';
import { currentUser } from '@stores/currentUser';
import LumeSymbol from '@assets/icons/Lume'; import LumeSymbol from '@assets/icons/Lume';
import { useStore } from '@nanostores/react';
import { PlusIcon } from '@radix-ui/react-icons'; import { PlusIcon } from '@radix-ui/react-icons';
import { useLocalStorage } from '@rehooks/local-storage';
import Link from 'next/link'; import Link from 'next/link';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import Database from 'tauri-plugin-sql-api'; import Database from 'tauri-plugin-sql-api';
export default function AccountBar() { export default function AccountBar() {
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
const $currentUser: any = useStore(currentUser); const [currentUser]: any = useLocalStorage('current-user');
const getAccounts = useCallback(async () => { const getAccounts = useCallback(async () => {
const db = await Database.load('sqlite:lume.db'); const db = await Database.load('sqlite:lume.db');
@ -30,7 +27,7 @@ export default function AccountBar() {
<div className="flex h-full flex-col items-center justify-between px-2 pt-12 pb-4"> <div className="flex h-full flex-col items-center justify-between px-2 pt-12 pb-4">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{users.map((user, index) => ( {users.map((user, index) => (
<Account key={index} user={user} current={$currentUser.pubkey} /> <Account key={index} user={user} current={currentUser.pubkey} />
))} ))}
<Link <Link
href="/onboarding" href="/onboarding"

View File

@ -4,10 +4,7 @@ import { RelayContext } from '@components/contexts/relay';
import { dateToUnix, hoursAgo } from '@utils/getDate'; import { dateToUnix, hoursAgo } from '@utils/getDate';
import { follows } from '@stores/follows'; import { useLocalStorage } from '@rehooks/local-storage';
import { relays } from '@stores/relays';
import { useStore } from '@nanostores/react';
import { memo, useCallback, useContext, useRef } from 'react'; import { memo, useCallback, useContext, useRef } from 'react';
export const NoteConnector = memo(function NoteConnector() { export const NoteConnector = memo(function NoteConnector() {
@ -16,8 +13,8 @@ export const NoteConnector = memo(function NoteConnector() {
const now = useRef(new Date()); const now = useRef(new Date());
const $follows = useStore(follows); const [follows]: any = useLocalStorage('follows');
const $relays = useStore(relays); const [relays]: any = useLocalStorage('relays');
const insertDB = useCallback( const insertDB = useCallback(
async (event: any) => { async (event: any) => {
@ -35,11 +32,11 @@ export const NoteConnector = memo(function NoteConnector() {
[ [
{ {
kinds: [1], kinds: [1],
authors: $follows, authors: follows,
since: dateToUnix(hoursAgo(12, now.current)), since: dateToUnix(hoursAgo(12, now.current)),
}, },
], ],
$relays, relays,
(event: any) => { (event: any) => {
insertDB(event).catch(console.error); insertDB(event).catch(console.error);
}, },

View File

@ -1,13 +1,56 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* 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'; import Database from 'tauri-plugin-sql-api';
export const DatabaseContext = createContext({}); 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 }) { export default function DatabaseProvider({ children }: { children: React.ReactNode }) {
const value = db; const db = useMemo(() => initDB, []);
return <DatabaseContext.Provider value={value}>{children}</DatabaseContext.Provider>; 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 <DatabaseContext.Provider value={{ db }}>{children}</DatabaseContext.Provider>;
} }

View File

@ -1,8 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* 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 * as Dialog from '@radix-ui/react-dialog';
import { useLocalStorage } from '@rehooks/local-storage';
import * as commands from '@uiw/react-md-editor/lib/commands'; import * as commands from '@uiw/react-md-editor/lib/commands';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { dateToUnix, useNostr } from 'nostr-react'; import { dateToUnix, useNostr } from 'nostr-react';
@ -17,9 +15,9 @@ export default function CreatePost() {
const { publish } = useNostr(); const { publish } = useNostr();
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const $currentUser: any = useStore(currentUser); const [currentUser]: any = useLocalStorage('current-user');
const pubkey = $currentUser.pubkey; const pubkey = currentUser.pubkey;
const privkey = $currentUser.privkey; const privkey = currentUser.privkey;
const postButton = { const postButton = {
name: 'post', name: 'post',
@ -27,9 +25,7 @@ export default function CreatePost() {
buttonProps: { className: 'cta-btn', 'aria-label': 'Post a message' }, buttonProps: { className: 'cta-btn', 'aria-label': 'Post a message' },
icon: ( icon: (
<div className="relative inline-flex h-10 w-16 transform cursor-pointer overflow-hidden rounded bg-zinc-900 px-2.5 ring-zinc-500/50 ring-offset-zinc-900 will-change-transform focus:outline-none focus:ring-1 focus:ring-offset-2 active:translate-y-1"> <div className="relative inline-flex h-10 w-16 transform cursor-pointer overflow-hidden rounded bg-zinc-900 px-2.5 ring-zinc-500/50 ring-offset-zinc-900 will-change-transform focus:outline-none focus:ring-1 focus:ring-offset-2 active:translate-y-1">
<span className="absolute inset-px z-10 inline-flex items-center justify-center rounded bg-zinc-900 text-zinc-200"> <span className="absolute inset-px z-10 inline-flex items-center justify-center rounded bg-zinc-900 text-zinc-200">Post</span>
Post
</span>
<span className="absolute inset-0 z-0 scale-x-[2.0] blur before:absolute before:inset-0 before:top-1/2 before:aspect-square before:animate-disco before:bg-gradient-conic before:from-gray-300 before:via-fuchsia-600 before:to-orange-600"></span> <span className="absolute inset-0 z-0 scale-x-[2.0] blur before:absolute before:inset-0 before:top-1/2 before:aspect-square before:animate-disco before:bg-gradient-conic before:from-gray-300 before:via-fuchsia-600 before:to-orange-600"></span>
</div> </div>
), ),

View File

@ -4,14 +4,12 @@ import { NoteConnector } from '@components/connectors/note';
import CreatePost from '@components/navigatorBar/createPost'; import CreatePost from '@components/navigatorBar/createPost';
import { ProfileMenu } from '@components/navigatorBar/profileMenu'; import { ProfileMenu } from '@components/navigatorBar/profileMenu';
import { currentUser } from '@stores/currentUser';
import { useStore } from '@nanostores/react';
import { PlusIcon } from '@radix-ui/react-icons'; import { PlusIcon } from '@radix-ui/react-icons';
import { useLocalStorage } from '@rehooks/local-storage';
export default function NavigatorBar() { export default function NavigatorBar() {
const $currentUser: any = useStore(currentUser); const [currentUser]: any = useLocalStorage('current-user');
const profile = $currentUser.metadata !== undefined ? JSON.parse($currentUser.metadata) : { display_name: null, username: null }; const profile = currentUser.metadata !== undefined ? JSON.parse(currentUser.metadata) : { display_name: null, username: null };
return ( return (
<div className="flex h-full flex-col flex-wrap justify-between overflow-hidden px-2 pt-3 pb-4"> <div className="flex h-full flex-col flex-wrap justify-between overflow-hidden px-2 pt-3 pb-4">
@ -25,7 +23,7 @@ export default function NavigatorBar() {
<div className="flex flex-col p-2"> <div className="flex flex-col p-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h5 className="font-semibold leading-tight text-zinc-100">{profile.display_name || ''}</h5> <h5 className="font-semibold leading-tight text-zinc-100">{profile.display_name || ''}</h5>
<ProfileMenu pubkey={$currentUser.pubkey} /> <ProfileMenu pubkey={currentUser.pubkey} />
</div> </div>
<span className="text-sm leading-tight text-zinc-500">@{profile.username || ''}</span> <span className="text-sm leading-tight text-zinc-500">@{profile.username || ''}</span>
</div> </div>

View File

@ -1,26 +1,18 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* 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 { HeartFilledIcon, HeartIcon } from '@radix-ui/react-icons';
import { useLocalStorage } from '@rehooks/local-storage';
import { dateToUnix, useNostr, useNostrEvents } from 'nostr-react'; import { dateToUnix, useNostr, useNostrEvents } from 'nostr-react';
import { getEventHash, signEvent } from 'nostr-tools'; import { getEventHash, signEvent } from 'nostr-tools';
import { useState } from 'react'; import { useState } from 'react';
export default function Reaction({ export default function Reaction({ eventID, eventPubkey }: { eventID: string; eventPubkey: string }) {
eventID,
eventPubkey,
}: {
eventID: string;
eventPubkey: string;
}) {
const { publish } = useNostr(); const { publish } = useNostr();
const [reaction, setReaction] = useState(0); const [reaction, setReaction] = useState(0);
const [isReact, setIsReact] = useState(false); const [isReact, setIsReact] = useState(false);
const $currentUser: any = useStore(currentUser); const [currentUser]: any = useLocalStorage('current-user');
const pubkey = $currentUser.pubkey; const pubkey = currentUser.pubkey;
const privkey = $currentUser.privkey; const privkey = currentUser.privkey;
const { onEvent } = useNostrEvents({ const { onEvent } = useNostrEvents({
filter: { filter: {
@ -65,15 +57,9 @@ export default function Reaction({
}; };
return ( return (
<button <button onClick={(e) => handleReaction(e)} className="group flex w-16 items-center gap-1.5 text-sm text-zinc-500">
onClick={(e) => handleReaction(e)}
className="group flex w-16 items-center gap-1.5 text-sm text-zinc-500">
<div className="rounded-lg p-1 group-hover:bg-zinc-600"> <div className="rounded-lg p-1 group-hover:bg-zinc-600">
{isReact ? ( {isReact ? <HeartFilledIcon className="h-4 w-4 group-hover:text-red-400" /> : <HeartIcon className="h-4 w-4 text-zinc-500" />}
<HeartFilledIcon className="h-4 w-4 group-hover:text-red-400" />
) : (
<HeartIcon className="h-4 w-4 text-zinc-500" />
)}
</div> </div>
<span>{reaction}</span> <span>{reaction}</span>
</button> </button>

View File

@ -2,12 +2,10 @@
import AccountBar from '@components/accountBar'; import AccountBar from '@components/accountBar';
import ActiveLink from '@components/activeLink'; import ActiveLink from '@components/activeLink';
import { currentUser } from '@stores/currentUser'; import { useLocalStorage } from '@rehooks/local-storage';
import { useStore } from '@nanostores/react';
export default function UserLayout({ children }: { children: React.ReactNode }) { export default function UserLayout({ children }: { children: React.ReactNode }) {
const $currentUser: any = useStore(currentUser); const [currentUser]: any = useLocalStorage('current-user');
return ( return (
<div className="flex h-full w-full flex-row"> <div className="flex h-full w-full flex-row">
@ -27,13 +25,13 @@ export default function UserLayout({ children }: { children: React.ReactNode })
</div> </div>
<div className="flex flex-col gap-1 text-zinc-500"> <div className="flex flex-col gap-1 text-zinc-500">
<ActiveLink <ActiveLink
href={`/profile/${$currentUser.pubkey}`} href={`/profile/${currentUser.pubkey}`}
activeClassName="ring-1 ring-white/10 dark:bg-zinc-900 dark:text-white" activeClassName="ring-1 ring-white/10 dark:bg-zinc-900 dark:text-white"
className="flex h-10 items-center gap-1 rounded-lg px-2.5 text-sm font-medium hover:bg-zinc-900"> className="flex h-10 items-center gap-1 rounded-lg px-2.5 text-sm font-medium hover:bg-zinc-900">
<span>Personal Page</span> <span>Personal Page</span>
</ActiveLink> </ActiveLink>
<ActiveLink <ActiveLink
href={`/profile/update?pubkey=${$currentUser.pubkey}`} href={`/profile/update?pubkey=${currentUser.pubkey}`}
activeClassName="ring-1 ring-white/10 dark:bg-zinc-900 dark:text-white" activeClassName="ring-1 ring-white/10 dark:bg-zinc-900 dark:text-white"
className="flex h-10 items-center gap-1 rounded-lg px-2.5 text-sm font-medium hover:bg-zinc-900"> className="flex h-10 items-center gap-1 rounded-lg px-2.5 text-sm font-medium hover:bg-zinc-900">
<span>Update Profile</span> <span>Update Profile</span>

View File

@ -2,9 +2,7 @@
import DatabaseProvider from '@components/contexts/database'; import DatabaseProvider from '@components/contexts/database';
import RelayProvider from '@components/contexts/relay'; import RelayProvider from '@components/contexts/relay';
import { relays } from '@stores/relays'; import { useLocalStorage } from '@rehooks/local-storage';
import { useStore } from '@nanostores/react';
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import { ReactElement, ReactNode } from 'react'; import { ReactElement, ReactNode } from 'react';
@ -23,12 +21,12 @@ type AppPropsWithLayout = AppProps & {
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) { export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
// Use the layout defined at the page level, if available // Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => page); const getLayout = Component.getLayout ?? ((page) => page);
// Get all relays // Get relays from localstorage
const $relays = useStore(relays); const [relays] = useLocalStorage('relays');
return ( return (
<DatabaseProvider> <DatabaseProvider>
<RelayProvider relays={$relays}>{getLayout(<Component {...pageProps} />)}</RelayProvider> <RelayProvider relays={relays}>{getLayout(<Component {...pageProps} />)}</RelayProvider>
</DatabaseProvider> </DatabaseProvider>
); );
} }

View File

@ -2,90 +2,32 @@
import BaseLayout from '@layouts/baseLayout'; import BaseLayout from '@layouts/baseLayout';
import FullLayout from '@layouts/fullLayout'; 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 LumeSymbol from '@assets/icons/Lume';
import { useLocalStorage } from '@rehooks/local-storage';
import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/api/notification';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useRouter } from 'next/router'; 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() { export default function Page() {
const db: any = useContext(DatabaseContext);
const router = useRouter(); const router = useRouter();
const [currentUser]: any = useLocalStorage('current-user');
const [loading, setLoading] = useState(true); 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(() => { useEffect(() => {
requestNotification().then(() => { console.log(currentUser);
getAccount() if (!currentUser) {
.then((res: any) => { setTimeout(() => {
if (res.length === 0) { setLoading(false);
setTimeout(() => { router.push('/onboarding');
setLoading(false); }, 1500);
router.push('/onboarding'); } else {
}, 1500); setTimeout(() => {
} else { setLoading(false);
// store current user in localstorage router.push('/feed/following');
currentUser.set(res[0]); }, 1500);
getFollows(res[0]) }
.then(async (res) => { }, [currentUser, router]);
// 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]);
return ( return (
<div className="relative flex h-full flex-col items-center justify-between"> <div className="relative flex h-full flex-col items-center justify-between">

View File

@ -5,16 +5,13 @@ import OnboardingLayout from '@layouts/onboardingLayout';
import { DatabaseContext } from '@components/contexts/database'; import { DatabaseContext } from '@components/contexts/database';
import { RelayContext } from '@components/contexts/relay'; 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 { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons';
import { useLocalStorage, writeStorage } from '@rehooks/local-storage';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent } from 'nostr-tools'; 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'; import { Config, names, uniqueNamesGenerator } from 'unique-names-generator';
const config: Config = { const config: Config = {
@ -22,11 +19,12 @@ const config: Config = {
}; };
export default function Page() { export default function Page() {
const db: any = useContext(DatabaseContext);
const router = useRouter(); const router = useRouter();
const { db }: any = useContext(DatabaseContext);
const relayPool: any = useContext(RelayContext); const relayPool: any = useContext(RelayContext);
const $relays = useStore(relays);
const [relays] = useLocalStorage('relays');
const [type, setType] = useState('password'); const [type, setType] = useState('password');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -47,13 +45,22 @@ export default function Page() {
}; };
// auto-generated profile // auto-generated profile
const data = { const data = useMemo(
display_name: name, () => ({
name: name, display_name: name,
username: name.toLowerCase(), name: name,
picture: 'https://bafybeidfsbrzqbvontmucteomoz2rkrxugu462l5hyhh6uioslkfzzs4oq.ipfs.w3s.link/avatar-11.png', username: name.toLowerCase(),
banner: 'https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg', 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 () => { const createAccount = async () => {
setLoading(true); setLoading(true);
@ -68,27 +75,25 @@ export default function Page() {
}; };
event.id = getEventHash(event); event.id = getEventHash(event);
event.sig = signEvent(event, privKey); event.sig = signEvent(event, privKey);
// publish to relays
relayPool.publish(event, $relays);
// save account to database insertDB()
await db.execute( .then(() => {
`INSERT INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubKey}", "${privKey}", "${npub}", "${nsec}", '${JSON.stringify(data)}')` // publish to relays
); relayPool.publish(event, relays);
// set currentUser in global state
// set currentUser in global state writeStorage('current-user', {
currentUser.set({ metadata: JSON.stringify(data),
metadata: JSON.stringify(data), npub: npub,
npub: npub, privkey: privKey,
privkey: privKey, pubkey: pubKey,
pubkey: pubKey, });
}); // redirect to pre-follow
setTimeout(() => {
// redirect to pre-follow setLoading(false);
setTimeout(() => { router.push('/onboarding/create/pre-follows');
setLoading(false); }, 1500);
router.push('/onboarding/following'); })
}, 1500); .catch(console.error);
}; };
return ( return (

View File

@ -6,28 +6,25 @@ import { DatabaseContext } from '@components/contexts/database';
import { truncate } from '@utils/truncate'; import { truncate } from '@utils/truncate';
import { currentUser } from '@stores/currentUser';
import data from '@assets/directory.json'; import data from '@assets/directory.json';
import { useStore } from '@nanostores/react';
import { CheckCircledIcon } from '@radix-ui/react-icons'; import { CheckCircledIcon } from '@radix-ui/react-icons';
import { useLocalStorage } from '@rehooks/local-storage';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useState } from 'react'; 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() { export default function Page() {
const db: any = useContext(DatabaseContext); const db: any = useContext(DatabaseContext);
const router = useRouter(); const router = useRouter();
const shuffle = (arr) => [...arr].sort(() => Math.random() - 0.5);
const [follow, setFollow] = useState([]); const [follow, setFollow] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [list] = useState(shuffle(data)); const [list] = useState(shuffle(data));
const $currentUser: any = useStore(currentUser); const [currentUser]: any = useLocalStorage('current-user');
const followUser = (e) => { const followUser = (e) => {
const npub = e.currentTarget.getAttribute('data-npub'); const npub = e.currentTarget.getAttribute('data-npub');
@ -36,11 +33,11 @@ export default function Page() {
const insertDB = async () => { const insertDB = async () => {
// self follow // 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 selected
follow.forEach(async (npub) => { follow.forEach(async (npub) => {
const { data } = nip19.decode(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")`);
}); });
}; };

View File

@ -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 (
<div className="flex h-full flex-col justify-between px-8">
<div>{/* spacer */}</div>
<motion.div layoutId="form">
<div className="mb-8 flex flex-col gap-3">
<motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
Fetching your profile...
</motion.h1>
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
As long as you have private key, you alway can sync your profile on every nostr client, so please keep your key safely
</motion.h2>
</div>
</motion.div>
<motion.div layoutId="action" className="pb-5">
<div className="flex h-10 items-center">
{loading === true ? (
<svg className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path
className="opacity-75"
fill="currentColor"
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"></path>
</svg>
) : (
<></>
)}
</div>
</motion.div>
</div>
);
}
Page.getLayout = function getLayout(
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal
) {
return (
<BaseLayout>
<OnboardingLayout>{page}</OnboardingLayout>
</BaseLayout>
);
};

View File

@ -10,9 +10,7 @@ export default function Page() {
<div className="flex h-full flex-col justify-between px-8"> <div className="flex h-full flex-col justify-between px-8">
<div>{/* spacer */}</div> <div>{/* spacer */}</div>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<motion.h1 <motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
layoutId="title"
className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
Other social network require email/password Other social network require email/password
<br /> <br />
nostr use{' '} nostr use{' '}
@ -21,8 +19,8 @@ export default function Page() {
</span> </span>
</motion.h1> </motion.h1>
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400"> <motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
If you have used nostr before, you can import your own private key. Otherwise, you can 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
create a new key or use auto-generated account created by system. created by system.
</motion.h2> </motion.h2>
<motion.div layoutId="form"></motion.div> <motion.div layoutId="form"></motion.div>
<motion.div layoutId="action" className="mt-4 flex gap-2"> <motion.div layoutId="action" className="mt-4 flex gap-2">
@ -32,7 +30,7 @@ export default function Page() {
Create new key Create new key
</Link> </Link>
<Link <Link
href="/onboarding/import" href="/onboarding/login"
className="hover:bg-zinc-900/2.5 transform rounded-lg border border-black/5 bg-zinc-800 px-3.5 py-2 font-medium ring-1 ring-inset ring-zinc-900/10 hover:text-zinc-900 active:translate-y-1 dark:text-zinc-300 dark:ring-white/10 dark:hover:bg-zinc-700 dark:hover:text-white"> className="hover:bg-zinc-900/2.5 transform rounded-lg border border-black/5 bg-zinc-800 px-3.5 py-2 font-medium ring-1 ring-inset ring-zinc-900/10 hover:text-zinc-900 active:translate-y-1 dark:text-zinc-300 dark:ring-white/10 dark:hover:bg-zinc-700 dark:hover:text-white">
Login with private key Login with private key
</Link> </Link>
@ -44,13 +42,7 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>

View File

@ -5,35 +5,69 @@ import OnboardingLayout from '@layouts/onboardingLayout';
import { DatabaseContext } from '@components/contexts/database'; import { DatabaseContext } from '@components/contexts/database';
import { RelayContext } from '@components/contexts/relay'; import { RelayContext } from '@components/contexts/relay';
import { relays } from '@stores/relays'; import { useLocalStorage } from '@rehooks/local-storage';
import { useStore } from '@nanostores/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import Link from 'next/link';
import { useRouter } from 'next/router'; 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() { export default function Page() {
const db: any = useContext(DatabaseContext); const { db }: any = useContext(DatabaseContext);
const relayPool: any = useContext(RelayContext); const relayPool: any = useContext(RelayContext);
const $relays = useStore(relays);
const [loading, setLoading] = useState(false);
const [relays] = useLocalStorage('relays');
const router = useRouter(); const router = useRouter();
const { pubkey }: any = router.query; const { privkey }: any = router.query;
const [follows, setFollows] = useState([null]); const pubkey = useMemo(() => (privkey ? getPublicKey(privkey) : null), [privkey]);
const [loading, setLoading] = useState(false);
// 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( relayPool.subscribe(
[ [
{ {
authors: [pubkey], authors: [pubkey],
kinds: [0], kinds: [0, 3],
since: 0, since: 0,
}, },
], ],
$relays, relays,
(event: any) => { (event: any) => {
setFollows(event.tags); if (event.kind === 0) {
insertAccount(event.content);
} else {
if (event.tags.length > 0) {
insertFollows(event.tags);
}
}
}, },
undefined, undefined,
(events: any, relayURL: any) => { (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 ( return (
<div className="flex h-full flex-col justify-between px-8"> <div className="flex h-full flex-col justify-between px-8">
<div>{/* spacer */}</div> <div>{/* spacer */}</div>
<motion.div layoutId="form"> <motion.div layoutId="form">
<div className="mb-8 flex flex-col gap-3"> <div className="mb-8 flex flex-col gap-3">
<motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent"> <motion.h1 layoutId="title" className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">
Fetching your follows... Fetching your profile...
</motion.h1> </motion.h1>
<motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400"> <motion.h2 layoutId="subtitle" className="w-3/4 text-zinc-400">
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
</motion.h2> </motion.h2>
</div> </div>
</motion.div> </motion.div>
@ -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"></path> 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"></path>
</svg> </svg>
) : ( ) : (
<></> <Link
href="/"
className="transform rounded-lg bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 px-3.5 py-2 font-medium active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30">
<span className="drop-shadow-lg">Finish</span>
</Link>
)} )}
</div> </div>
</motion.div> </motion.div>

View File

@ -44,7 +44,7 @@ export default function Page() {
try { try {
router.push({ router.push({
pathname: '/onboarding/fetch-profile', pathname: '/onboarding/login/fetch',
query: { privkey: privkey }, query: { privkey: privkey },
}); });
} catch (error) { } catch (error) {

View File

@ -2,9 +2,7 @@
import BaseLayout from '@layouts/baseLayout'; import BaseLayout from '@layouts/baseLayout';
import UserLayout from '@layouts/userLayout'; import UserLayout from '@layouts/userLayout';
import { currentUser } from '@stores/currentUser'; import { useLocalStorage } from '@rehooks/local-storage';
import { useStore } from '@nanostores/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { dateToUnix, useNostr } from 'nostr-react'; import { dateToUnix, useNostr } from 'nostr-react';
import { getEventHash, signEvent } from 'nostr-tools'; import { getEventHash, signEvent } from 'nostr-tools';
@ -28,11 +26,8 @@ export default function Page() {
const { publish } = useNostr(); const { publish } = useNostr();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const $currentUser: any = useStore(currentUser); const [currentUser]: any = useLocalStorage('current-user');
const profile = const profile = currentUser.metadata !== undefined ? JSON.parse(currentUser.metadata) : { display_name: null, username: null };
$currentUser.metadata !== undefined
? JSON.parse($currentUser.metadata)
: { display_name: null, username: null };
const { const {
register, register,
@ -48,28 +43,24 @@ export default function Page() {
content: JSON.stringify(data), content: JSON.stringify(data),
created_at: dateToUnix(), created_at: dateToUnix(),
kind: 0, kind: 0,
pubkey: $currentUser.pubkey, pubkey: currentUser.pubkey,
tags: [], tags: [],
}; };
event.id = getEventHash(event); event.id = getEventHash(event);
event.sig = signEvent(event, $currentUser.privkey); event.sig = signEvent(event, currentUser.privkey);
publish(event); publish(event);
// save account to database // save account to database
const db = await Database.load('sqlite:lume.db'); const db = await Database.load('sqlite:lume.db');
await db.execute( await db.execute(`UPDATE accounts SET metadata = '${JSON.stringify(data)}' WHERE pubkey = "${currentUser.pubkey}"`);
`UPDATE accounts SET metadata = '${JSON.stringify(data)}' WHERE pubkey = "${
$currentUser.pubkey
}"`
);
await db.close(); await db.close();
// set currentUser in global state // set currentUser in global state
currentUser.set({ currentUser.set({
metadata: JSON.stringify(data), metadata: JSON.stringify(data),
npub: $currentUser.npub, npub: currentUser.npub,
privkey: $currentUser.privkey, privkey: currentUser.privkey,
pubkey: $currentUser.pubkey, pubkey: currentUser.pubkey,
}); });
// redirect to newsfeed // redirect to newsfeed
@ -80,16 +71,11 @@ export default function Page() {
}; };
return ( return (
<form <form onSubmit={handleSubmit(onSubmit)} className="flex h-full w-full flex-col justify-between px-6">
onSubmit={handleSubmit(onSubmit)}
className="flex h-full w-full flex-col justify-between px-6">
<div className="mb-8 flex flex-col gap-3 pt-8"> <div className="mb-8 flex flex-col gap-3 pt-8">
<h1 className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent"> <h1 className="bg-gradient-to-br from-zinc-200 to-zinc-400 bg-clip-text text-3xl font-medium text-transparent">Update profile</h1>
Update profile
</h1>
<h2 className="w-3/4 text-zinc-400"> <h2 className="w-3/4 text-zinc-400">
Your profile will be published to all relays, as long as you have the private key, you 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
always can recover your profile in any client
</h2> </h2>
</div> </div>
<fieldset className="flex flex-col gap-2"> <fieldset className="flex flex-col gap-2">
@ -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" 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"
/> />
</div> </div>
<span className="text-sm text-red-400"> <span className="text-sm text-red-400">{errors.display_name && <p>{errors.display_name.message}</p>}</span>
{errors.display_name && <p>{errors.display_name.message}</p>}
</span>
</div> </div>
</div> </div>
<div className="grid grid-cols-4"> <div className="grid grid-cols-4">
@ -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" 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"
/> />
</div> </div>
<span className="text-sm text-red-400"> <span className="text-sm text-red-400">{errors.name && <p>{errors.name.message}</p>}</span>
{errors.name && <p>{errors.name.message}</p>}
</span>
</div> </div>
</div> </div>
<div className="grid grid-cols-4"> <div className="grid grid-cols-4">
@ -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" 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"
/> />
</div> </div>
<span className="text-sm text-red-400"> <span className="text-sm text-red-400">{errors.username && <p>{errors.username.message}</p>}</span>
{errors.username && <p>{errors.username.message}</p>}
</span>
</div> </div>
</div> </div>
<div className="grid grid-cols-4"> <div className="grid grid-cols-4">
@ -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" 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"
/> />
</div> </div>
<span className="text-sm text-red-400"> <span className="text-sm text-red-400">{errors.picture && <p>{errors.picture.message}</p>}</span>
{errors.picture && <p>{errors.picture.message}</p>}
</span>
</div> </div>
</div> </div>
<div className="grid grid-cols-4"> <div className="grid grid-cols-4">
@ -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" 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"
/> />
</div> </div>
<span className="text-sm text-red-400"> <span className="text-sm text-red-400">{errors.banner && <p>{errors.banner.message}</p>}</span>
{errors.banner && <p>{errors.banner.message}</p>}
</span>
</div> </div>
</div> </div>
<div className="grid grid-cols-4"> <div className="grid grid-cols-4">
@ -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" 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"
/> />
</div> </div>
<span className="text-sm text-red-400"> <span className="text-sm text-red-400">{errors.about && <p>{errors.about.message}</p>}</span>
{errors.about && <p>{errors.about.message}</p>}
</span>
</div> </div>
</div> </div>
</fieldset> </fieldset>
<div className="pb-5"> <div className="pb-5">
<div className="flex h-10 items-center"> <div className="flex h-10 items-center">
{loading === true ? ( {loading === true ? (
<svg <svg className="h-5 w-5 animate-spin text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
className="h-5 w-5 animate-spin text-white" <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24">
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"></circle>
<path <path
className="opacity-75" className="opacity-75"
fill="currentColor" fill="currentColor"
@ -231,13 +195,7 @@ export default function Page() {
} }
Page.getLayout = function getLayout( Page.getLayout = function getLayout(
page: page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) { ) {
return ( return (
<BaseLayout> <BaseLayout>

View File

@ -1,10 +0,0 @@
import { persistentAtom } from '@nanostores/persistent';
export const currentUser = persistentAtom(
'currentUser',
{},
{
encode: JSON.stringify,
decode: JSON.parse,
}
);

View File

@ -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);
},
});

View File

@ -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);
},
}
);

View File

@ -6,7 +6,6 @@
"@layouts/*": ["src/layouts/*"], "@layouts/*": ["src/layouts/*"],
"@components/*": ["src/components/*"], "@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"], "@utils/*": ["src/utils/*"],
"@stores/*": ["src/stores/*"],
"@assets/*": ["src/assets/*"] "@assets/*": ["src/assets/*"]
}, },
"target": "es2017", "target": "es2017",