-
n o s t r
+
navigate("/")}>n o s t r
-
navigate("/login")}>Login
+ {key ?
:
+
navigate("/login")}>Login
+ }
diff --git a/src/pages/Login.js b/src/pages/Login.js
index 7d741b4a..97509b8c 100644
--- a/src/pages/Login.js
+++ b/src/pages/Login.js
@@ -1,5 +1,49 @@
+import { useEffect, useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { setPrivateKey } from "../state/Login";
+import * as secp from '@noble/secp256k1';
+import { bech32 } from "bech32";
+import { useNavigate } from "react-router-dom";
+
export default function LoginPage() {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const privateKey = useSelector(s => s.login.privateKey);
+ const [key, setKey] = useState("");
+
+ function doLogin() {
+ if(key.startsWith("nsec")) {
+ let nKey = bech32.decode(key);
+ let buff = bech32.fromWords(nKey.words);
+ let hexKey = secp.utils.bytesToHex(Uint8Array.from(buff));
+ if(secp.utils.isValidPrivateKey(hexKey)) {
+ dispatch(setPrivateKey(hexKey));
+ } else {
+ throw "INVALID PRIVATE KEY";
+ }
+ } else {
+ if(secp.utils.isValidPrivateKey(key)) {
+ dispatch(setPrivateKey(key));
+ } else {
+ throw "INVALID PRIVATE KEY";
+ }
+ }
+ }
+
+ useEffect(() => {
+ if(privateKey) {
+ navigate("/");
+ }
+ }, [privateKey]);
return (
-
I do login
+ <>
+
Login
+
Enter your private key:
+
+
setKey(e.target.value)}/>
+
doLogin()}>Login
+
+
+ >
);
}
\ No newline at end of file
diff --git a/src/pages/ProfilePage.css b/src/pages/ProfilePage.css
new file mode 100644
index 00000000..35985ddc
--- /dev/null
+++ b/src/pages/ProfilePage.css
@@ -0,0 +1,17 @@
+.profile {
+ display: flex;
+}
+
+.profile > div:last-child {
+ flex-grow: 1;
+ margin-left: 10px;
+}
+
+.profile img.avatar {
+ width: 256px;
+ height: 256px;
+}
+
+.profile img.avatar:hover {
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js
index 601bb873..0f7615fb 100644
--- a/src/pages/ProfilePage.js
+++ b/src/pages/ProfilePage.js
@@ -1,17 +1,55 @@
+import "./ProfilePage.css";
import { useSelector } from "react-redux";
import { useParams } from "react-router-dom";
+import useProfile from "./feed/ProfileFeed";
import useProfileFeed from "./feed/ProfileFeed";
+import { useState } from "react";
export default function ProfilePage() {
const params = useParams();
const id = params.id;
- useProfileFeed(id);
-
- const user = useSelector(s => s.users.users[id]);
+ const user = useProfile(id);
+ const loginPubKey = useSelector(s => s.login.publicKey);
+ const isMe = loginPubKey === id;
+
+ let [name, setName] = useState(user?.name);
+ let [about, setAbout] = useState(user?.amount);
+ let [website, setWebsite] = useState(user?.website);
+
+ function editor() {
+ return (
+ <>
+
+
Name:
+
+ setName(e.target.value)} />
+
+
+
+
About:
+
+ setAbout(e.target.value)} />
+
+
+
+
Website:
+
+ setWebsite(e.target.value)} />
+
+
+ >
+ )
+ }
return (
-
+
+
+
+
+ {isMe ? editor() : null}
+
+
)
}
\ No newline at end of file
diff --git a/src/pages/feed/ProfileFeed.js b/src/pages/feed/ProfileFeed.js
index acc6d925..d64fb29d 100644
--- a/src/pages/feed/ProfileFeed.js
+++ b/src/pages/feed/ProfileFeed.js
@@ -1,13 +1,19 @@
import { useContext, useEffect } from "react";
-import { useDispatch } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
import { NostrContext } from "../..";
import { addPubKey } from "../../state/Users";
-export default function useProfileFeed(id) {
+export default function useProfile(pubKey) {
const dispatch = useDispatch();
const system = useContext(NostrContext);
-
+ const user = useSelector(s => s.users.users[pubKey]);
+ const pubKeys = useSelector(s => s.users.pubKeys);
+
useEffect(() => {
- dispatch(addPubKey(id));
- }, []);
+ if (!pubKeys.includes(pubKey)) {
+ dispatch(addPubKey(pubKey));
+ }
+ }, []);
+
+ return user;
}
\ No newline at end of file
diff --git a/src/pages/feed/UsersFeed.js b/src/pages/feed/UsersFeed.js
index 95abb7b9..7995b178 100644
--- a/src/pages/feed/UsersFeed.js
+++ b/src/pages/feed/UsersFeed.js
@@ -14,44 +14,46 @@ export default function useUsersStore() {
const [loading, setLoading] = useState(false);
function isUserCached(id) {
- let expire = new Date().getTime() - (1_000 * 60 * 5) ; // 60s expire
+ let expire = new Date().getTime() - (1_000 * 60 * 5); // 60s expire
let u = users[id];
return u && u.loaded > expire;
}
- async function getUsers() {
+ function mapEventToProfile(ev) {
+ let metaEvent = Event.FromObject(ev);
+ let data = JSON.parse(metaEvent.Content);
+ return {
+ pubkey: metaEvent.PubKey,
+ fromEvent: ev,
+ loaded: new Date().getTime(),
+ ...data
+ };
+ }
+ async function getUsers() {
let needProfiles = pKeys.filter(a => !isUserCached(a));
- if(needProfiles.length === 0) {
+ if (needProfiles.length === 0) {
return;
}
console.debug("Need profiles: ", needProfiles);
let sub = new Subscriptions();
sub.Authors = new Set(needProfiles);
sub.Kinds.add(EventKind.SetMetadata);
+ sub.OnEvent = (ev) => {
+ dispatch(setUserData(mapEventToProfile(ev)));
+ };
let events = await system.RequestSubscription(sub);
- console.debug("Got events: ", events);
- let loaded = new Date().getTime();
- let profiles = events.filter(a => a.kind === EventKind.SetMetadata).map(a => {
- let metaEvent = Event.FromObject(a);
- let data = JSON.parse(metaEvent.Content);
- return {
- pubkey: metaEvent.PubKey,
- fromEvent: a,
- loaded,
- ...data
- };
- });
+ let profiles = events
+ .filter(a => a.kind === EventKind.SetMetadata)
+ .map(mapEventToProfile);
let missing = needProfiles.filter(a => !events.some(b => b.pubkey === a));
let missingProfiles = missing.map(a => {
return {
pubkey: a,
- loaded
+ loaded: new Date().getTime()
}
});
- console.debug("Got profiles: ", profiles);
- console.debug("Missing profiles: ", missing);
dispatch(setUserData([
...profiles,
...missingProfiles
diff --git a/src/state/Login.js b/src/state/Login.js
index 495da3af..4aebf750 100644
--- a/src/state/Login.js
+++ b/src/state/Login.js
@@ -1,10 +1,12 @@
import { createSlice } from '@reduxjs/toolkit'
+import * as secp from '@noble/secp256k1';
const PrivateKeyItem = "secret";
const RelayList = "relays";
const DefaultRelays = JSON.stringify([
"wss://nostr-pub.wellorder.net",
"wss://relay.damus.io",
+ "wss://beta.nostr.v0l.io"
]);
const LoginSlice = createSlice({
@@ -13,22 +15,37 @@ const LoginSlice = createSlice({
/**
* Current user private key
*/
- privateKey: window.localStorage.getItem(PrivateKeyItem),
+ privateKey: null,
+
+ /**
+ * Current users public key
+ */
+ publicKey: null,
/**
* Configured relays for this user
*/
- relays: JSON.parse(window.localStorage.getItem(RelayList) || DefaultRelays)
+ relays: []
},
reducers: {
+ init: (state) => {
+ state.privateKey = window.localStorage.getItem(PrivateKeyItem);
+ if(state.privateKey) {
+ state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(state.privateKey, true));
+ }
+ state.relays = JSON.parse(window.localStorage.getItem(RelayList) || DefaultRelays);
+ },
setPrivateKey: (state, action) => {
state.privateKey = action.payload;
+ window.localStorage.setItem(PrivateKeyItem, action.payload);
+ state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload, true));
},
logout: (state) => {
state.privateKey = null;
+ window.localStorage.removeItem(PrivateKeyItem);
}
}
});
-export const { setPrivateKey, logout } = LoginSlice.actions;
+export const { init, setPrivateKey, logout } = LoginSlice.actions;
export const reducer = LoginSlice.reducer;
\ No newline at end of file
diff --git a/src/state/Users.js b/src/state/Users.js
index e96551cd..ac402280 100644
--- a/src/state/Users.js
+++ b/src/state/Users.js
@@ -31,6 +31,9 @@ const UsersSlice = createSlice({
}
}
state.pubKeys = Array.from(temp);
+ state.users = {
+ ...state.users
+ };
},
setUserData: (state, action) => {
let ud = action.payload;
@@ -49,9 +52,9 @@ const UsersSlice = createSlice({
state.users[x.pubkey] = x;
window.localStorage.setItem(`user:${x.pubkey}`, JSON.stringify(x));
- let newUsersObj = {};
- Object.assign(newUsersObj, state.users);
- state.users = newUsersObj;
+ state.users = {
+ ...state.users
+ };
}
}
}