updated onboarding process use new storage util

This commit is contained in:
Ren Amamiya 2023-03-23 15:48:57 +07:00
parent 705430a11e
commit 18a9bf3e49
6 changed files with 196 additions and 212 deletions

View File

@ -1,13 +1,13 @@
import { DatabaseContext } from '@components/contexts/database';
import { ImageWithFallback } from '@components/imageWithFallback';
import { createCacheProfile } from '@utils/storage';
import { truncate } from '@utils/truncate';
import { fetch } from '@tauri-apps/api/http';
import { memo, useCallback, useContext, useEffect, useState } from 'react';
import destr from 'destr';
import { memo, useCallback, useEffect, useState } from 'react';
export const UserBase = memo(function UserBase({ pubkey }: { pubkey: string }) {
const { db }: any = useContext(DatabaseContext);
const [profile, setProfile] = useState(null);
const fetchProfile = useCallback(async (id: string) => {
@ -15,24 +15,17 @@ export const UserBase = memo(function UserBase({ pubkey }: { pubkey: string }) {
method: 'GET',
timeout: 30,
});
return res;
return res.data;
}, []);
const cacheProfile = useCallback(
async (event) => {
// insert to database
await db.execute('INSERT OR IGNORE INTO cache_profiles (id, metadata) VALUES (?, ?);', [pubkey, event.content]);
// update state
setProfile(JSON.parse(event.content));
},
[db, pubkey]
);
useEffect(() => {
fetchProfile(pubkey)
.then((res) => cacheProfile(res))
.then((res: any) => {
setProfile(destr(res.content));
createCacheProfile(res.pubkey, res.content);
})
.catch(console.error);
}, [fetchProfile, cacheProfile, pubkey]);
}, [fetchProfile, pubkey]);
return (
<div className="flex items-center gap-2">
@ -41,7 +34,7 @@ export const UserBase = memo(function UserBase({ pubkey }: { pubkey: string }) {
<ImageWithFallback src={profile.picture} alt={pubkey} fill={true} className="rounded-full object-cover" />
)}
</div>
<div className="flex w-full flex-1 flex-col items-start">
<div className="flex w-full flex-1 flex-col items-start text-start">
<span className="font-medium leading-tight text-zinc-200">{profile?.display_name || profile?.name}</span>
<span className="text-sm leading-tight text-zinc-400">{truncate(pubkey, 16, ' .... ')}</span>
</div>

View File

@ -1,14 +1,13 @@
import BaseLayout from '@layouts/base';
import { DatabaseContext } from '@components/contexts/database';
import { RelayContext } from '@components/contexts/relay';
import { pool } from '@utils/pool';
import { createAccount, getAllRelays } from '@utils/storage';
import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons';
import { useLocalStorage, writeStorage } from '@rehooks/local-storage';
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, useMemo, useState } from 'react';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useMemo, useState } from 'react';
import { Config, names, uniqueNamesGenerator } from 'unique-names-generator';
const config: Config = {
@ -18,11 +17,6 @@ const config: Config = {
export default function Page() {
const router = useRouter();
const { db }: any = useContext(DatabaseContext);
const relayPool: any = useContext(RelayContext);
const [relays] = useLocalStorage('relays');
const [type, setType] = useState('password');
const [loading, setLoading] = useState(false);
@ -33,16 +27,8 @@ export default function Page() {
const npub = nip19.npubEncode(pubKey);
const nsec = nip19.nsecEncode(privKey);
// toggle privatek key
const showPrivateKey = () => {
if (type === 'password') {
setType('text');
} else {
setType('password');
}
};
// auto-generated profile
const data = useMemo(
// auto-generated profile metadata
const metadata = useMemo(
() => ({
display_name: name,
name: name,
@ -52,23 +38,35 @@ export default function Page() {
}),
[name]
);
// insert to database
const insertDB = async () => {
await db.execute('INSERT OR IGNORE INTO accounts (id, privkey, npub, nsec, metadata) VALUES (?, ?, ?, ?, ?)', [
pubKey,
privKey,
npub,
nsec,
data,
]);
// build profile
const data = useMemo(
() => ({
pubkey: pubKey,
privkey: privKey,
npub: npub,
nsec: nsec,
metadata: metadata,
}),
[metadata, npub, nsec, privKey, pubKey]
);
// toggle privatek key
const showPrivateKey = () => {
if (type === 'password') {
setType('text');
} else {
setType('password');
}
};
// build event and broadcast to all relays
const createAccount = () => {
// create account and broadcast to all relays
const submit = () => {
setLoading(true);
// build event
const event: any = {
content: JSON.stringify(data),
content: JSON.stringify(metadata),
created_at: Math.floor(Date.now() / 1000),
kind: 0,
pubkey: pubKey,
@ -76,23 +74,20 @@ export default function Page() {
};
event.id = getEventHash(event);
event.sig = signEvent(event, privKey);
// insert to database then broadcast
insertDB()
createAccount(data)
.then(() => {
// publish to relays
relayPool.publish(event, relays);
// set currentUser in global state
writeStorage('current-user', {
metadata: JSON.stringify(data),
npub: npub,
privkey: privKey,
id: pubKey,
});
// redirect to pre-follow
setTimeout(() => {
setLoading(false);
router.push('/onboarding/create/step-2');
}, 1500);
getAllRelays()
.then((res) => {
// publish to relays
pool(res).publish(event, res);
router.push({
pathname: '/onboarding/create/step-2',
query: { id: pubKey, privkey: privKey },
});
})
.catch(console.error);
})
.catch(console.error);
};
@ -146,12 +141,12 @@ export default function Page() {
<div className="relative w-full rounded-lg border border-black/5 px-3.5 py-4 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-600">
<div className="flex space-x-4">
<div className="relative h-10 w-10 rounded-full">
<Image className="inline-block rounded-full" src={data.picture} alt="" fill={true} />
<Image className="inline-block rounded-full" src={metadata.picture} alt="" fill={true} />
</div>
<div className="flex-1 space-y-4 py-1">
<div className="flex items-center gap-2">
<p className="font-semibold">{data.display_name}</p>
<p className="text-zinc-400">@{data.username}</p>
<p className="font-semibold">{metadata.display_name}</p>
<p className="text-zinc-400">@{metadata.username}</p>
</div>
<div className="space-y-3">
<div className="grid grid-cols-3 gap-4">
@ -183,7 +178,7 @@ export default function Page() {
</svg>
) : (
<button
onClick={() => createAccount()}
onClick={() => submit()}
className="w-full transform rounded-lg bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 px-3.5 py-2.5 font-medium text-zinc-800 active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
>
<span className="drop-shadow-lg">Continue </span>

View File

@ -1,24 +1,15 @@
import BaseLayout from '@layouts/base';
import { DatabaseContext } from '@components/contexts/database';
import { RelayContext } from '@components/contexts/relay';
import { UserBase } from '@components/user/base';
import { pool } from '@utils/pool';
import { createFollows, getAllRelays } from '@utils/storage';
import { CheckCircledIcon } from '@radix-ui/react-icons';
import useLocalStorage from '@rehooks/local-storage';
import { createClient } from '@supabase/supabase-js';
import { useRouter } from 'next/router';
import { getEventHash, nip19, signEvent } from 'nostr-tools';
import {
JSXElementConstructor,
Key,
ReactElement,
ReactFragment,
ReactPortal,
useContext,
useEffect,
useState,
} from 'react';
import { getEventHash, signEvent } from 'nostr-tools';
import { JSXElementConstructor, Key, ReactElement, ReactFragment, ReactPortal, useEffect, useState } from 'react';
const supabase = createClient(
'https://niwaazauwnrwiwmnocnn.supabase.co',
@ -62,12 +53,7 @@ const initialList = [
export default function Page() {
const router = useRouter();
const { db }: any = useContext(DatabaseContext);
const relayPool: any = useContext(RelayContext);
const [currentUser]: any = useLocalStorage('current-user');
const [relays] = useLocalStorage('relays');
const { id, privkey }: any = router.query;
const [loading, setLoading] = useState(false);
const [list, setList]: any = useState(initialList);
@ -78,31 +64,19 @@ export default function Page() {
const arr = follows.includes(pubkey) ? follows.filter((i) => i !== pubkey) : [...follows, pubkey];
setFollows(arr);
};
// insert follow to database
const insertDB = async () => {
// self follow
await db.execute(
`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${currentUser.id}", "${currentUser.id}", "0")`
);
// follow selected
follows.forEach(async (pubkey) => {
await db.execute(
`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${pubkey}", "${currentUser.id}", "0")`
);
});
};
// build event tags
const createTags = () => {
const tags = [];
const tags = () => {
const arr = [];
// push item to tags
follows.forEach((item) => {
tags.push(['p', item]);
arr.push(['p', item]);
});
return tags;
return arr;
};
// commit and publish to relays
const createFollows = () => {
// save follows to database then broadcast
const submit = () => {
setLoading(true);
// build event
@ -110,21 +84,25 @@ export default function Page() {
content: '',
created_at: Math.floor(Date.now() / 1000),
kind: 3,
pubkey: currentUser.id,
tags: createTags(),
pubkey: id,
tags: tags(),
};
event.id = getEventHash(event);
event.sig = signEvent(event, currentUser.privkey);
event.sig = signEvent(event, privkey);
insertDB().then(() => {
// publish to relays
relayPool.publish(event, relays);
// redirect to home
setTimeout(() => {
setLoading(false);
router.push('/');
}, 1000);
});
createFollows(follows, id, 0)
.then((res) => {
if (res === 'ok') {
getAllRelays()
.then((res) => {
// publish to relays
pool(res).publish(event, res);
router.push('/');
})
.catch(console.error);
}
})
.catch(console.error);
};
useEffect(() => {
@ -174,7 +152,7 @@ export default function Page() {
{follows.length >= 10 && (
<div className="fixed bottom-0 left-0 z-10 flex h-24 w-full items-center justify-center">
<button
onClick={() => createFollows()}
onClick={() => submit()}
className="relative z-20 inline-flex w-36 transform items-center justify-center rounded-full bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 px-3.5 py-2.5 font-medium text-zinc-800 shadow-xl active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
>
{loading === true ? (

View File

@ -1,105 +1,75 @@
import BaseLayout from '@layouts/base';
import { DatabaseContext } from '@components/contexts/database';
import { RelayContext } from '@components/contexts/relay';
import { pool } from '@utils/pool';
import { createAccount, createFollows, getAllRelays } from '@utils/storage';
import { truncate } from '@utils/truncate';
import { useLocalStorage, writeStorage } from '@rehooks/local-storage';
import destr from 'destr';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { getPublicKey, nip19 } from 'nostr-tools';
import {
JSXElementConstructor,
ReactElement,
ReactFragment,
ReactPortal,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useEffect, useState } from 'react';
const tags = (arr) => {
const newarr = [];
// push item to newarr
arr.forEach((item) => {
newarr.push(['p', item]);
});
return newarr;
};
export default function Page() {
const { db }: any = useContext(DatabaseContext);
const relayPool: any = useContext(RelayContext);
const router = useRouter();
const { privkey }: any = router.query;
const privkey: any = router.query.privkey;
const pubkey = getPublicKey(privkey);
const [relays] = useLocalStorage('relays');
const [profile, setProfile] = useState({ picture: '', display_name: '', username: '' });
const [profile, setProfile] = useState(null);
const pubkey = useMemo(() => (privkey ? getPublicKey(privkey) : null), [privkey]);
useEffect(() => {
getAllRelays()
.then((res) => {
pool(res).subscribe(
[
{
authors: [pubkey],
kinds: [0, 3],
since: 0,
},
],
res,
(event: any) => {
if (event.kind === 0) {
const data = {
pubkey: pubkey,
privkey: privkey,
npub: nip19.npubEncode(pubkey),
nsec: nip19.nsecEncode(privkey),
metadata: event.content,
};
setProfile(destr(event.content));
createAccount(data);
} else {
if (event.tags.length > 0) {
createFollows(tags(event.tags), pubkey, 0);
}
}
},
undefined,
undefined,
{
unsubscribeOnEose: true,
}
);
})
.catch(console.error);
}, [privkey, pubkey]);
// save account to database
const insertAccount = useCallback(
async (metadata) => {
const npub = privkey ? nip19.npubEncode(pubkey) : null;
const nsec = privkey ? nip19.nsecEncode(privkey) : null;
// insert to database
await db.execute('INSERT OR IGNORE INTO accounts (id, privkey, npub, nsec, metadata) VALUES (?, ?, ?, ?, ?)', [
pubkey,
privkey,
npub,
nsec,
metadata,
]);
// write to localstorage
writeStorage('current-user', { id: pubkey, privkey: privkey, npub: npub, nsec: nsec, metadata: metadata });
// update state
setProfile(JSON.parse(metadata));
},
[db, privkey, pubkey]
);
// save follows to database
const insertFollows = useCallback(
async (follows) => {
follows.forEach(async (item) => {
if (item) {
// insert to database
await db.execute(
`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${item[1]}", "${pubkey}", "0")`
);
}
});
},
[db, pubkey]
);
// submit then redirect to home
const submit = () => {
router.push('/');
};
useEffect(() => {
const unsubscribe = relayPool.subscribe(
[
{
authors: [pubkey],
kinds: [0, 3],
since: 0,
},
],
relays,
(event: any) => {
if (event.kind === 0) {
insertAccount(event.content);
} else {
if (event.tags.length > 0) {
insertFollows(event.tags);
}
}
},
undefined,
(events: any, relayURL: any) => {
console.log(events, relayURL);
}
);
return () => {
unsubscribe();
};
}, [insertAccount, insertFollows, pubkey, relayPool, relays]);
return (
<div className="grid h-full w-full grid-rows-5">
<div className="row-span-1 flex items-center justify-center">
@ -115,13 +85,13 @@ export default function Page() {
<div className="w-full rounded-lg bg-zinc-900 p-4 shadow-input ring-1 ring-zinc-800">
<div className="flex space-x-4">
<div className="relative h-10 w-10 rounded-full">
<Image className="inline-block rounded-full" src={profile.picture} alt="" fill={true} />
<Image className="inline-block rounded-full" src={profile?.picture} alt="" fill={true} />
</div>
<div className="flex-1 space-y-4 py-1">
<div className="flex items-center gap-2">
<p className="font-semibold">{profile.display_name}</p>
<p className="font-semibold">{profile?.display_name || profile?.name}</p>
<span className="leading-tight text-zinc-500">·</span>
<p className="text-zinc-500">@{profile.username}</p>
<p className="text-zinc-500">@{profile?.username || truncate(pubkey, 16, ' .... ')}</p>
</div>
<div className="space-y-3">
<div className="grid grid-cols-3 gap-4">

6
src/utils/pool.tsx Normal file
View File

@ -0,0 +1,6 @@
import { RelayPool } from 'nostr-relaypool';
export function pool({ relays }: { relays: any }) {
const createPool = new RelayPool(relays, { useEventCache: false, logSubscriptions: false });
return createPool;
}

View File

@ -15,17 +15,59 @@ export async function connect(): Promise<Database> {
// get all relays
export async function getAllRelays() {
const db = await connect();
return await db.select('SELECT relay_url FROM relays WHERE relay_status = "1"');
const result: any = await db.select('SELECT relay_url FROM relays WHERE relay_status = "1";');
return result.reduce((relays, { relay_url }) => {
relays.push(relay_url);
return relays;
}, []);
}
// get active account
export async function getActiveAccount() {
const db = await connect();
return await db.select(`SELECT * FROM accounts LIMIT 1`);
return await db.select(`SELECT * FROM accounts LIMIT 1;`);
}
// get all follows by account id
export async function getAllFollowsByID(id: string) {
export async function getAllFollowsByID(id) {
const db = await connect();
return await db.select(`SELECT pubkey FROM follows WHERE account = "${id}"`);
return await db.select(`SELECT pubkey FROM follows WHERE account = "${id}";`);
}
// create account
export async function createAccount(data) {
const db = await connect();
return await db.execute(
'INSERT OR IGNORE INTO accounts (id, privkey, npub, nsec, metadata) VALUES (?, ?, ?, ?, ?);',
[data.pubkey, data.privkey, data.npub, data.nsec, data.metadata]
);
}
// create follow
export async function createFollow(pubkey, account, kind) {
const db = await connect();
return await db.execute('INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES (?, ?, ?);', [
pubkey,
account,
kind || 0,
]);
}
// create follow
export async function createFollows(data, account, kind) {
const db = await connect();
data.forEach(async (item) => {
await db.execute('INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES (?, ?, ?);', [
item,
account,
kind || 0,
]);
});
return 'ok';
}
// create cache profile
export async function createCacheProfile(id, metadata) {
const db = await connect();
return await db.execute('INSERT OR IGNORE INTO cache_profiles (id, metadata) VALUES (?, ?);', [id, metadata]);
}