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

@@ -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 (

View File

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

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>{/* spacer */}</div>
<div className="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">
Other social network require email/password
<br />
nostr use{' '}
@@ -21,8 +19,8 @@ export default function Page() {
</span>
</motion.h1>
<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
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.
</motion.h2>
<motion.div layoutId="form"></motion.div>
<motion.div layoutId="action" className="mt-4 flex gap-2">
@@ -32,7 +30,7 @@ export default function Page() {
Create new key
</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">
Login with private key
</Link>
@@ -44,13 +42,7 @@ export default function Page() {
}
Page.getLayout = function getLayout(
page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
page: string | number | boolean | ReactElement<unknown, string | JSXElementConstructor<unknown>> | ReactFragment | ReactPortal
) {
return (
<BaseLayout>

View File

@@ -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 (
<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 follows...
Fetching your profile...
</motion.h1>
<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>
</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>
</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>
</motion.div>

View File

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