LNURL tipping
This commit is contained in:
parent
8c0b0ac986
commit
037f39e386
11
src/Util.js
11
src/Util.js
@ -33,6 +33,17 @@ export function bech32ToHex(str) {
|
|||||||
return secp.utils.bytesToHex(Uint8Array.from(buff));
|
return secp.utils.bytesToHex(Uint8Array.from(buff));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode bech32 to string UTF-8
|
||||||
|
* @param {string} str bech32 encoded string
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function bech32ToText(str) {
|
||||||
|
let decoded = bech32.decode(str, 1000);
|
||||||
|
let buf = bech32.fromWords(decoded.words);
|
||||||
|
return new TextDecoder().decode(Uint8Array.from(buf));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert hex note id to bech32 link url
|
* Convert hex note id to bech32 link url
|
||||||
* @param {string} hex
|
* @param {string} hex
|
||||||
|
15
src/element/LNURLTip.css
Normal file
15
src/element/LNURLTip.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.lnurl-tip {
|
||||||
|
background-color: #222;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 50vw;
|
||||||
|
text-align: center;
|
||||||
|
min-height: 10vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width: 720px) {
|
||||||
|
.lnurl-tip {
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
114
src/element/LNURLTip.js
Normal file
114
src/element/LNURLTip.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import "./LNURLTip.css";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { bech32ToText } from "../Util";
|
||||||
|
import Modal from "./Modal";
|
||||||
|
import QrCode from "./QrCode";
|
||||||
|
|
||||||
|
export default function LNURLTip(props) {
|
||||||
|
const onClose = props.onClose || (() => { });
|
||||||
|
const service = props.svc;
|
||||||
|
const show = props.show || false;
|
||||||
|
const amounts = [50, 100, 500, 1_000, 5_000, 10_000];
|
||||||
|
const [payService, setPayService] = useState("");
|
||||||
|
const [amount, setAmount] = useState(0);
|
||||||
|
const [invoice, setInvoice] = useState("");
|
||||||
|
const [comment, setComment] = useState("");
|
||||||
|
const [error, setError] = useState("")
|
||||||
|
|
||||||
|
async function fetchJson(url) {
|
||||||
|
let rsp = await fetch(url);
|
||||||
|
if (rsp.ok) {
|
||||||
|
let data = await rsp.json();
|
||||||
|
console.log(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadService() {
|
||||||
|
let isServiceUrl = service.toLowerCase().startsWith("lnurl");
|
||||||
|
if (isServiceUrl) {
|
||||||
|
let serviceUrl = bech32ToText(service);
|
||||||
|
return await fetchJson(serviceUrl);
|
||||||
|
} else {
|
||||||
|
let ns = service.split("@");
|
||||||
|
return await fetchJson(`https://${ns[1]}/.well-known/lnurlp/${ns[0]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadInvoice() {
|
||||||
|
if (amount === 0) return null;
|
||||||
|
const url = `${payService.callback}?amount=${parseInt(amount * 1000)}&comment=${encodeURIComponent(comment)}`;
|
||||||
|
try {
|
||||||
|
let rsp = await fetch(url);
|
||||||
|
if (rsp.ok) {
|
||||||
|
let data = await rsp.json();
|
||||||
|
console.log(data);
|
||||||
|
if (data.status === "ERROR") {
|
||||||
|
setError(data.reason);
|
||||||
|
} else {
|
||||||
|
setInvoice(data.pr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError("Failed to load invoice");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setError("Failed to load invoice");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (payService && amount > 0) {
|
||||||
|
loadInvoice();
|
||||||
|
}
|
||||||
|
}, [payService, amount]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (show) {
|
||||||
|
loadService()
|
||||||
|
.then(a => setPayService(a))
|
||||||
|
.catch(() => setError("Failed to load LNURL service"));
|
||||||
|
}
|
||||||
|
}, [show, service]);
|
||||||
|
|
||||||
|
const serviceAmounts = useMemo(() => {
|
||||||
|
if (payService) {
|
||||||
|
let min = (payService.minSendable ?? 0) / 1000;
|
||||||
|
let max = (payService.maxSendable ?? 0) / 1000;
|
||||||
|
return amounts.filter(a => a >= min && a <= max);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [payService]);
|
||||||
|
|
||||||
|
const metadata = useMemo(() => {
|
||||||
|
if (payService) {
|
||||||
|
let meta = JSON.parse(payService.metadata);
|
||||||
|
return {
|
||||||
|
description: meta.find(a => a[0] === "text/plain")[1]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [payService]);
|
||||||
|
|
||||||
|
if (!show) return null;
|
||||||
|
return (
|
||||||
|
<Modal onClose={() => onClose()}>
|
||||||
|
<div className="lnurl-tip" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<h2>⚡️ Send sats</h2>
|
||||||
|
<div className="f-ellipsis mb10">{service}</div>
|
||||||
|
<div className="f-ellipsis mb10">{metadata?.description}</div>
|
||||||
|
<div className="flex">
|
||||||
|
{payService?.commentAllowed > 0 ?
|
||||||
|
<input type="text" placeholder="Comment" className="mb10 f-grow" maxLength={payService?.commentAllowed} onChange={(e) => setComment(e.target.value)} /> : null}
|
||||||
|
</div>
|
||||||
|
<div className="mb10">
|
||||||
|
{serviceAmounts.map(a => <span className={`pill ${amount === a ? "active" : ""}`} key={a} onClick={() => setAmount(a)}>
|
||||||
|
{a.toLocaleString()}
|
||||||
|
</span>)}
|
||||||
|
</div>
|
||||||
|
{error ? <p className="error">{error}</p> : null}
|
||||||
|
<QrCode link={invoice} />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
@ -2,8 +2,8 @@ import "./Note.css";
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { faHeart, faThumbsDown, faReply, faInfo, faTrash } from "@fortawesome/free-solid-svg-icons";
|
import { faHeart, faReply, faThumbsDown, faTrash, faBolt } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import Event from "../nostr/Event";
|
import Event from "../nostr/Event";
|
||||||
@ -12,6 +12,7 @@ import useEventPublisher from "../feed/EventPublisher";
|
|||||||
import { NoteCreator } from "./NoteCreator";
|
import { NoteCreator } from "./NoteCreator";
|
||||||
import { extractLinks, extractMentions, extractInvoices } from "../Text";
|
import { extractLinks, extractMentions, extractInvoices } from "../Text";
|
||||||
import { eventLink } from "../Util";
|
import { eventLink } from "../Util";
|
||||||
|
import LNURLTip from "./LNURLTip";
|
||||||
|
|
||||||
export default function Note(props) {
|
export default function Note(props) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -28,13 +29,15 @@ export default function Note(props) {
|
|||||||
const likes = reactions?.filter(({ Content }) => Content === "+" || Content === "❤️").length ?? 0
|
const likes = reactions?.filter(({ Content }) => Content === "+" || Content === "❤️").length ?? 0
|
||||||
const dislikes = reactions?.filter(({ Content }) => Content === "-").length ?? 0
|
const dislikes = reactions?.filter(({ Content }) => Content === "-").length ?? 0
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const [showReply, setShowReply] = useState(false);
|
const [reply, setReply] = useState(false);
|
||||||
|
const [tip, setTip] = useState(false);
|
||||||
const users = useSelector(s => s.users?.users);
|
const users = useSelector(s => s.users?.users);
|
||||||
const login = useSelector(s => s.login.publicKey);
|
const login = useSelector(s => s.login.publicKey);
|
||||||
const ev = dataEvent ?? Event.FromObject(data);
|
const ev = dataEvent ?? Event.FromObject(data);
|
||||||
const isMine = ev.PubKey === login;
|
const isMine = ev.PubKey === login;
|
||||||
const liked = reactions?.find(({ PubKey, Content }) => Content === "+" && PubKey === login)
|
const liked = reactions?.find(({ PubKey, Content }) => Content === "+" && PubKey === login)
|
||||||
const disliked = reactions?.find(({ PubKey, Content }) => Content === "-" && PubKey === login)
|
const disliked = reactions?.find(({ PubKey, Content }) => Content === "-" && PubKey === login)
|
||||||
|
const author = users[ev.PubKey];
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
showHeader: true,
|
showHeader: true,
|
||||||
@ -106,6 +109,20 @@ export default function Note(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tipButton() {
|
||||||
|
let service = author?.lud16 || author?.lud06;
|
||||||
|
if (service) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="pill" onClick={(e) => setTip(true)}>
|
||||||
|
<FontAwesomeIcon icon={faBolt} />
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ev.IsContent()) {
|
if (!ev.IsContent()) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -132,9 +149,10 @@ export default function Note(props) {
|
|||||||
{options.showFooter ?
|
{options.showFooter ?
|
||||||
<div className="footer">
|
<div className="footer">
|
||||||
{isMine ? <span className="pill">
|
{isMine ? <span className="pill">
|
||||||
<FontAwesomeIcon icon={faTrash} onClick={() => deleteEvent()} />
|
<FontAwesomeIcon icon={faTrash} onClick={(e) => deleteEvent()} />
|
||||||
</span> : null}
|
</span> : null}
|
||||||
<span className="pill" onClick={() => setShowReply(!showReply)}>
|
{tipButton()}
|
||||||
|
<span className="pill" onClick={(e) => setReply(s => !s)}>
|
||||||
<FontAwesomeIcon icon={faReply} />
|
<FontAwesomeIcon icon={faReply} />
|
||||||
</span>
|
</span>
|
||||||
{Object.keys(emojiReactions).map((emoji) => {
|
{Object.keys(emojiReactions).map((emoji) => {
|
||||||
@ -156,11 +174,9 @@ export default function Note(props) {
|
|||||||
<FontAwesomeIcon color={disliked ? "orange" : "currentColor"} icon={faThumbsDown} />
|
<FontAwesomeIcon color={disliked ? "orange" : "currentColor"} icon={faThumbsDown} />
|
||||||
{dislikes}
|
{dislikes}
|
||||||
</span>
|
</span>
|
||||||
<span className="pill" onClick={() => console.debug(ev)}>
|
|
||||||
<FontAwesomeIcon icon={faInfo} />
|
|
||||||
</span>
|
|
||||||
</div> : null}
|
</div> : null}
|
||||||
{showReply ? <NoteCreator replyTo={ev} onSend={() => setShowReply(false)} /> : null}
|
<NoteCreator replyTo={ev} onSend={(e) => setReply(false)} show={reply} />
|
||||||
|
<LNURLTip svc={author?.lud16 || author?.lud06} onClose={(e) => setTip(false)} show={tip} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -10,6 +10,7 @@ import { FileExtensionRegex } from "../Const";
|
|||||||
export function NoteCreator(props) {
|
export function NoteCreator(props) {
|
||||||
const replyTo = props.replyTo;
|
const replyTo = props.replyTo;
|
||||||
const onSend = props.onSend;
|
const onSend = props.onSend;
|
||||||
|
const show = props.show || false;
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const [note, setNote] = useState("");
|
const [note, setNote] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
@ -42,6 +43,7 @@ export function NoteCreator(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!show) return false;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{replyTo ? <small>{`Reply to: ${replyTo.Id.substring(0, 8)}`}</small> : null}
|
{replyTo ? <small>{`Reply to: ${replyTo.Id.substring(0, 8)}`}</small> : null}
|
||||||
@ -51,7 +53,7 @@ export function NoteCreator(props) {
|
|||||||
<div className="actions flex f-row">
|
<div className="actions flex f-row">
|
||||||
<div className="attachment flex f-row">
|
<div className="attachment flex f-row">
|
||||||
{error.length > 0 ? <b className="error">{error}</b> : null}
|
{error.length > 0 ? <b className="error">{error}</b> : null}
|
||||||
<FontAwesomeIcon icon={faPaperclip} size="xl" onClick={(e) => attachFile()}/>
|
<FontAwesomeIcon icon={faPaperclip} size="xl" onClick={(e) => attachFile()} />
|
||||||
</div>
|
</div>
|
||||||
<div className="btn" onClick={() => sendNote()}>Send</div>
|
<div className="btn" onClick={() => sendNote()}>Send</div>
|
||||||
</div>
|
</div>
|
||||||
|
41
src/element/QrCode.js
Normal file
41
src/element/QrCode.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import QRCodeStyling from "qr-code-styling";
|
||||||
|
import {useEffect, useRef} from "react";
|
||||||
|
|
||||||
|
export default function QrCode(props) {
|
||||||
|
const qrRef = useRef();
|
||||||
|
const link = props.link;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("Showing QR: ", link);
|
||||||
|
if (link?.length > 0) {
|
||||||
|
let qr = new QRCodeStyling({
|
||||||
|
width: props.width || 256,
|
||||||
|
height: props.height || 256,
|
||||||
|
data: link,
|
||||||
|
margin: 5,
|
||||||
|
type: 'canvas',
|
||||||
|
image: props.avatar,
|
||||||
|
dotsOptions: {
|
||||||
|
type: 'rounded'
|
||||||
|
},
|
||||||
|
cornersSquareOptions: {
|
||||||
|
type: 'extra-rounded'
|
||||||
|
},
|
||||||
|
imageOptions: {
|
||||||
|
crossOrigin: "anonymous"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
qrRef.current.innerHTML = "";
|
||||||
|
qr.append(qrRef.current);
|
||||||
|
qrRef.current.onclick = function (e) {
|
||||||
|
let elm = document.createElement("a");
|
||||||
|
elm.href = `lightning:${link}`;
|
||||||
|
elm.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [link]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="qr" ref={qrRef}></div>
|
||||||
|
);
|
||||||
|
}
|
@ -118,6 +118,10 @@ span.pill {
|
|||||||
margin: 2px 5px;
|
margin: 2px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.pill.active {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
span.pill:hover {
|
span.pill:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -177,6 +181,10 @@ body.scroll-lock {
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb10 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import "./ProfilePage.css";
|
import "./ProfilePage.css";
|
||||||
import { useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { bech32 } from "bech32";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faQrcode, faCopy, faCheck } from "@fortawesome/free-solid-svg-icons";
|
import { faQrcode, faCopy, faCheck } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
@ -11,14 +10,14 @@ import { resetProfile } from "../state/Users";
|
|||||||
import Nostrich from "../nostrich.jpg";
|
import Nostrich from "../nostrich.jpg";
|
||||||
import useEventPublisher from "../feed/EventPublisher";
|
import useEventPublisher from "../feed/EventPublisher";
|
||||||
import QRCodeStyling from "qr-code-styling";
|
import QRCodeStyling from "qr-code-styling";
|
||||||
import Modal from "../element/Modal";
|
|
||||||
import { logout } from "../state/Login";
|
import { logout } from "../state/Login";
|
||||||
import FollowButton from "../element/FollowButton";
|
import FollowButton from "../element/FollowButton";
|
||||||
import VoidUpload from "../feed/VoidUpload";
|
import VoidUpload from "../feed/VoidUpload";
|
||||||
import { openFile, parseId } from "../Util";
|
import { bech32ToText, openFile, parseId } from "../Util";
|
||||||
import Timeline from "../element/Timeline";
|
import Timeline from "../element/Timeline";
|
||||||
import { extractLinks } from '../Text'
|
import { extractLinks } from '../Text'
|
||||||
import { useCopy } from '../useCopy'
|
import { useCopy } from '../useCopy'
|
||||||
|
import LNURLTip from "../element/LNURLTip";
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -36,16 +35,18 @@ export default function ProfilePage() {
|
|||||||
const [about, setAbout] = useState("");
|
const [about, setAbout] = useState("");
|
||||||
const [website, setWebsite] = useState("");
|
const [website, setWebsite] = useState("");
|
||||||
const [nip05, setNip05] = useState("");
|
const [nip05, setNip05] = useState("");
|
||||||
|
const [lud06, setLud06] = useState("");
|
||||||
const [lud16, setLud16] = useState("");
|
const [lud16, setLud16] = useState("");
|
||||||
const [showLnQr, setShowLnQr] = useState(false);
|
const [showLnQr, setShowLnQr] = useState(false);
|
||||||
|
|
||||||
useMemo(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
setName(user.name ?? "");
|
setName(user.name ?? "");
|
||||||
setPicture(user.picture ?? "");
|
setPicture(user.picture ?? "");
|
||||||
setAbout(user.about ?? "");
|
setAbout(user.about ?? "");
|
||||||
setWebsite(user.website ?? "");
|
setWebsite(user.website ?? "");
|
||||||
setNip05(user.nip05 ?? "");
|
setNip05(user.nip05 ?? "");
|
||||||
|
setLud06(user.lud06 ?? "");
|
||||||
setLud16(user.lud16 ?? "");
|
setLud16(user.lud16 ?? "");
|
||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
@ -53,8 +54,7 @@ export default function ProfilePage() {
|
|||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
// some clients incorrectly set this to LNURL service, patch this
|
// some clients incorrectly set this to LNURL service, patch this
|
||||||
if (lud16.toLowerCase().startsWith("lnurl")) {
|
if (lud16.toLowerCase().startsWith("lnurl")) {
|
||||||
let decoded = bech32.decode(lud16, 1000);
|
let url = bech32ToText(lud16);
|
||||||
let url = new TextDecoder().decode(Uint8Array.from(bech32.fromWords(decoded.words)));
|
|
||||||
if (url.startsWith("http")) {
|
if (url.startsWith("http")) {
|
||||||
let parsedUri = new URL(url);
|
let parsedUri = new URL(url);
|
||||||
// is lightning address
|
// is lightning address
|
||||||
@ -172,6 +172,7 @@ export default function ProfilePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function details() {
|
function details() {
|
||||||
|
const lnurl = lud16 || lud06;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex name">
|
<div className="flex name">
|
||||||
@ -195,17 +196,13 @@ export default function ProfilePage() {
|
|||||||
<p>{extractLinks([about])}</p>
|
<p>{extractLinks([about])}</p>
|
||||||
{website ? <a href={website} target="_blank" rel="noreferrer">{website}</a> : null}
|
{website ? <a href={website} target="_blank" rel="noreferrer">{website}</a> : null}
|
||||||
|
|
||||||
{lud16 ? <div className="flex">
|
{lnurl ? <div className="flex">
|
||||||
<div className="btn" onClick={(e) => setShowLnQr(true)}>
|
<div className="btn" onClick={(e) => setShowLnQr(true)}>
|
||||||
<FontAwesomeIcon icon={faQrcode} size="xl" />
|
<FontAwesomeIcon icon={faQrcode} size="xl" />
|
||||||
</div>
|
</div>
|
||||||
<div className="f-ellipsis"> ⚡️ {lud16}</div>
|
<div className="f-ellipsis"> ⚡️ {lnurl}</div>
|
||||||
</div> : null}
|
</div> : null}
|
||||||
{showLnQr === true ?
|
<LNURLTip svc={lnurl} show={showLnQr} onClose={() => setShowLnQr(false)}/>
|
||||||
<Modal onClose={() => setShowLnQr(false)}>
|
|
||||||
<h4>{lud16}</h4>
|
|
||||||
<div ref={qrRef}></div>
|
|
||||||
</Modal> : null}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ export default function RootPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{pubKey ? <>
|
{pubKey ? <>
|
||||||
<NoteCreator />
|
<NoteCreator show={true}/>
|
||||||
<div className="tabs root-tabs">
|
<div className="tabs root-tabs">
|
||||||
<div className={`f-1 ${tab === RootTab.Follows ? "active" : ""}`} onClick={() => setTab(RootTab.Follows)}>
|
<div className={`f-1 ${tab === RootTab.Follows ? "active" : ""}`} onClick={() => setTab(RootTab.Follows)}>
|
||||||
Follows
|
Follows
|
||||||
|
Loading…
Reference in New Issue
Block a user