mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-19 19:46:34 +00:00
refactor
This commit is contained in:
parent
225179dd6d
commit
5c7b18bf29
@ -6,13 +6,11 @@
|
|||||||
CREATE TABLE
|
CREATE TABLE
|
||||||
accounts (
|
accounts (
|
||||||
id INTEGER NOT NULL PRIMARY KEY,
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
npub TEXT NOT NULL UNIQUE,
|
||||||
pubkey TEXT NOT NULL UNIQUE,
|
pubkey TEXT NOT NULL UNIQUE,
|
||||||
privkey TEXT NOT NULL,
|
privkey TEXT NOT NULL,
|
||||||
|
follows JSON,
|
||||||
is_active INTEGER NOT NULL DEFAULT 0,
|
is_active INTEGER NOT NULL DEFAULT 0,
|
||||||
follows TEXT,
|
|
||||||
channels TEXT,
|
|
||||||
chats TEXT,
|
|
||||||
metadata TEXT,
|
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -20,8 +18,18 @@ CREATE TABLE
|
|||||||
CREATE TABLE
|
CREATE TABLE
|
||||||
plebs (
|
plebs (
|
||||||
id INTEGER NOT NULL PRIMARY KEY,
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
pubkey TEXT NOT NULL UNIQUE,
|
npub TEXT NOT NULL UNIQUE,
|
||||||
metadata TEXT,
|
display_name TEXT,
|
||||||
|
name TEXT,
|
||||||
|
username TEXT,
|
||||||
|
about TEXT,
|
||||||
|
bio TEXT,
|
||||||
|
website TEXT,
|
||||||
|
picture TEXT,
|
||||||
|
banner TEXT,
|
||||||
|
nip05 TEXT,
|
||||||
|
lud06 TEXT,
|
||||||
|
lud16 TEXT,
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -33,10 +41,11 @@ CREATE TABLE
|
|||||||
account_id INTEGER NOT NULL,
|
account_id INTEGER NOT NULL,
|
||||||
pubkey TEXT NOT NULL,
|
pubkey TEXT NOT NULL,
|
||||||
kind INTEGER NOT NULL DEFAULT 1,
|
kind INTEGER NOT NULL DEFAULT 1,
|
||||||
tags TEXT NOT NULL,
|
tags JSON,
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
parent_id TEXT,
|
parent_id TEXT,
|
||||||
|
parent_comment_id TEXT,
|
||||||
FOREIGN KEY (account_id) REFERENCES accounts (id)
|
FOREIGN KEY (account_id) REFERENCES accounts (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -45,7 +54,9 @@ CREATE TABLE
|
|||||||
channels (
|
channels (
|
||||||
id INTEGER NOT NULL PRIMARY KEY,
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
event_id TEXT NOT NULL UNIQUE,
|
event_id TEXT NOT NULL UNIQUE,
|
||||||
metadata TEXT NOT NULL,
|
name TEXT,
|
||||||
|
about TEXT,
|
||||||
|
picture TEXT,
|
||||||
created_at INTEGER NOT NULL
|
created_at INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
CREATE TABLE
|
CREATE TABLE
|
||||||
chats (
|
chats (
|
||||||
id INTEGER NOT NULL PRIMARY KEY,
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
account_id INTEGER NOT NULL,
|
event_id TEXT NOT NULL UNIQUE,
|
||||||
pubkey TEXT NOT NULL UNIQUE,
|
receiver_pubkey INTEGER NOT NULL,
|
||||||
created_at INTEGER NOT NULL,
|
sender_pubkey TEXT NOT NULL,
|
||||||
FOREIGN KEY (account_id) REFERENCES accounts (id)
|
content TEXT NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL
|
||||||
);
|
);
|
@ -1,40 +1,57 @@
|
|||||||
-- Add migration script here
|
-- Add migration script here
|
||||||
INSERT
|
INSERT
|
||||||
OR IGNORE INTO channels (event_id, pubkey, metadata, created_at)
|
OR IGNORE INTO channels (
|
||||||
|
event_id,
|
||||||
|
pubkey,
|
||||||
|
name,
|
||||||
|
about,
|
||||||
|
picture,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
VALUES
|
VALUES
|
||||||
(
|
(
|
||||||
"e3cadf5beca1b2af1cddaa41a633679bedf263e3de1eb229c6686c50d85df753",
|
"e3cadf5beca1b2af1cddaa41a633679bedf263e3de1eb229c6686c50d85df753",
|
||||||
"126103bfddc8df256b6e0abfd7f3797c80dcc4ea88f7c2f87dd4104220b4d65f",
|
"126103bfddc8df256b6e0abfd7f3797c80dcc4ea88f7c2f87dd4104220b4d65f",
|
||||||
'{"name":"lume-general","picture":"https://void.cat/d/UNyxBmAh1MUx5gQTX95jyf.webp","about":"General channel for Lume"}',
|
"lume-general",
|
||||||
|
"General channel for Lume",
|
||||||
|
"https://void.cat/d/UNyxBmAh1MUx5gQTX95jyf.webp",
|
||||||
1681898574
|
1681898574
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT
|
INSERT
|
||||||
OR IGNORE INTO channels (event_id, pubkey, metadata, created_at)
|
OR IGNORE INTO channels (
|
||||||
VALUES
|
event_id,
|
||||||
(
|
pubkey,
|
||||||
"1abf8948d2fd05dd1836b33b324dca65138b2e80c77b27eeeed4323246efba4d",
|
name,
|
||||||
"126103bfddc8df256b6e0abfd7f3797c80dcc4ea88f7c2f87dd4104220b4d65f",
|
about,
|
||||||
'{"picture":"https://void.cat/d/MsqUKXXC4SxDfmT2KiHovJ.webp","name":"Arcade Open R&D","about":""}',
|
picture,
|
||||||
1682252461
|
created_at
|
||||||
);
|
)
|
||||||
|
|
||||||
INSERT
|
|
||||||
OR IGNORE INTO channels (event_id, pubkey, metadata, created_at)
|
|
||||||
VALUES
|
VALUES
|
||||||
(
|
(
|
||||||
"42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5",
|
"42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5",
|
||||||
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||||
'{"about":"General discussion about the Amethyst Nostr client for Android","name":"Amethyst Users","picture":"https://nostr.build/i/5970.png"}',
|
"Amethyst Users",
|
||||||
|
"General discussion about the Amethyst Nostr client for Android",
|
||||||
|
"https://nostr.build/i/5970.png",
|
||||||
1674092111
|
1674092111
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT
|
INSERT
|
||||||
OR IGNORE INTO channels (event_id, pubkey, metadata, created_at)
|
OR IGNORE INTO channels (
|
||||||
|
event_id,
|
||||||
|
pubkey,
|
||||||
|
name,
|
||||||
|
about,
|
||||||
|
picture,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
VALUES
|
VALUES
|
||||||
(
|
(
|
||||||
"25e5c82273a271cb1a840d0060391a0bf4965cafeb029d5ab55350b418953fbb",
|
"25e5c82273a271cb1a840d0060391a0bf4965cafeb029d5ab55350b418953fbb",
|
||||||
"ed1d0e1f743a7d19aa2dfb0162df73bacdbc699f67cc55bb91a98c35f7deac69",
|
"ed1d0e1f743a7d19aa2dfb0162df73bacdbc699f67cc55bb91a98c35f7deac69",
|
||||||
'{"about":"","name":"Nostr","picture":"https://cloudflare-ipfs.com/ipfs/QmTN4Eas9atUULVbEAbUU8cowhtvK7g3t7jfKztY7wc8eP?.png"}',
|
"Nostr",
|
||||||
|
"",
|
||||||
|
"https://cloudflare-ipfs.com/ipfs/QmTN4Eas9atUULVbEAbUU8cowhtvK7g3t7jfKztY7wc8eP?.png",
|
||||||
1661333723
|
1661333723
|
||||||
);
|
);
|
@ -1,6 +0,0 @@
|
|||||||
-- Add migration script here
|
|
||||||
ALTER TABLE accounts
|
|
||||||
DROP COLUMN channels;
|
|
||||||
|
|
||||||
ALTER TABLE accounts
|
|
||||||
DROP COLUMN chats;
|
|
@ -87,12 +87,6 @@ fn main() {
|
|||||||
sql: include_str!("../migrations/20230425050745_add_blacklist_model.sql"),
|
sql: include_str!("../migrations/20230425050745_add_blacklist_model.sql"),
|
||||||
kind: MigrationKind::Up,
|
kind: MigrationKind::Up,
|
||||||
},
|
},
|
||||||
Migration {
|
|
||||||
version: 20230427081017,
|
|
||||||
description: "clean up account",
|
|
||||||
sql: include_str!("../migrations/20230427081017_clean_up_account.sql"),
|
|
||||||
kind: MigrationKind::Up,
|
|
||||||
},
|
|
||||||
Migration {
|
Migration {
|
||||||
version: 20230521092300,
|
version: 20230521092300,
|
||||||
description: "create block",
|
description: "create block",
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
|
|
||||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||||
|
|
||||||
import { useProfile } from "@utils/hooks/useProfile";
|
import { useProfile } from "@utils/hooks/useProfile";
|
||||||
import { shortenKey } from "@utils/shortenKey";
|
import { shortenKey } from "@utils/shortenKey";
|
||||||
|
|
||||||
export default function User({ pubkey }: { pubkey: string }) {
|
export function User({ pubkey }: { pubkey: string }) {
|
||||||
const { user } = useProfile(pubkey);
|
const { user } = useProfile(pubkey);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -15,7 +13,6 @@ export default function User({ pubkey }: { pubkey: string }) {
|
|||||||
src={user?.picture || DEFAULT_AVATAR}
|
src={user?.picture || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
className="h-11 w-11 rounded-md object-cover"
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import EyeOffIcon from "@icons/eyeOff";
|
import EyeOffIcon from "@icons/eyeOff";
|
||||||
import EyeOnIcon from "@icons/eyeOn";
|
import EyeOnIcon from "@icons/eyeOn";
|
||||||
|
import { createAccount } from "@utils/storage";
|
||||||
import { onboardingAtom } from "@stores/onboarding";
|
|
||||||
|
|
||||||
import { useSetAtom } from "jotai";
|
|
||||||
import { generatePrivateKey, getPublicKey, nip19 } from "nostr-tools";
|
import { generatePrivateKey, getPublicKey, nip19 } from "nostr-tools";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const [type, setType] = useState("password");
|
const [type, setType] = useState("password");
|
||||||
const setOnboarding = useSetAtom(onboardingAtom);
|
|
||||||
const privkey = useMemo(() => generatePrivateKey(), []);
|
const privkey = useMemo(() => generatePrivateKey(), []);
|
||||||
|
|
||||||
const pubkey = getPublicKey(privkey);
|
const pubkey = getPublicKey(privkey);
|
||||||
@ -26,9 +22,12 @@ export function Page() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = () => {
|
const submit = async () => {
|
||||||
setOnboarding((prev) => ({ ...prev, pubkey: pubkey, privkey: privkey }));
|
const account = await createAccount(npub, pubkey, privkey, null, 1);
|
||||||
|
|
||||||
|
if (account) {
|
||||||
navigate("/app/auth/create/step-2");
|
navigate("/app/auth/create/step-2");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,38 +1,53 @@
|
|||||||
import { AvatarUploader } from "@shared/avatarUploader";
|
import { AvatarUploader } from "@shared/avatarUploader";
|
||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
import { onboardingAtom } from "@stores/onboarding";
|
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||||
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { useAtom } from "jotai";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
|
const pool: any = useContext(RelayContext);
|
||||||
|
|
||||||
|
const { account } = useActiveAccount();
|
||||||
|
|
||||||
const [image, setImage] = useState(DEFAULT_AVATAR);
|
const [image, setImage] = useState(DEFAULT_AVATAR);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [onboarding, setOnboarding] = useAtom(onboardingAtom);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
formState: { isDirty, isValid },
|
formState: { isDirty, isValid },
|
||||||
} = useForm({
|
} = useForm();
|
||||||
defaultValues: async () => {
|
|
||||||
if (onboarding.metadata) {
|
|
||||||
return onboarding.metadata;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setOnboarding((prev) => ({ ...prev, metadata: data }));
|
|
||||||
navigate("/app/auth/create/step-3");
|
const event: any = {
|
||||||
|
content: JSON.stringify(data),
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
kind: 0,
|
||||||
|
pubkey: account.pubkey,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
event.id = getEventHash(event);
|
||||||
|
event.sig = getSignature(event, account.privkey);
|
||||||
|
|
||||||
|
// publish
|
||||||
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
|
||||||
|
// redirect to step 3
|
||||||
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
navigate("/app/auth/create/step-3", {
|
||||||
|
overwriteLastHistoryEntry: true,
|
||||||
|
}),
|
||||||
|
2000,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,17 +1,11 @@
|
|||||||
import User from "@app/auth/components/user";
|
import { User } from "@app/auth/components/user";
|
||||||
|
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
|
||||||
|
|
||||||
import CheckCircleIcon from "@icons/checkCircle";
|
import CheckCircleIcon from "@icons/checkCircle";
|
||||||
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
import { onboardingAtom } from "@stores/onboarding";
|
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||||
|
import { updateAccount } from "@utils/storage";
|
||||||
import { createAccount, createPleb } from "@utils/storage";
|
|
||||||
import { arrayToNIP02 } from "@utils/transform";
|
import { arrayToNIP02 } from "@utils/transform";
|
||||||
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
@ -117,9 +111,10 @@ const initialList = [
|
|||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
|
||||||
|
const { account } = useActiveAccount();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [follows, setFollows] = useState([]);
|
const [follows, setFollows] = useState([]);
|
||||||
const [onboarding] = useAtom(onboardingAtom);
|
|
||||||
|
|
||||||
// toggle follow state
|
// toggle follow state
|
||||||
const toggleFollow = (pubkey: string) => {
|
const toggleFollow = (pubkey: string) => {
|
||||||
@ -129,68 +124,37 @@ export function Page() {
|
|||||||
setFollows(arr);
|
setFollows(arr);
|
||||||
};
|
};
|
||||||
|
|
||||||
const broadcastAccount = () => {
|
|
||||||
// build event
|
|
||||||
const event: any = {
|
|
||||||
content: JSON.stringify(onboarding.metadata),
|
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
|
||||||
kind: 0,
|
|
||||||
pubkey: onboarding.pubkey,
|
|
||||||
tags: [],
|
|
||||||
};
|
|
||||||
event.id = getEventHash(event);
|
|
||||||
event.sig = signEvent(event, onboarding.privkey);
|
|
||||||
// broadcast
|
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
|
||||||
};
|
|
||||||
|
|
||||||
const broadcastContacts = () => {
|
|
||||||
const nip02 = arrayToNIP02(follows);
|
|
||||||
// build event
|
|
||||||
const event: any = {
|
|
||||||
content: "",
|
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
|
||||||
kind: 3,
|
|
||||||
pubkey: onboarding.pubkey,
|
|
||||||
tags: nip02,
|
|
||||||
};
|
|
||||||
event.id = getEventHash(event);
|
|
||||||
event.sig = signEvent(event, onboarding.privkey);
|
|
||||||
// broadcast
|
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
|
||||||
};
|
|
||||||
|
|
||||||
// save follows to database then broadcast
|
// save follows to database then broadcast
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const followsIncludeSelf = follows.concat([onboarding.pubkey]);
|
// update account follows
|
||||||
// insert to database
|
updateAccount("follows", follows, account.pubkey);
|
||||||
createAccount(
|
|
||||||
onboarding.pubkey,
|
const tags = arrayToNIP02(follows);
|
||||||
onboarding.privkey,
|
|
||||||
onboarding.metadata,
|
const event: any = {
|
||||||
arrayToNIP02(followsIncludeSelf),
|
content: "",
|
||||||
1,
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
)
|
kind: 3,
|
||||||
.then((res) => {
|
pubkey: account.pubkey,
|
||||||
if (res) {
|
tags: tags,
|
||||||
for (const tag of follows) {
|
};
|
||||||
fetch(`https://us.rbr.bio/${tag}/metadata.json`)
|
|
||||||
.then((data) => data.json())
|
event.id = getEventHash(event);
|
||||||
.then((data) => createPleb(tag, data ?? ""));
|
event.sig = getSignature(event, account.privkey);
|
||||||
}
|
|
||||||
broadcastAccount();
|
// publish
|
||||||
broadcastContacts();
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
|
||||||
|
// redirect to step 3
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => navigate("/", { overwriteLastHistoryEntry: true }),
|
() =>
|
||||||
|
navigate("/app/prefetch", {
|
||||||
|
overwriteLastHistoryEntry: true,
|
||||||
|
}),
|
||||||
2000,
|
2000,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
console.error();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { onboardingAtom } from "@stores/onboarding";
|
import { createAccount } from "@utils/storage";
|
||||||
|
|
||||||
import { useSetAtom } from "jotai";
|
|
||||||
import { getPublicKey, nip19 } from "nostr-tools";
|
import { getPublicKey, nip19 } from "nostr-tools";
|
||||||
import { Resolver, useForm } from "react-hook-form";
|
import { Resolver, useForm } from "react-hook-form";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
@ -24,8 +22,6 @@ const resolver: Resolver<FormValues> = async (values) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const setOnboardingPrivkey = useSetAtom(onboardingAtom);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
setError,
|
setError,
|
||||||
@ -42,9 +38,15 @@ export function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof getPublicKey(privkey) === "string") {
|
if (typeof getPublicKey(privkey) === "string") {
|
||||||
setOnboardingPrivkey((prev) => ({ ...prev, privkey: privkey }));
|
const pubkey = getPublicKey(privkey);
|
||||||
|
const npub = nip19.npubEncode(pubkey);
|
||||||
|
|
||||||
|
const account = await createAccount(npub, pubkey, privkey, null, 1);
|
||||||
|
|
||||||
|
if (account) {
|
||||||
navigate("/app/auth/import/step-2");
|
navigate("/app/auth/import/step-2");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError("key", {
|
setError("key", {
|
||||||
type: "custom",
|
type: "custom",
|
||||||
|
@ -1,85 +1,55 @@
|
|||||||
import { Image } from "@shared/image";
|
import { User } from "@app/auth/components/user";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { READONLY_RELAYS } from "@stores/constants";
|
||||||
import { DEFAULT_AVATAR, READONLY_RELAYS } from "@stores/constants";
|
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||||
import { onboardingAtom } from "@stores/onboarding";
|
import { updateAccount } from "@utils/storage";
|
||||||
|
import { nip02ToArray } from "@utils/transform";
|
||||||
import { shortenKey } from "@utils/shortenKey";
|
import { useContext, useState } from "react";
|
||||||
import { createAccount, createPleb } from "@utils/storage";
|
|
||||||
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { getPublicKey } from "nostr-tools";
|
|
||||||
import { useContext, useMemo, useState } from "react";
|
|
||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const { account } = useActiveAccount();
|
||||||
const [onboarding, setOnboarding] = useAtom(onboardingAtom);
|
|
||||||
const pubkey = useMemo(
|
|
||||||
() => (onboarding.privkey ? getPublicKey(onboarding.privkey) : ""),
|
|
||||||
[onboarding.privkey],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, error } = useSWRSubscription(
|
const [loading, setLoading] = useState(false);
|
||||||
pubkey ? pubkey : null,
|
const [follows, setFollows] = useState([]);
|
||||||
(key, { next }) => {
|
|
||||||
|
useSWRSubscription(account ? account.pubkey : null, (key: string) => {
|
||||||
const unsubscribe = pool.subscribe(
|
const unsubscribe = pool.subscribe(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
kinds: [0, 3],
|
kinds: [3],
|
||||||
authors: [key],
|
authors: [key],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
READONLY_RELAYS,
|
READONLY_RELAYS,
|
||||||
(event: any) => {
|
(event: any) => {
|
||||||
switch (event.kind) {
|
setFollows(event.tags);
|
||||||
case 0:
|
|
||||||
// update state
|
|
||||||
next(null, JSON.parse(event.content));
|
|
||||||
// create account
|
|
||||||
setOnboarding((prev) => ({ ...prev, metadata: event.content }));
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
setOnboarding((prev) => ({ ...prev, follows: event.tags }));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
// show loading indicator
|
// show loading indicator
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const follows = onboarding.follows.concat([["p", pubkey]]);
|
// follows as list
|
||||||
// insert to database
|
const followsList = nip02ToArray(follows);
|
||||||
createAccount(pubkey, onboarding.privkey, onboarding.metadata, follows, 1)
|
|
||||||
.then((res) => {
|
// update account follows
|
||||||
if (res) {
|
updateAccount("follows", followsList, account.pubkey);
|
||||||
for (const tag of onboarding.follows) {
|
|
||||||
fetch(`https://rbr.bio/${tag[1]}/metadata.json`)
|
// redirect to home
|
||||||
.then((data) => data.json())
|
|
||||||
.then((data) => createPleb(tag[1], data ?? ""));
|
|
||||||
}
|
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => navigate("/", { overwriteLastHistoryEntry: true }),
|
() => navigate("/app/prefetch", { overwriteLastHistoryEntry: true }),
|
||||||
2000,
|
2000,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
console.error();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -91,8 +61,7 @@ export function Page() {
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full rounded-lg border border-zinc-800 bg-zinc-900 p-4">
|
<div className="w-full rounded-lg border border-zinc-800 bg-zinc-900 p-4">
|
||||||
{error && <div>Failed to load profile</div>}
|
{!account ? (
|
||||||
{!data ? (
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-11 w-11 animate-pulse rounded-lg bg-zinc-800" />
|
<div className="h-11 w-11 animate-pulse rounded-lg bg-zinc-800" />
|
||||||
@ -104,21 +73,7 @@ export function Page() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<div className="flex items-center gap-2">
|
<User pubkey={account.pubkey} />
|
||||||
<Image
|
|
||||||
className="relative inline-flex h-11 w-11 rounded-lg ring-2 ring-zinc-900"
|
|
||||||
src={data.picture || DEFAULT_AVATAR}
|
|
||||||
alt={pubkey}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-medium leading-none text-white">
|
|
||||||
{data.display_name || data.name}
|
|
||||||
</h3>
|
|
||||||
<p className="text-base text-zinc-400">
|
|
||||||
{data.nip05 || shortenKey(pubkey)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => submit()}
|
onClick={() => submit()}
|
||||||
|
@ -12,7 +12,7 @@ import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|||||||
import { createChannel } from "@utils/storage";
|
import { createChannel } from "@utils/storage";
|
||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { Fragment, useContext, useEffect, useState } from "react";
|
import { Fragment, useContext, useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useSWRConfig } from "swr";
|
import { useSWRConfig } from "swr";
|
||||||
@ -56,7 +56,7 @@ export default function ChannelCreateModal() {
|
|||||||
tags: [],
|
tags: [],
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
|
|
||||||
// publish channel
|
// publish channel
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
@ -13,7 +13,7 @@ import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|||||||
|
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { useResetAtom } from "jotai/utils";
|
import { useResetAtom } from "jotai/utils";
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
export default function ChannelMessageForm({
|
export default function ChannelMessageForm({
|
||||||
@ -50,7 +50,7 @@ export default function ChannelMessageForm({
|
|||||||
tags: tags,
|
tags: tags,
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
|
|
||||||
// publish note
|
// publish note
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
@ -12,7 +12,7 @@ import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { Fragment, useContext, useState } from "react";
|
import { Fragment, useContext, useState } from "react";
|
||||||
|
|
||||||
export default function MessageHideButton({ id }: { id: string }) {
|
export default function MessageHideButton({ id }: { id: string }) {
|
||||||
@ -40,7 +40,7 @@ export default function MessageHideButton({ id }: { id: string }) {
|
|||||||
tags: [["e", id]],
|
tags: [["e", id]],
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
|
|
||||||
// publish note
|
// publish note
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
@ -12,7 +12,7 @@ import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { Fragment, useContext, useState } from "react";
|
import { Fragment, useContext, useState } from "react";
|
||||||
|
|
||||||
export default function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
export default function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
||||||
@ -40,7 +40,7 @@ export default function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
|||||||
tags: [["p", pubkey]],
|
tags: [["p", pubkey]],
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
|
|
||||||
// publish note
|
// publish note
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
@ -12,7 +12,7 @@ import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|||||||
import { getChannel } from "@utils/storage";
|
import { getChannel } from "@utils/storage";
|
||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { Fragment, useContext, useEffect, useState } from "react";
|
import { Fragment, useContext, useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ export default function ChannelUpdateModal({ id }: { id: string }) {
|
|||||||
tags: [["e", id]],
|
tags: [["e", id]],
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
|
|
||||||
// publish channel
|
// publish channel
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
@ -11,7 +11,7 @@ import useSWRSubscription from "swr/subscription";
|
|||||||
const fetcher = async ([, id]) => {
|
const fetcher = async ([, id]) => {
|
||||||
const result = await getChannel(id);
|
const result = await getChannel(id);
|
||||||
if (result) {
|
if (result) {
|
||||||
return JSON.parse(result.metadata);
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -38,14 +38,14 @@ export default function ChatsListItem({ pubkey }: { pubkey: string }) {
|
|||||||
>
|
>
|
||||||
<div className="relative h-5 w-5 shrink-0 rounded">
|
<div className="relative h-5 w-5 shrink-0 rounded">
|
||||||
<Image
|
<Image
|
||||||
src={user.picture || DEFAULT_AVATAR}
|
src={user?.picture || DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="h-5 w-5 rounded bg-white object-cover"
|
className="h-5 w-5 rounded bg-white object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h5 className="max-w-[9rem] truncate font-medium text-zinc-200 group-hover:text-white">
|
<h5 className="max-w-[9rem] truncate font-medium text-zinc-200 group-hover:text-white">
|
||||||
{user.nip05 || user.name || shortenKey(pubkey)}
|
{user?.nip05 || user.name || shortenKey(pubkey)}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -2,16 +2,17 @@ import ChatsListItem from "@app/chat/components/item";
|
|||||||
import ChatsListSelfItem from "@app/chat/components/self";
|
import ChatsListSelfItem from "@app/chat/components/self";
|
||||||
|
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||||
import { getChats } from "@utils/storage";
|
import { getChatsByPubkey } from "@utils/storage";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
const fetcher = ([, account]) => getChats(account);
|
const fetcher = ([, pubkey]) => getChatsByPubkey(pubkey);
|
||||||
|
|
||||||
export default function ChatsList() {
|
export default function ChatsList() {
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
const { account, isLoading, isError } = useActiveAccount();
|
||||||
|
|
||||||
const { data: chats, error }: any = useSWR(
|
const { data: chats, error }: any = useSWR(
|
||||||
!isLoading && !isError && account ? ["chats", account] : null,
|
!isLoading && !isError && account ? ["chats", account.pubkey] : null,
|
||||||
fetcher,
|
fetcher,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -30,8 +31,8 @@ export default function ChatsList() {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
chats.map((item: { pubkey: string }) => (
|
chats.map((item) => (
|
||||||
<ChatsListItem key={item.pubkey} pubkey={item.pubkey} />
|
<ChatsListItem key={item.sender_pubkey} pubkey={item.sender_pubkey} />
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@ import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|||||||
|
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useResetAtom } from "jotai/utils";
|
import { useResetAtom } from "jotai/utils";
|
||||||
import { getEventHash, nip04, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature, nip04 } from "nostr-tools";
|
||||||
import { useCallback, useContext } from "react";
|
import { useCallback, useContext } from "react";
|
||||||
|
|
||||||
export default function ChatMessageForm({
|
export default function ChatMessageForm({
|
||||||
@ -40,7 +40,7 @@ export default function ChatMessageForm({
|
|||||||
tags: [["p", receiverPubkey]],
|
tags: [["p", receiverPubkey]],
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
// publish note
|
// publish note
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
// reset state
|
// reset state
|
||||||
|
@ -15,7 +15,6 @@ export default function ChatsListSelfItem() {
|
|||||||
const pagePubkey = searchParams.pubkey;
|
const pagePubkey = searchParams.pubkey;
|
||||||
|
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
const { account, isLoading, isError } = useActiveAccount();
|
||||||
const profile = account ? JSON.parse(account.metadata) : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -39,14 +38,14 @@ export default function ChatsListSelfItem() {
|
|||||||
>
|
>
|
||||||
<div className="relative h-5 w-5 shrink-0 rounded">
|
<div className="relative h-5 w-5 shrink-0 rounded">
|
||||||
<Image
|
<Image
|
||||||
src={profile?.picture || DEFAULT_AVATAR}
|
src={account?.picture || DEFAULT_AVATAR}
|
||||||
alt={account.pubkey}
|
alt={account.pubkey}
|
||||||
className="h-5 w-5 rounded bg-white object-cover"
|
className="h-5 w-5 rounded bg-white object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="inline-flex items-baseline">
|
<div className="inline-flex items-baseline">
|
||||||
<h5 className="max-w-[9rem] truncate font-medium text-zinc-200">
|
<h5 className="max-w-[9rem] truncate font-medium text-zinc-200">
|
||||||
{profile?.nip05 || profile?.name || shortenKey(account.pubkey)}
|
{account?.nip05 || account?.name || shortenKey(account.pubkey)}
|
||||||
</h5>
|
</h5>
|
||||||
<span className="text-zinc-600">(you)</span>
|
<span className="text-zinc-600">(you)</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,23 +1,15 @@
|
|||||||
import { getActiveAccount } from "@utils/storage";
|
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||||
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
const fetcher = () => getActiveAccount();
|
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const { data, isLoading } = useSWR("account", fetcher, {
|
const { account, isLoading } = useActiveAccount();
|
||||||
revalidateIfStale: false,
|
|
||||||
revalidateOnFocus: false,
|
|
||||||
revalidateOnReconnect: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isLoading && !data) {
|
if (!isLoading && !account) {
|
||||||
navigate("/app/auth", { overwriteLastHistoryEntry: true });
|
navigate("/app/auth", { overwriteLastHistoryEntry: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoading && data) {
|
if (!isLoading && account) {
|
||||||
navigate("/app/inital-data", { overwriteLastHistoryEntry: true });
|
navigate("/app/prefetch", { overwriteLastHistoryEntry: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import NoteReply from "@app/note/components/metadata/reply";
|
import NoteReply from "@app/note/components/metadata/reply";
|
||||||
import NoteRepost from "@app/note/components/metadata/repost";
|
import NoteRepost from "@app/note/components/metadata/repost";
|
||||||
import NoteZap from "@app/note/components/metadata/zap";
|
import NoteZap from "@app/note/components/metadata/zap";
|
||||||
import ZapIcon from "@shared/icons/zap";
|
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
import { READONLY_RELAYS } from "@stores/constants";
|
||||||
import { decode } from "light-bolt11-decoder";
|
import { decode } from "light-bolt11-decoder";
|
||||||
|
@ -7,7 +7,7 @@ import { WRITEONLY_RELAYS } from "@stores/constants";
|
|||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||||
|
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function NoteLike({
|
export default function NoteLike({
|
||||||
@ -35,7 +35,7 @@ export default function NoteLike({
|
|||||||
pubkey: account.pubkey,
|
pubkey: account.pubkey,
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
// publish event to all relays
|
// publish event to all relays
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
// update state
|
// update state
|
||||||
|
@ -10,7 +10,7 @@ import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { compactNumber } from "@utils/number";
|
import { compactNumber } from "@utils/number";
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { Fragment, useContext, useEffect, useState } from "react";
|
import { Fragment, useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function NoteReply({
|
export default function NoteReply({
|
||||||
@ -24,7 +24,6 @@ export default function NoteReply({
|
|||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
const { account, isLoading, isError } = useActiveAccount();
|
||||||
const profile = account ? JSON.parse(account.metadata) : null;
|
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
@ -44,7 +43,7 @@ export default function NoteReply({
|
|||||||
tags: [["e", id]],
|
tags: [["e", id]],
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
|
|
||||||
// publish event
|
// publish event
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
@ -106,7 +105,7 @@ export default function NoteReply({
|
|||||||
<div>
|
<div>
|
||||||
<div className="relative h-11 w-11 shrink-0 overflow-hidden rounded-md border border-white/10">
|
<div className="relative h-11 w-11 shrink-0 overflow-hidden rounded-md border border-white/10">
|
||||||
<Image
|
<Image
|
||||||
src={profile?.picture}
|
src={account?.picture}
|
||||||
alt="user's avatar"
|
alt="user's avatar"
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
className="h-11 w-11 rounded-md object-cover"
|
||||||
/>
|
/>
|
||||||
|
@ -8,7 +8,7 @@ import { dateToUnix } from "@utils/date";
|
|||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||||
|
|
||||||
import { compactNumber } from "@utils/number";
|
import { compactNumber } from "@utils/number";
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function NoteRepost({
|
export default function NoteRepost({
|
||||||
@ -36,7 +36,7 @@ export default function NoteRepost({
|
|||||||
pubkey: account.pubkey,
|
pubkey: account.pubkey,
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
// publish event to all relays
|
// publish event to all relays
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
// update state
|
// update state
|
||||||
|
@ -6,15 +6,14 @@ import { WRITEONLY_RELAYS } from "@stores/constants";
|
|||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||||
|
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
|
|
||||||
export default function NoteReplyForm({ id }: { id: string }) {
|
export default function NoteReplyForm({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
|
||||||
|
|
||||||
|
const { account, isLoading, isError } = useActiveAccount();
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
const profile = account ? JSON.parse(account.metadata) : null;
|
|
||||||
|
|
||||||
const submitEvent = () => {
|
const submitEvent = () => {
|
||||||
if (!isLoading && !isError && account) {
|
if (!isLoading && !isError && account) {
|
||||||
@ -26,7 +25,7 @@ export default function NoteReplyForm({ id }: { id: string }) {
|
|||||||
tags: [["e", id]],
|
tags: [["e", id]],
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
|
|
||||||
// publish note
|
// publish note
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
@ -42,7 +41,7 @@ export default function NoteReplyForm({ id }: { id: string }) {
|
|||||||
<div>
|
<div>
|
||||||
<div className="relative h-9 w-9 shrink-0 overflow-hidden rounded-md">
|
<div className="relative h-9 w-9 shrink-0 overflow-hidden rounded-md">
|
||||||
<Image
|
<Image
|
||||||
src={profile?.picture}
|
src={account?.picture}
|
||||||
alt={account?.pubkey}
|
alt={account?.pubkey}
|
||||||
className="h-9 w-9 rounded-md object-cover"
|
className="h-9 w-9 rounded-md object-cover"
|
||||||
/>
|
/>
|
||||||
|
@ -1,23 +1,18 @@
|
|||||||
import { RelayContext } from "@shared/relayProvider";
|
|
||||||
|
|
||||||
import LumeIcon from "@icons/lume";
|
import LumeIcon from "@icons/lume";
|
||||||
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
import { READONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix, getHourAgo } from "@utils/date";
|
import { dateToUnix, getHourAgo } from "@utils/date";
|
||||||
|
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||||
import {
|
import {
|
||||||
addToBlacklist,
|
addToBlacklist,
|
||||||
countTotalLongNotes,
|
|
||||||
countTotalNotes,
|
countTotalNotes,
|
||||||
createChat,
|
createChat,
|
||||||
createNote,
|
createNote,
|
||||||
getActiveAccount,
|
|
||||||
getLastLogin,
|
getLastLogin,
|
||||||
updateLastLogin,
|
|
||||||
} from "@utils/storage";
|
} from "@utils/storage";
|
||||||
import { getParentID, nip02ToArray } from "@utils/transform";
|
import { getParentID } from "@utils/transform";
|
||||||
|
import { useCallback, useContext, useRef } from "react";
|
||||||
import { useContext, useEffect, useRef } from "react";
|
import useSWRSubscription from "swr/subscription";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
function isJSON(str: string) {
|
function isJSON(str: string) {
|
||||||
@ -29,43 +24,36 @@ function isJSON(str: string) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lastLogin: string;
|
||||||
|
let totalNotes: number;
|
||||||
|
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
lastLogin = await getLastLogin();
|
||||||
|
totalNotes = await countTotalNotes();
|
||||||
|
}
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
|
||||||
const now = useRef(new Date());
|
const now = useRef(new Date());
|
||||||
|
const eose = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
const { account, isLoading, isError } = useActiveAccount();
|
||||||
let unsubscribe: () => void;
|
|
||||||
let timeout: any;
|
|
||||||
|
|
||||||
const fetchInitalData = async () => {
|
const getQuery = useCallback(() => {
|
||||||
const account = await getActiveAccount();
|
|
||||||
const lastLogin = await getLastLogin();
|
|
||||||
const notes = await countTotalNotes();
|
|
||||||
const longNotes = await countTotalLongNotes();
|
|
||||||
|
|
||||||
const follows = nip02ToArray(JSON.parse(account.follows));
|
|
||||||
const query = [];
|
const query = [];
|
||||||
|
const follows = JSON.parse(account.follows);
|
||||||
|
|
||||||
let sinceNotes: number;
|
let queryNoteSince: number;
|
||||||
let sinceLongNotes: number;
|
let querySince: number;
|
||||||
|
|
||||||
if (notes === 0) {
|
if (totalNotes === 0) {
|
||||||
sinceNotes = dateToUnix(getHourAgo(48, now.current));
|
queryNoteSince = dateToUnix(getHourAgo(48, now.current));
|
||||||
} else {
|
} else {
|
||||||
if (parseInt(lastLogin) > 0) {
|
if (parseInt(lastLogin) > 0) {
|
||||||
sinceNotes = parseInt(lastLogin);
|
queryNoteSince = parseInt(lastLogin);
|
||||||
} else {
|
} else {
|
||||||
sinceNotes = dateToUnix(getHourAgo(48, now.current));
|
queryNoteSince = dateToUnix(getHourAgo(48, now.current));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (longNotes === 0) {
|
|
||||||
sinceLongNotes = 0;
|
|
||||||
} else {
|
|
||||||
if (parseInt(lastLogin) > 0) {
|
|
||||||
sinceLongNotes = parseInt(lastLogin);
|
|
||||||
} else {
|
|
||||||
sinceLongNotes = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,35 +61,31 @@ export function Page() {
|
|||||||
query.push({
|
query.push({
|
||||||
kinds: [1, 6, 1063],
|
kinds: [1, 6, 1063],
|
||||||
authors: follows,
|
authors: follows,
|
||||||
since: sinceNotes,
|
since: queryNoteSince,
|
||||||
until: dateToUnix(now.current),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// kind 4 (chats) query
|
// kind 4 (chats) query
|
||||||
query.push({
|
query.push({
|
||||||
kinds: [4],
|
kinds: [4],
|
||||||
"#p": [account.pubkey],
|
"#p": [account.pubkey],
|
||||||
since: 0,
|
since: querySince,
|
||||||
until: dateToUnix(now.current),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// kind 43, 43 (mute user, hide message) query
|
// kind 43, 43 (mute user, hide message) query
|
||||||
query.push({
|
query.push({
|
||||||
authors: [account.pubkey],
|
authors: [account.pubkey],
|
||||||
kinds: [43, 44],
|
kinds: [43, 44],
|
||||||
since: 0,
|
since: querySince,
|
||||||
until: dateToUnix(now.current),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// kind 30023 (long post) query
|
return query;
|
||||||
query.push({
|
}, [account.follows]);
|
||||||
kinds: [30023],
|
|
||||||
since: sinceLongNotes,
|
|
||||||
until: dateToUnix(now.current),
|
|
||||||
});
|
|
||||||
|
|
||||||
// subscribe relays
|
useSWRSubscription(
|
||||||
unsubscribe = pool.subscribe(
|
!isLoading && !isError && account ? "prefetch" : null,
|
||||||
|
() => {
|
||||||
|
const query = getQuery();
|
||||||
|
const unsubscribe = pool.subscribe(
|
||||||
query,
|
query,
|
||||||
READONLY_RELAYS,
|
READONLY_RELAYS,
|
||||||
(event: any) => {
|
(event: any) => {
|
||||||
@ -124,9 +108,13 @@ export function Page() {
|
|||||||
}
|
}
|
||||||
// chat
|
// chat
|
||||||
case 4:
|
case 4:
|
||||||
if (event.pubkey !== account.pubkey) {
|
createChat(
|
||||||
createChat(account.id, event.pubkey, event.created_at);
|
event.id,
|
||||||
}
|
account.pubkey,
|
||||||
|
event.pubkey,
|
||||||
|
event.content,
|
||||||
|
event.created_at,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
// repost
|
// repost
|
||||||
case 6:
|
case 6:
|
||||||
@ -165,47 +153,24 @@ export function Page() {
|
|||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
// long post
|
|
||||||
case 30023: {
|
|
||||||
// insert event to local database
|
|
||||||
const verifyMetadata = isJSON(event.tags);
|
|
||||||
if (verifyMetadata) {
|
|
||||||
createNote(
|
|
||||||
event.id,
|
|
||||||
account.id,
|
|
||||||
event.pubkey,
|
|
||||||
event.kind,
|
|
||||||
event.tags,
|
|
||||||
event.content,
|
|
||||||
event.created_at,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
() => {
|
() => {
|
||||||
updateLastLogin(dateToUnix(now.current));
|
eose.current += 1;
|
||||||
timeout = setTimeout(() => {
|
if (eose.current === READONLY_RELAYS.length) {
|
||||||
navigate("/app/space", { overwriteLastHistoryEntry: true });
|
navigate("/app/space", { overwriteLastHistoryEntry: true });
|
||||||
}, 5000);
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
fetchInitalData().catch(console.error);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (unsubscribe) {
|
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
}
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
};
|
||||||
}, [pool]);
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
@ -1,10 +1,9 @@
|
|||||||
import { StrictMode } from "react";
|
|
||||||
import { Root, createRoot, hydrateRoot } from "react-dom/client";
|
|
||||||
import "vidstack/styles/defaults.css";
|
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { Shell } from "./shell";
|
import { Shell } from "./shell";
|
||||||
import { PageContextClient } from "./types";
|
import { PageContextClient } from "./types";
|
||||||
|
import { StrictMode } from "react";
|
||||||
|
import { Root, createRoot, hydrateRoot } from "react-dom/client";
|
||||||
|
import "vidstack/styles/defaults.css";
|
||||||
|
|
||||||
export const clientRouting = true;
|
export const clientRouting = true;
|
||||||
export const hydrationCanBeAborted = true;
|
export const hydrationCanBeAborted = true;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
|
import { Shell } from "./shell";
|
||||||
|
import { PageContextServer } from "./types";
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import ReactDOMServer from "react-dom/server";
|
import ReactDOMServer from "react-dom/server";
|
||||||
import { dangerouslySkipEscape, escapeInject } from "vite-plugin-ssr/server";
|
import { dangerouslySkipEscape, escapeInject } from "vite-plugin-ssr/server";
|
||||||
|
|
||||||
import { Shell } from "./shell";
|
|
||||||
import { PageContextServer } from "./types";
|
|
||||||
|
|
||||||
export const passToClient = ["pageProps"];
|
export const passToClient = ["pageProps"];
|
||||||
|
|
||||||
export function render(pageContext: PageContextServer) {
|
export function render(pageContext: PageContextServer) {
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import { RelayProvider } from "@shared/relayProvider";
|
|
||||||
|
|
||||||
import { PageContextProvider } from "@utils/hooks/usePageContext";
|
|
||||||
|
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
import { LayoutDefault } from "./layoutDefault";
|
import { LayoutDefault } from "./layoutDefault";
|
||||||
import { PageContext } from "./types";
|
import { PageContext } from "./types";
|
||||||
|
import { RelayProvider } from "@shared/relayProvider";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { PageContextProvider } from "@utils/hooks/usePageContext";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
|
|
||||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||||
|
import { useProfile } from "@utils/hooks/useProfile";
|
||||||
|
|
||||||
export default function ActiveAccount({ user }: { user: any }) {
|
export default function ActiveAccount({ data }: { data: any }) {
|
||||||
const userData = JSON.parse(user.metadata);
|
const { user } = useProfile(data.npub);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type="button" className="relative h-11 w-11 overflow-hidden">
|
<button type="button" className="relative h-11 w-11 overflow-hidden">
|
||||||
<Image
|
<Image
|
||||||
src={userData.picture || DEFAULT_AVATAR}
|
src={user?.picture || DEFAULT_AVATAR}
|
||||||
alt="user's avatar"
|
alt={data.npub}
|
||||||
className="h-11 w-11 rounded-md object-cover"
|
className="h-11 w-11 rounded-md object-cover"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
|
|
||||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||||
|
import { useProfile } from "@utils/hooks/useProfile";
|
||||||
|
|
||||||
export default function InactiveAccount({ user }: { user: any }) {
|
export default function InactiveAccount({ data }: { data: any }) {
|
||||||
const userData = JSON.parse(user.metadata);
|
const { user } = useProfile(data.npub);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-11 w-11 shrink rounded-md">
|
<div className="relative h-11 w-11 shrink rounded-md">
|
||||||
<Image
|
<Image
|
||||||
src={userData.picture || DEFAULT_AVATAR}
|
src={user?.picture || DEFAULT_AVATAR}
|
||||||
alt="user's avatar"
|
alt={data.npub}
|
||||||
className="h-11 w-11 rounded-lg object-cover"
|
className="h-11 w-11 rounded-lg object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,7 @@ import { WRITEONLY_RELAYS } from "@stores/constants";
|
|||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
|
|
||||||
import { getEventHash, signEvent } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { useCallback, useContext, useMemo, useState } from "react";
|
import { useCallback, useContext, useMemo, useState } from "react";
|
||||||
import { Node, Transforms, createEditor } from "slate";
|
import { Node, Transforms, createEditor } from "slate";
|
||||||
import { withHistory } from "slate-history";
|
import { withHistory } from "slate-history";
|
||||||
@ -91,7 +91,7 @@ export function Post({ pubkey, privkey }: { pubkey: string; privkey: string }) {
|
|||||||
tags: [],
|
tags: [],
|
||||||
};
|
};
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = signEvent(event, privkey);
|
event.sig = getSignature(event, privkey);
|
||||||
|
|
||||||
// publish note
|
// publish note
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
@ -44,7 +44,7 @@ export default function EventCollector() {
|
|||||||
since: dateToUnix(now.current),
|
since: dateToUnix(now.current),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kinds: [0, 3],
|
kinds: [3],
|
||||||
authors: [key.pubkey],
|
authors: [key.pubkey],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -60,10 +60,6 @@ export default function EventCollector() {
|
|||||||
READONLY_RELAYS,
|
READONLY_RELAYS,
|
||||||
(event: any) => {
|
(event: any) => {
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
// metadata
|
|
||||||
case 0:
|
|
||||||
updateAccount("metadata", event.content, event.pubkey);
|
|
||||||
break;
|
|
||||||
// short text note
|
// short text note
|
||||||
case 1: {
|
case 1: {
|
||||||
const parentID = getParentID(event.tags, event.id);
|
const parentID = getParentID(event.tags, event.id);
|
||||||
@ -82,15 +78,21 @@ export default function EventCollector() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// contacts
|
// contacts
|
||||||
case 3:
|
case 3: {
|
||||||
|
const follows = nip02ToArray(event.tags);
|
||||||
// update account's folllows with NIP-02 tag list
|
// update account's folllows with NIP-02 tag list
|
||||||
updateAccount("follows", event.tags, event.pubkey);
|
updateAccount("follows", follows, event.pubkey);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
// chat
|
// chat
|
||||||
case 4:
|
case 4:
|
||||||
if (event.pubkey !== key.pubkey) {
|
createChat(
|
||||||
createChat(key.id, event.pubkey, event.created_at);
|
event.id,
|
||||||
}
|
key.pubkey,
|
||||||
|
event.pubkey,
|
||||||
|
event.content,
|
||||||
|
event.created_at,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
// repost
|
// repost
|
||||||
case 6:
|
case 6:
|
||||||
|
@ -25,7 +25,7 @@ export default function MultiAccounts() {
|
|||||||
{!activeAccount ? (
|
{!activeAccount ? (
|
||||||
<div className="group relative flex h-10 w-10 shrink animate-pulse items-center justify-center rounded-lg bg-zinc-900" />
|
<div className="group relative flex h-10 w-10 shrink animate-pulse items-center justify-center rounded-lg bg-zinc-900" />
|
||||||
) : (
|
) : (
|
||||||
<ActiveAccount user={activeAccount} />
|
<ActiveAccount data={activeAccount} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
<div>
|
<div>
|
||||||
@ -49,7 +49,7 @@ export default function MultiAccounts() {
|
|||||||
) : (
|
) : (
|
||||||
accounts.map(
|
accounts.map(
|
||||||
(account: { is_active: number; pubkey: string }) => (
|
(account: { is_active: number; pubkey: string }) => (
|
||||||
<InactiveAccount key={account.pubkey} user={account} />
|
<InactiveAccount key={account.pubkey} data={account} />
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { FULL_RELAYS } from "@stores/constants";
|
import { FULL_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { RelayPool } from "nostr-relaypool";
|
import { RelayPool } from "nostr-relaypool";
|
||||||
import { createContext } from "react";
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
@ -6,12 +6,12 @@ export const DEFAULT_CHANNEL_BANNER =
|
|||||||
"https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg";
|
"https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg";
|
||||||
|
|
||||||
// metadata service
|
// metadata service
|
||||||
export const METADATA_SERVICE = "https://rbr.bio";
|
export const METADATA_SERVICE = "https://us.rbr.bio";
|
||||||
|
|
||||||
// read-only relay list
|
// read-only relay list
|
||||||
export const READONLY_RELAYS = [
|
export const READONLY_RELAYS = [
|
||||||
"wss://welcome.nostr.wine",
|
"wss://welcome.nostr.wine",
|
||||||
"wss://relay.nostr.band/all",
|
"wss://relay.nostr.band",
|
||||||
];
|
];
|
||||||
|
|
||||||
// write-only relay list
|
// write-only relay list
|
||||||
@ -23,6 +23,6 @@ export const WRITEONLY_RELAYS = [
|
|||||||
// full-relay list
|
// full-relay list
|
||||||
export const FULL_RELAYS = [
|
export const FULL_RELAYS = [
|
||||||
"wss://welcome.nostr.wine",
|
"wss://welcome.nostr.wine",
|
||||||
"wss://relay.nostr.band/all",
|
"wss://relay.nostr.band",
|
||||||
"wss://nostr.mutinywallet.com",
|
"wss://nostr.mutinywallet.com",
|
||||||
];
|
];
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { getActiveAccount } from "@utils/storage";
|
import { getActiveAccount } from "@utils/storage";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
const fetcher = () => getActiveAccount();
|
const fetcher = () => getActiveAccount();
|
||||||
|
@ -1,33 +1,47 @@
|
|||||||
import { METADATA_SERVICE } from "@stores/constants";
|
import { METADATA_SERVICE } from "@stores/constants";
|
||||||
|
|
||||||
import { createPleb, getPleb } from "@utils/storage";
|
import { createPleb, getPleb } from "@utils/storage";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
const fetcher = async (pubkey: string) => {
|
const fetcher = async (key: string) => {
|
||||||
const result = await getPleb(pubkey);
|
let npub: string;
|
||||||
if (result) {
|
|
||||||
const metadata = JSON.parse(result["metadata"]);
|
|
||||||
result["content"] = metadata.content;
|
|
||||||
result["metadata"] = undefined;
|
|
||||||
|
|
||||||
|
if (key.substring(0, 4) === "npub") {
|
||||||
|
npub = key;
|
||||||
|
} else {
|
||||||
|
npub = nip19.npubEncode(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = Math.floor(Date.now() / 1000);
|
||||||
|
const result = await getPleb(npub);
|
||||||
|
|
||||||
|
if (result && result.created_at + 86400 < current) {
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
const result = await fetch(`${METADATA_SERVICE}/${pubkey}/metadata.json`);
|
const res = await fetch(`${METADATA_SERVICE}/${key}/metadata.json`);
|
||||||
const resultJSON = await result.json();
|
|
||||||
const cache = await createPleb(pubkey, resultJSON);
|
|
||||||
|
|
||||||
if (cache) {
|
if (!res.ok) {
|
||||||
return resultJSON;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
const saveToDB = await createPleb(key, json);
|
||||||
|
|
||||||
|
if (saveToDB) {
|
||||||
|
return JSON.parse(json.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useProfile(pubkey: string) {
|
export function useProfile(key: string) {
|
||||||
const { data, error, isLoading } = useSWR(pubkey, fetcher);
|
const { data, error, isLoading } = useSWR(key, fetcher, {
|
||||||
|
revalidateIfStale: false,
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: true,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: data ? JSON.parse(data.content ? data.content : null) : null,
|
user: data,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError: error,
|
isError: error,
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { nip19 } from "nostr-tools";
|
||||||
import Database from "tauri-plugin-sql-api";
|
import Database from "tauri-plugin-sql-api";
|
||||||
|
|
||||||
let db: null | Database = null;
|
let db: null | Database = null;
|
||||||
@ -15,10 +16,7 @@ export async function connect(): Promise<Database> {
|
|||||||
// get active account
|
// get active account
|
||||||
export async function getActiveAccount() {
|
export async function getActiveAccount() {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
// #TODO: check is_active == true
|
const result = await db.select("SELECT * FROM accounts WHERE is_active = 1;");
|
||||||
const result = await db.select(
|
|
||||||
"SELECT * FROM accounts WHERE is_active = 1 LIMIT 1;",
|
|
||||||
);
|
|
||||||
return result[0];
|
return result[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,16 +30,16 @@ export async function getAccounts() {
|
|||||||
|
|
||||||
// create account
|
// create account
|
||||||
export async function createAccount(
|
export async function createAccount(
|
||||||
|
npub: string,
|
||||||
pubkey: string,
|
pubkey: string,
|
||||||
privkey: string,
|
privkey: string,
|
||||||
metadata: string,
|
|
||||||
follows?: string[][],
|
follows?: string[][],
|
||||||
is_active?: number,
|
is_active?: number,
|
||||||
) {
|
) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
return await db.execute(
|
return await db.execute(
|
||||||
"INSERT OR IGNORE INTO accounts (pubkey, privkey, metadata, follows, is_active) VALUES (?, ?, ?, ?, ?);",
|
"INSERT OR IGNORE INTO accounts (npub, pubkey, privkey, follows, is_active) VALUES (?, ?, ?, ?, ?);",
|
||||||
[pubkey, privkey, metadata, follows || "", is_active || 0],
|
[npub, pubkey, privkey, follows || "", is_active || 0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,20 +63,47 @@ export async function getPlebs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get pleb by pubkey
|
// get pleb by pubkey
|
||||||
export async function getPleb(pubkey: string) {
|
export async function getPleb(npub: string) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
const result = await db.select(
|
const result = await db.select(`SELECT * FROM plebs WHERE npub = "${npub}";`);
|
||||||
`SELECT * FROM plebs WHERE pubkey = "${pubkey}"`,
|
|
||||||
);
|
if (result) {
|
||||||
return result[0];
|
return result[0];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create pleb
|
// create pleb
|
||||||
export async function createPleb(pubkey: string, metadata: string) {
|
export async function createPleb(key: string, json: any) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
|
const data = JSON.parse(json.content);
|
||||||
|
|
||||||
|
let npub: string;
|
||||||
|
|
||||||
|
if (key.substring(0, 4) === "npub") {
|
||||||
|
npub = key;
|
||||||
|
} else {
|
||||||
|
npub = nip19.npubEncode(key);
|
||||||
|
}
|
||||||
|
|
||||||
return await db.execute(
|
return await db.execute(
|
||||||
"INSERT OR IGNORE INTO plebs (pubkey, metadata) VALUES (?, ?);",
|
"INSERT OR REPLACE INTO plebs (npub, display_name, name, username, about, bio, website, picture, banner, nip05, lud06, lud16, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||||
[pubkey, metadata],
|
[
|
||||||
|
npub,
|
||||||
|
data.display_name || data.displayName,
|
||||||
|
data.name,
|
||||||
|
data.username,
|
||||||
|
data.about,
|
||||||
|
data.bio,
|
||||||
|
data.website,
|
||||||
|
data.picture || data.image,
|
||||||
|
data.banner,
|
||||||
|
data.nip05,
|
||||||
|
data.lud06,
|
||||||
|
data.lud16,
|
||||||
|
data.created_at,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,30 +264,34 @@ export async function createChannel(
|
|||||||
// update channel metadata
|
// update channel metadata
|
||||||
export async function updateChannelMetadata(event_id: string, value: string) {
|
export async function updateChannelMetadata(event_id: string, value: string) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
|
const data = JSON.parse(value);
|
||||||
|
|
||||||
return await db.execute(
|
return await db.execute(
|
||||||
"UPDATE channels SET metadata = ? WHERE event_id = ?;",
|
"UPDATE channels SET name = ?, picture = ?, about = ? WHERE event_id = ?;",
|
||||||
[value, event_id],
|
[data.name, data.picture, data.about, event_id],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all chats
|
// get all chats
|
||||||
export async function getChats(account_id: number) {
|
export async function getChatsByPubkey(pubkey: string) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
return await db.select(
|
return await db.select(
|
||||||
`SELECT * FROM chats WHERE account_id <= "${account_id}" ORDER BY created_at DESC;`,
|
`SELECT DISTINCT sender_pubkey FROM chats WHERE receiver_pubkey = "${pubkey}" ORDER BY created_at DESC;`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create chat
|
// create chat
|
||||||
export async function createChat(
|
export async function createChat(
|
||||||
account_id: number,
|
event_id: string,
|
||||||
pubkey: string,
|
receiver_pubkey: string,
|
||||||
|
sender_pubkey: string,
|
||||||
|
content: string,
|
||||||
created_at: number,
|
created_at: number,
|
||||||
) {
|
) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
return await db.execute(
|
return await db.execute(
|
||||||
"INSERT OR IGNORE INTO chats (account_id, pubkey, created_at) VALUES (?, ?, ?);",
|
"INSERT OR IGNORE INTO chats (event_id, receiver_pubkey, sender_pubkey, content, created_at) VALUES (?, ?, ?, ?, ?);",
|
||||||
[account_id, pubkey, created_at],
|
[event_id, receiver_pubkey, sender_pubkey, content, created_at],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user