diff --git a/package.json b/package.json index 7a7852447..727a79125 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.2.1", + "@fortawesome/free-solid-svg-icons": "^6.2.1", + "@fortawesome/react-fontawesome": "^0.2.0", "@noble/secp256k1": "^1.7.0", "@reduxjs/toolkit": "^1.9.1", "bech32": "^2.0.0", diff --git a/src/index.css b/src/index.css index f5b2abf8a..1ca688b46 100644 --- a/src/index.css +++ b/src/index.css @@ -39,12 +39,17 @@ code { user-select: none; background-color: #000; border: 1px solid; + display: inline-block; } .btn:hover { background-color: #333; } +.btn-sm { + padding: 5px; +} + input[type="text"], input[type="password"] { padding: 10px; border-radius: 5px; @@ -93,6 +98,7 @@ div.form-group { div.form-group > div { padding: 3px 5px; + word-break: break-word; } div.form-group > div:first-child { diff --git a/src/pages/Login.js b/src/pages/Login.js index 35b820146..7175b183f 100644 --- a/src/pages/Login.js +++ b/src/pages/Login.js @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { setPrivateKey, setPublicKey } from "../state/Login"; +import { setPrivateKey, setNip07PubKey } from "../state/Login"; import * as secp from '@noble/secp256k1'; import { bech32 } from "bech32"; import { useNavigate } from "react-router-dom"; @@ -32,12 +32,11 @@ export default function LoginPage() { async function doNip07Login() { let pubKey = await window.nostr.getPublicKey(); - dispatch(setPublicKey(pubKey)); + dispatch(setNip07PubKey(pubKey)); } function altLogins() { let nip07 = 'nostr' in window; - if (!nip07) { return null; } diff --git a/src/pages/ProfilePage.css b/src/pages/ProfilePage.css index fcc84d7de..a806d757a 100644 --- a/src/pages/ProfilePage.css +++ b/src/pages/ProfilePage.css @@ -8,8 +8,8 @@ } .profile .avatar { - width: 256px; - height: 256px; + width: 128px; + height: 128px; background-size: contain; cursor: pointer; } @@ -26,4 +26,15 @@ .profile .avatar .edit:hover { opacity: 0.5; -} \ No newline at end of file +} + +@media(max-width: 720px) { + .profile { + flex-direction: column; + align-items: center; + } + .profile > div:last-child { + margin: 0; + width: 100%; + } +} diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 4bb7c54c0..f58e3fda1 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -8,6 +8,9 @@ import Nostrich from "../nostrich.jpg"; import useEventPublisher from "./feed/EventPublisher"; import useTimelineFeed from "./feed/TimelineFeed"; import Note from "../element/Note"; +import { bech32 } from "bech32"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faQrcode } from "@fortawesome/free-solid-svg-icons"; export default function ProfilePage() { const dispatch = useDispatch(); @@ -37,6 +40,23 @@ export default function ProfilePage() { } }, [user]); + useEffect(() => { + // some clients incorrectly set this to LNURL service, patch this + if (lud16.toLowerCase().startsWith("lnurl")) { + let decoded = bech32.decode(lud16, 1000); + let url = new TextDecoder().decode(Uint8Array.from(bech32.fromWords(decoded.words))); + if (url.startsWith("http")) { + let parsedUri = new URL(url); + // is lightning address + if (parsedUri.pathname.startsWith("/.well-known/lnurlp/")) { + let pathParts = parsedUri.pathname.split('/'); + let username = pathParts[pathParts.length - 1]; + setLud16(`${username}@${parsedUri.hostname}`); + } + } + } + }, [lud16]); + async function saveProfile() { let ev = await publisher.metadata({ name, @@ -79,7 +99,7 @@ export default function ProfilePage() {
-
Lightning Address:
+
LN Address:
setLud16(e.target.value)} />
@@ -116,18 +136,23 @@ export default function ProfilePage() { {website}
: null} -
-
NIP-05:
-
- {nip05} -
-
-
-
Lightning Address:
-
- {lud16} -
-
+ {nip05 ? +
+
NIP-05:
+
+ {nip05} +
+
: null} + {lud16 ? +
+
LN Address:
+
+ {lud16}  +
{ }}> + +
+
+
: null} ) } @@ -135,15 +160,13 @@ export default function ProfilePage() { return ( <>
-
-
- {isMe ? -
-
Edit
-
- : null - } -
+
+ {isMe ? +
+
Edit
+
+ : null + }
{isMe ? editor() : details()} diff --git a/src/pages/feed/EventPublisher.js b/src/pages/feed/EventPublisher.js index 712e58681..583e9f9b1 100644 --- a/src/pages/feed/EventPublisher.js +++ b/src/pages/feed/EventPublisher.js @@ -9,6 +9,26 @@ export default function useEventPublisher() { const system = useContext(NostrContext); const pubKey = useSelector(s => s.login.publicKey); const privKey = useSelector(s => s.login.privateKey); + const nip07 = useSelector(s => s.login.nip07); + const hasNip07 = 'nostr' in window; + + /** + * + * @param {Event} ev + * @param {*} privKey + * @returns + */ + async function signEvent(ev, privKey) { + if(nip07 === true && hasNip07) { + ev.Id = await ev.CreateId(); + let tmpEv = await window.nostr.signEvent(ev.ToObject()); + console.log(tmpEv); + return Event.FromObject(tmpEv); + } else { + await ev.Sign(privKey); + } + return ev; + } return { broadcast: (ev) => { @@ -19,8 +39,7 @@ export default function useEventPublisher() { let ev = Event.ForPubKey(pubKey); ev.Kind = EventKind.SetMetadata; ev.Content = JSON.stringify(obj); - await ev.Sign(privKey); - return ev; + return await signEvent(ev, privKey); }, note: async (msg) => { if(typeof msg !== "string") { @@ -29,8 +48,7 @@ export default function useEventPublisher() { let ev = Event.ForPubKey(pubKey); ev.Kind = EventKind.TextNote; ev.Content = msg; - await ev.Sign(privKey); - return ev; + return await signEvent(ev, privKey); }, like: async (evRef) => { let ev = Event.ForPubKey(pubKey); @@ -38,8 +56,7 @@ export default function useEventPublisher() { ev.Content = "+"; ev.Tags.push(new Tag(["e", evRef.Id], 0)); ev.Tags.push(new Tag(["p", evRef.PubKey], 1)); - await ev.Sign(privKey); - return ev; + return await signEvent(ev, privKey); }, dislike: async (evRef) => { let ev = Event.ForPubKey(pubKey); @@ -47,8 +64,7 @@ export default function useEventPublisher() { ev.Content = "-"; ev.Tags.push(new Tag(["e", evRef.Id], 0)); ev.Tags.push(new Tag(["p", evRef.PubKey], 1)); - await ev.Sign(privKey); - return ev; + return await signEvent(ev, privKey); } } } \ No newline at end of file diff --git a/src/state/Login.js b/src/state/Login.js index 0ec99c879..3588856c7 100644 --- a/src/state/Login.js +++ b/src/state/Login.js @@ -24,7 +24,12 @@ const LoginSlice = createSlice({ /** * A list of pubkeys this user follows */ - follows: [] + follows: [], + + /** + * Login keys are managed by extension + */ + nip07: false, }, reducers: { init: (state) => { @@ -47,6 +52,10 @@ const LoginSlice = createSlice({ setPublicKey: (state, action) => { state.publicKey = action.payload; }, + setNip07PubKey: (state, action) => { + state.publicKey = action.payload; + state.nip07 = true; + }, setRelays: (state, action) => { state.relays = action.payload; }, @@ -60,5 +69,5 @@ const LoginSlice = createSlice({ } }); -export const { init, setPrivateKey, setPublicKey, setRelays, setFollows, logout } = LoginSlice.actions; +export const { init, setPrivateKey, setPublicKey, setNip07PubKey, setRelays, setFollows, logout } = LoginSlice.actions; export const reducer = LoginSlice.reducer; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9ae41a052..0ca22cecd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1200,6 +1200,32 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@fortawesome/fontawesome-common-types@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.1.tgz#411e02a820744d3f7e0d8d9df9d82b471beaa073" + integrity sha512-Sz07mnQrTekFWLz5BMjOzHl/+NooTdW8F8kDQxjWwbpOJcnoSg4vUDng8d/WR1wOxM0O+CY9Zw0nR054riNYtQ== + +"@fortawesome/fontawesome-svg-core@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.1.tgz#e87e905e444b5e7b715af09b64d27b53d4c8f9d9" + integrity sha512-HELwwbCz6C1XEcjzyT1Jugmz2NNklMrSPjZOWMlc+ZsHIVk+XOvOXLGGQtFBwSyqfJDNgRq4xBCwWOaZ/d9DEA== + dependencies: + "@fortawesome/fontawesome-common-types" "6.2.1" + +"@fortawesome/free-solid-svg-icons@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.1.tgz#2290ea5adcf1537cbd0c43de6feb38af02141d27" + integrity sha512-oKuqrP5jbfEPJWTij4sM+/RvgX+RMFwx3QZCZcK9PrBDgxC35zuc7AOFsyMjMd/PIFPeB2JxyqDr5zs/DZFPPw== + dependencies: + "@fortawesome/fontawesome-common-types" "6.2.1" + +"@fortawesome/react-fontawesome@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz#d90dd8a9211830b4e3c08e94b63a0ba7291ddcf4" + integrity sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw== + dependencies: + prop-types "^15.8.1" + "@humanwhocodes/config-array@^0.11.6": version "0.11.8" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"