Zap modal (#209)
This commit is contained in:
parent
41aa93a279
commit
1e76e729f7
@ -20,8 +20,8 @@ export default function AsyncButton(props: any) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...props} className={`btn ${props.className}${loading ? " disabled" : ""}`} onClick={(e) => handle(e)}>
|
||||
{props.children}
|
||||
</div>
|
||||
<button type="button" disabled={loading} {...props} onClick={(e) => handle(e)}>
|
||||
{props.children}
|
||||
</button>
|
||||
)
|
||||
}
|
@ -4,7 +4,7 @@ import { useState } from "react";
|
||||
import { decode as invoiceDecode } from "light-bolt11-decoder";
|
||||
import { useMemo } from "react";
|
||||
import NoteTime from "Element/NoteTime";
|
||||
import LNURLTip from "Element/LNURLTip";
|
||||
import SendSats from "Element/SendSats";
|
||||
import ZapCircle from "Icons/ZapCircle";
|
||||
import useWebln from "Hooks/useWebln";
|
||||
|
||||
@ -49,7 +49,7 @@ export default function Invoice(props: InvoiceProps) {
|
||||
<>
|
||||
<h4>Lightning Invoice</h4>
|
||||
<ZapCircle className="zap-circle" />
|
||||
<LNURLTip invoice={invoice} show={showInvoice} onClose={() => setShowInvoice(false)} />
|
||||
<SendSats title="Pay Invoice" invoice={invoice} show={showInvoice} onClose={() => setShowInvoice(false)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
.lnurl-tip {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.lnurl-tip .btn {
|
||||
background-color: inherit;
|
||||
width: 210px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.lnurl-tip .btn:hover {
|
||||
background-color: var(--gray);
|
||||
}
|
||||
|
||||
.sat-amount {
|
||||
display: inline-block;
|
||||
background-color: var(--gray-secondary);
|
||||
color: var(--font-color);
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
user-select: none;
|
||||
margin: 2px 5px;
|
||||
}
|
||||
|
||||
.sat-amount:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sat-amount.active {
|
||||
font-weight: bold;
|
||||
color: var(--note-bg);
|
||||
background-color: var(--font-color);
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice .actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice .actions .copy-action {
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice .actions .pay-actions {
|
||||
margin: 10px auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
@ -11,7 +11,7 @@ import {
|
||||
CheckRegisterResponse
|
||||
} from "Nip05/ServiceProvider";
|
||||
import AsyncButton from "Element/AsyncButton";
|
||||
import LNURLTip from "Element/LNURLTip";
|
||||
import SendSats from "Element/SendSats";
|
||||
import Copy from "Element/Copy";
|
||||
import { useUserProfile }from "Feed/ProfileFeed";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
@ -176,7 +176,7 @@ export default function Nip5Service(props: Nip05ServiceProps) {
|
||||
{availabilityResponse?.available === false && !registerStatus && <div className="flex">
|
||||
<b className="error">Not available: {mapError(availabilityResponse.why!, availabilityResponse.reasonTag || null)}</b>
|
||||
</div>}
|
||||
<LNURLTip
|
||||
<SendSats
|
||||
invoice={registerResponse?.invoice}
|
||||
show={showInvoice}
|
||||
onClose={() => setShowInvoice(false)}
|
||||
|
@ -12,6 +12,7 @@
|
||||
}
|
||||
|
||||
.note-creator textarea {
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
background-color: var(--note-bg);
|
||||
|
@ -13,7 +13,7 @@ import { formatShort } from "Number";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { getReactions, hexToBech32, normalizeReaction, Reaction } from "Util";
|
||||
import { NoteCreator } from "Element/NoteCreator";
|
||||
import LNURLTip from "Element/LNURLTip";
|
||||
import SendSats from "Element/SendSats";
|
||||
import { parseZap, ZapsSummary } from "Element/Zap";
|
||||
import { useUserProfile } from "Feed/ProfileFeed";
|
||||
import { default as NEvent } from "Nostr/Event";
|
||||
@ -268,7 +268,14 @@ export default function NoteFooter(props: NoteFooterProps) {
|
||||
show={reply}
|
||||
setShow={setReply}
|
||||
/>
|
||||
<LNURLTip svc={author?.lud16 || author?.lud06} onClose={() => setTip(false)} show={tip} author={author?.pubkey} note={ev.Id} />
|
||||
<SendSats
|
||||
svc={author?.lud16 || author?.lud06}
|
||||
onClose={() => setTip(false)}
|
||||
show={tip}
|
||||
author={author?.pubkey}
|
||||
target={author?.display_name || author?.name}
|
||||
note={ev.Id}
|
||||
/>
|
||||
</div>
|
||||
<div className="zaps-container">
|
||||
<ZapsSummary zaps={zaps} />
|
||||
|
176
src/Element/SendSats.css
Normal file
176
src/Element/SendSats.css
Normal file
@ -0,0 +1,176 @@
|
||||
.lnurl-modal .modal-body {
|
||||
padding: 0;
|
||||
max-width: 470px;
|
||||
}
|
||||
|
||||
.lnurl-modal .lnurl-tip .pfp .avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.lnurl-tip {
|
||||
padding: 24px 32px;
|
||||
background-color: #1B1B1B;
|
||||
border-radius: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.lnurl-tip {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.light .lnurl-tip {
|
||||
background-color: var(--note-bg);
|
||||
}
|
||||
|
||||
.lnurl-tip h3 {
|
||||
color: var(--font-secondary-color);
|
||||
font-size: 11px;
|
||||
letter-spacing: .11em;
|
||||
font-weight: 600;
|
||||
line-height: 13px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.lnurl-tip .close {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 16px;
|
||||
color: var(--font-secondary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lnurl-tip .close:hover {
|
||||
color: var(--font-tertiary-color);
|
||||
}
|
||||
|
||||
.lnurl-tip .lnurl-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.lnurl-tip .lnurl-header h2 {
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
.lnurl-tip .btn {
|
||||
background-color: inherit;
|
||||
width: 210px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.lnurl-tip .btn:hover {
|
||||
}
|
||||
|
||||
.amounts {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
overflow-x: scroll;
|
||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||
scrollbar-width: none; /* for Firefox */
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.amounts::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sat-amount {
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
background-color: #2A2A2A;
|
||||
color: var(--font-color);
|
||||
padding: 12px 16px;
|
||||
border-radius: 100px;
|
||||
user-select: none;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.light .sat-amount {
|
||||
background-color: var(--gray);
|
||||
}
|
||||
|
||||
.sat-amount:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.sat-amount:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sat-amount.active {
|
||||
font-weight: bold;
|
||||
color: var(--note-bg);
|
||||
background-color: var(--font-color);
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice .actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice .actions .copy-action {
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice .actions .wallet-action {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.lnurl-tip .zap-action {
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.lnurl-tip .zap-action svg {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.lnurl-tip .zap-action-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lnurl-tip .custom-amount {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.lnurl-tip .custom-amount button {
|
||||
padding: 12px 18px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.lnurl-tip canvas {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.lnurl-tip .success-action .paid {
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.lnurl-tip .success-action a {
|
||||
color: var(--highlight);
|
||||
font-size: 19px;
|
||||
}
|
@ -1,12 +1,19 @@
|
||||
import "./LNURLTip.css";
|
||||
import "./SendSats.css";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { formatShort } from "Number";
|
||||
import { bech32ToText } from "Util";
|
||||
import { HexKey } from "Nostr";
|
||||
import Check from "Icons/Check";
|
||||
import Zap from "Icons/Zap";
|
||||
import Close from "Icons/Close";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import Modal from "Element/Modal";
|
||||
import QrCode from "Element/QrCode";
|
||||
import Copy from "Element/Copy";
|
||||
import useWebln from "Hooks/useWebln";
|
||||
import useHorizontalScroll from "Hooks/useHorizontalScroll";
|
||||
|
||||
interface LNURLService {
|
||||
nostrPubkey?: HexKey
|
||||
@ -34,6 +41,7 @@ export interface LNURLTipProps {
|
||||
invoice?: string, // shortcut to invoice qr tab
|
||||
title?: string,
|
||||
notice?: string
|
||||
target?: string
|
||||
note?: HexKey
|
||||
author?: HexKey
|
||||
}
|
||||
@ -42,10 +50,19 @@ export default function LNURLTip(props: LNURLTipProps) {
|
||||
const onClose = props.onClose || (() => { });
|
||||
const service = props.svc;
|
||||
const show = props.show || false;
|
||||
const { note, author } = props
|
||||
const amounts = [50, 100, 500, 1_000, 5_000, 10_000, 50_000];
|
||||
const { note, author, target } = props
|
||||
const amounts = [500, 1_000, 5_000, 10_000, 20_000, 50_000, 100_000, 1_000_000];
|
||||
const emojis: Record<number, string> = {
|
||||
1_000: "👍",
|
||||
5_000: "💜",
|
||||
10_000: "😍",
|
||||
20_000: "🤩",
|
||||
50_000: "🔥",
|
||||
100_000: "🚀",
|
||||
1_000_000: "🤯",
|
||||
}
|
||||
const [payService, setPayService] = useState<LNURLService>();
|
||||
const [amount, setAmount] = useState<number>();
|
||||
const [amount, setAmount] = useState<number>(500);
|
||||
const [customAmount, setCustomAmount] = useState<number>();
|
||||
const [invoice, setInvoice] = useState<LNURLInvoice>();
|
||||
const [comment, setComment] = useState<string>();
|
||||
@ -53,6 +70,7 @@ export default function LNURLTip(props: LNURLTipProps) {
|
||||
const [success, setSuccess] = useState<LNURLSuccessAction>();
|
||||
const webln = useWebln(show);
|
||||
const publisher = useEventPublisher();
|
||||
const horizontalScroll = useHorizontalScroll();
|
||||
|
||||
useEffect(() => {
|
||||
if (show && !props.invoice) {
|
||||
@ -63,7 +81,7 @@ export default function LNURLTip(props: LNURLTipProps) {
|
||||
setPayService(undefined);
|
||||
setError(undefined);
|
||||
setInvoice(props.invoice ? { pr: props.invoice } : undefined);
|
||||
setAmount(undefined);
|
||||
setAmount(500);
|
||||
setComment(undefined);
|
||||
setSuccess(undefined);
|
||||
}
|
||||
@ -155,12 +173,27 @@ export default function LNURLTip(props: LNURLTipProps) {
|
||||
};
|
||||
|
||||
function custom() {
|
||||
let min = (payService?.minSendable ?? 0) / 1000;
|
||||
let min = (payService?.minSendable ?? 1000) / 1000;
|
||||
let max = (payService?.maxSendable ?? 21_000_000_000) / 1000;
|
||||
return (
|
||||
<div className="flex mb10">
|
||||
<input type="number" min={min} max={max} className="f-grow mr10" value={customAmount} onChange={(e) => setCustomAmount(parseInt(e.target.value))} />
|
||||
<div className="btn" onClick={() => selectAmount(customAmount!)}>Confirm</div>
|
||||
<div className="custom-amount flex">
|
||||
<input
|
||||
type="number"
|
||||
min={min}
|
||||
max={max}
|
||||
className="f-grow mr10"
|
||||
placeholder="Custom"
|
||||
value={customAmount}
|
||||
onChange={(e) => setCustomAmount(parseInt(e.target.value))}
|
||||
/>
|
||||
<button
|
||||
className="secondary"
|
||||
type="button"
|
||||
disabled={!Boolean(customAmount)}
|
||||
onClick={() => selectAmount(customAmount!)}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -182,22 +215,36 @@ export default function LNURLTip(props: LNURLTipProps) {
|
||||
if (invoice) return null;
|
||||
return (
|
||||
<>
|
||||
<div className="f-ellipsis mb10">{metadata?.description ?? service}</div>
|
||||
<h3>Zap amount in sats</h3>
|
||||
<div className="amounts" ref={horizontalScroll}>
|
||||
{serviceAmounts.map(a =>
|
||||
<span className={`sat-amount ${amount === a ? "active" : ""}`} key={a} onClick={() => selectAmount(a)}>
|
||||
{emojis[a] && <>{emojis[a]} </> }
|
||||
{formatShort(a)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{payService && custom()}
|
||||
<div className="flex">
|
||||
{(payService?.commentAllowed ?? 0) > 0 ?
|
||||
<input type="text" placeholder="Comment" className="mb10 f-grow" maxLength={payService?.commentAllowed} onChange={(e) => setComment(e.target.value)} /> : null}
|
||||
{(payService?.commentAllowed ?? 0) > 0 &&
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Comment"
|
||||
className="f-grow"
|
||||
maxLength={payService?.commentAllowed}
|
||||
onChange={(e) => setComment(e.target.value)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className="mb10">
|
||||
{serviceAmounts.map(a => <span className={`sat-amount ${amount === a ? "active" : ""}`} key={a} onClick={() => selectAmount(a)}>
|
||||
{a.toLocaleString()}
|
||||
</span>)}
|
||||
{payService ?
|
||||
<span className={`sat-amount ${amount === -1 ? "active" : ""}`} onClick={() => selectAmount(-1)}>
|
||||
Custom
|
||||
</span> : null}
|
||||
</div>
|
||||
{amount === -1 ? custom() : null}
|
||||
{(amount ?? 0) > 0 && <button type="button" className="mb10" onClick={() => loadInvoice()}>Get Invoice</button>}
|
||||
{(amount ?? 0) > 0 && (
|
||||
<button type="button" className="zap-action" onClick={() => loadInvoice()}>
|
||||
<div className="zap-action-container">
|
||||
<Zap /> Zap
|
||||
{target && ` ${target} `}
|
||||
{formatShort(amount)} sats
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -208,22 +255,20 @@ export default function LNURLTip(props: LNURLTipProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="invoice">
|
||||
{props.notice && <b className="error">{props.notice}</b>}
|
||||
<QrCode data={pr} link={`lightning:${pr}`} />
|
||||
<div className="actions">
|
||||
{pr && (
|
||||
<>
|
||||
<div className="copy-action">
|
||||
<Copy text={pr} maxSize={26} />
|
||||
</div>
|
||||
<div className="pay-actions">
|
||||
<button type="button" onClick={() => window.open(`lightning:${pr}`)}>
|
||||
Open Wallet
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{props.notice && <b className="error">{props.notice}</b>}
|
||||
<QrCode data={pr} link={`lightning:${pr}`} />
|
||||
<div className="actions">
|
||||
{pr && (
|
||||
<>
|
||||
<div className="copy-action">
|
||||
<Copy text={pr} maxSize={26} />
|
||||
</div>
|
||||
<button className="wallet-action" type="button" onClick={() => window.open(`lightning:${pr}`)}>
|
||||
Open Wallet
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@ -232,24 +277,46 @@ export default function LNURLTip(props: LNURLTipProps) {
|
||||
function successAction() {
|
||||
if (!success) return null;
|
||||
return (
|
||||
<>
|
||||
<p>{success?.description ?? "Paid!"}</p>
|
||||
{success.url ? <a href={success.url} rel="noreferrer" target="_blank">{success.url}</a> : null}
|
||||
</>
|
||||
<div className="success-action">
|
||||
<p className="paid">
|
||||
<Check className="success mr10" />
|
||||
{success?.description ?? "Paid!"}
|
||||
</p>
|
||||
{success.url &&
|
||||
<p>
|
||||
<a
|
||||
href={success.url}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{success.url}
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const defaultTitle = payService?.nostrPubkey ? "⚡️ Send Zap!" : "⚡️ Send sats";
|
||||
const defaultTitle = payService?.nostrPubkey ? "Send zap" : "Send sats";
|
||||
const title = target ? `${defaultTitle} to ${target}` : defaultTitle
|
||||
if (!show) return null;
|
||||
return (
|
||||
<Modal onClose={onClose}>
|
||||
<div className="lnurl-tip" onClick={(e) => e.stopPropagation()}>
|
||||
<h2>{props.title || defaultTitle}</h2>
|
||||
{invoiceForm()}
|
||||
{error ? <p className="error">{error}</p> : null}
|
||||
{payInvoice()}
|
||||
{successAction()}
|
||||
</div>
|
||||
<Modal className="lnurl-modal" onClose={onClose}>
|
||||
<div className="lnurl-tip" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="close" onClick={onClose}>
|
||||
<Close />
|
||||
</div>
|
||||
<div className="lnurl-header">
|
||||
{author && <ProfileImage pubkey={author} showUsername={false} />}
|
||||
<h2>
|
||||
{props.title || title}
|
||||
</h2>
|
||||
</div>
|
||||
{invoiceForm()}
|
||||
{error && <p className="error">{error}</p>}
|
||||
{payInvoice()}
|
||||
{successAction()}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -2,7 +2,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
overflow-x: scroll;
|
||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
@ -12,7 +13,8 @@
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 1px solid var(--gray-secondary);
|
||||
color: var(--font-tertiary-color);
|
||||
border: 1px solid var(--font-tertiary-color);
|
||||
border-radius: 16px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
@ -21,7 +23,6 @@
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
color: #A3A3A3;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-left: 52px;
|
||||
margin-left: 56px;
|
||||
}
|
||||
|
||||
.note.thread-root .zaps-summary {
|
||||
@ -70,8 +70,8 @@
|
||||
}
|
||||
|
||||
.top-zap .avatar {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.top-zap .nip05 {
|
||||
|
@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useState } from "react";
|
||||
import { useUserProfile } from "Feed/ProfileFeed";
|
||||
import { HexKey } from "Nostr";
|
||||
import LNURLTip from "Element/LNURLTip";
|
||||
import SendSats from "Element/SendSats";
|
||||
|
||||
|
||||
const ZapButton = ({ pubkey, svc }: { pubkey?: HexKey, svc?: string }) => {
|
||||
@ -19,7 +19,7 @@ const ZapButton = ({ pubkey, svc }: { pubkey?: HexKey, svc?: string }) => {
|
||||
<div className="zap-button" onClick={(e) => setZap(true)}>
|
||||
<FontAwesomeIcon icon={faBolt} />
|
||||
</div>
|
||||
<LNURLTip svc={service} show={zap} onClose={() => setZap(false)} author={pubkey} />
|
||||
<SendSats target={profile?.display_name || profile?.name} svc={service} show={zap} onClose={() => setZap(false)} author={pubkey} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
22
src/Hooks/useHorizontalScroll.tsx
Normal file
22
src/Hooks/useHorizontalScroll.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { useEffect, useRef, WheelEvent, LegacyRef } from "react";
|
||||
|
||||
function useHorizontalScroll() {
|
||||
const elRef = useRef<HTMLDivElement>();
|
||||
useEffect(() => {
|
||||
const el = elRef.current;
|
||||
if (el) {
|
||||
const onWheel = (ev: WheelEvent) => {
|
||||
if (ev.deltaY == 0) return;
|
||||
ev.preventDefault();
|
||||
el.scrollTo({ left: el.scrollLeft + ev.deltaY, behavior: "smooth" });
|
||||
};
|
||||
// @ts-ignore
|
||||
el.addEventListener("wheel", onWheel);
|
||||
// @ts-ignore
|
||||
return () => el.removeEventListener("wheel", onWheel);
|
||||
}
|
||||
}, []);
|
||||
return elRef as LegacyRef<HTMLDivElement> | undefined
|
||||
}
|
||||
|
||||
export default useHorizontalScroll;
|
11
src/Icons/Close.tsx
Normal file
11
src/Icons/Close.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import IconProps from "./IconProps";
|
||||
|
||||
const Close = (props: IconProps) => {
|
||||
return (
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M7.33332 0.666992L0.666656 7.33366M0.666656 0.666992L7.33332 7.33366" stroke="currentColor" strokeWidth="1.33333" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Close
|
@ -1,8 +1,10 @@
|
||||
import IconProps from "./IconProps";
|
||||
|
||||
const Reply = () => {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.50004 9.70199L1.33337 5.53532M1.33337 5.53532L5.50004 1.36865M1.33337 5.53532H6.66671C9.46697 5.53532 10.8671 5.53532 11.9367 6.08029C12.8775 6.55965 13.6424 7.32456 14.1217 8.26537C14.6667 9.33493 14.6667 10.7351 14.6667 13.5353V14.702" stroke="currentColor" strokeWidth="1.66667" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.5 5.5C1.5 4.09987 1.5 3.3998 1.77248 2.86502C2.01217 2.39462 2.39462 2.01217 2.86502 1.77248C3.3998 1.5 4.09987 1.5 5.5 1.5H12.5C13.9001 1.5 14.6002 1.5 15.135 1.77248C15.6054 2.01217 15.9878 2.39462 16.2275 2.86502C16.5 3.3998 16.5 4.09987 16.5 5.5V10C16.5 11.4001 16.5 12.1002 16.2275 12.635C15.9878 13.1054 15.6054 13.4878 15.135 13.7275C14.6002 14 13.9001 14 12.5 14H10.4031C9.88308 14 9.62306 14 9.37435 14.051C9.15369 14.0963 8.94017 14.1712 8.73957 14.2737C8.51347 14.3892 8.31043 14.5517 7.90434 14.8765L5.91646 16.4668C5.56973 16.7442 5.39636 16.8829 5.25045 16.8831C5.12356 16.8832 5.00352 16.8255 4.92436 16.7263C4.83333 16.6123 4.83333 16.3903 4.83333 15.9463V14C4.05836 14 3.67087 14 3.35295 13.9148C2.49022 13.6836 1.81635 13.0098 1.58519 12.147C1.5 11.8291 1.5 11.4416 1.5 10.6667V5.5Z" stroke="currentColor" strokeWidth="1.66667" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ const intl = new Intl.NumberFormat("en", {
|
||||
export function formatShort(n: number) {
|
||||
if (n < 2e3) {
|
||||
return n
|
||||
} else if (n < 1e8) {
|
||||
} else if (n < 1e6) {
|
||||
return `${intl.format(n / 1e3)}K`
|
||||
} else {
|
||||
return `${intl.format(n / 1e6)}M`
|
||||
|
@ -62,7 +62,7 @@
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.profile .name h2 {
|
||||
.details-wrapper > .name > h2 {
|
||||
margin: 12px 0 0 0;
|
||||
font-weight: 600;
|
||||
font-size: 19px;
|
||||
@ -75,11 +75,11 @@
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.profile .avatar-wrapper {
|
||||
.profile-wrapper > .avatar-wrapper {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.profile .avatar-wrapper .avatar {
|
||||
.profile-wrapper > .avatar-wrapper .avatar {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background-image: var(--img-url);
|
||||
|
@ -19,7 +19,7 @@ import Avatar from "Element/Avatar";
|
||||
import LogoutButton from "Element/LogoutButton";
|
||||
import Timeline from "Element/Timeline";
|
||||
import Text from 'Element/Text'
|
||||
import LNURLTip from "Element/LNURLTip";
|
||||
import SendSats from "Element/SendSats";
|
||||
import Nip05 from "Element/Nip05";
|
||||
import Copy from "Element/Copy";
|
||||
import ProfilePreview from "Element/ProfilePreview";
|
||||
@ -35,6 +35,7 @@ import FollowsYou from "Element/FollowsYou"
|
||||
import QrCode from "Element/QrCode";
|
||||
import Modal from "Element/Modal";
|
||||
import { ProxyImg } from "Element/ProxyImg"
|
||||
import useHorizontalScroll from "Hooks/useHorizontalScroll";
|
||||
|
||||
const ProfileTab = {
|
||||
Notes: { text: "Notes", value: 0 },
|
||||
@ -71,6 +72,7 @@ export default function ProfilePage() {
|
||||
return profileZaps
|
||||
}, [zapFeed.store, id])
|
||||
const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0)
|
||||
const horizontalScroll = useHorizontalScroll()
|
||||
|
||||
useEffect(() => {
|
||||
setTab(ProfileTab.Notes);
|
||||
@ -111,7 +113,13 @@ export default function ProfilePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<LNURLTip svc={lnurl} show={showLnQr} onClose={() => setShowLnQr(false)} author={id} />
|
||||
<SendSats
|
||||
svc={lnurl}
|
||||
show={showLnQr}
|
||||
onClose={() => setShowLnQr(false)}
|
||||
author={id}
|
||||
target={user?.display_name || user?.name}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -242,7 +250,7 @@ export default function ProfilePage() {
|
||||
{userDetails()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tabs main-content">
|
||||
<div className="tabs main-content" ref={horizontalScroll}>
|
||||
{[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Zaps, ProfileTab.Muted].map(renderTab)}
|
||||
{isMe && renderTab(ProfileTab.Blocked)}
|
||||
</div>
|
||||
|
@ -3,8 +3,8 @@
|
||||
:root {
|
||||
--bg-color: #000;
|
||||
--font-color: #FFF;
|
||||
--font-secondary-color: #7B7B7B;
|
||||
--font-tertiary-color: #666;
|
||||
--font-secondary-color: #A7A7A7;
|
||||
--font-tertiary-color: #A3A3A3;
|
||||
--font-size: 16px;
|
||||
--font-size-small: 14px;
|
||||
--font-size-tiny: 12px;
|
||||
@ -34,7 +34,7 @@ html.light {
|
||||
--bg-color: #F1F1F1;
|
||||
--font-color: #57534E;
|
||||
--font-secondary-color: #7B7B7B;
|
||||
--font-tertiary-color: #F3F3F3;
|
||||
--font-tertiary-color: #A7A7A7;
|
||||
|
||||
--highlight-light: #16AAC1;
|
||||
--highlight: #0284C7;
|
||||
@ -126,18 +126,19 @@ button {
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
button.secondary:disabled {
|
||||
color: var(--font-secondary-color);
|
||||
cursor: not-allowed;
|
||||
color: var(--font-color);
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--font-secondary-color);
|
||||
}
|
||||
|
||||
button:disabled:hover {
|
||||
cursor: not-allowed;
|
||||
color: var(--font-color);
|
||||
background-color: transparent;
|
||||
background-color: var(--gray-superdark);
|
||||
border: 1px solid var(--font-secondary-color);
|
||||
border-color: var(--gray-superdark);
|
||||
}
|
||||
|
||||
.light button.transparent {
|
||||
@ -239,14 +240,12 @@ button.icon:hover {
|
||||
}
|
||||
|
||||
.btn-rnd {
|
||||
border-radius: 100%;
|
||||
border-color: var(--gray-secondary);
|
||||
border: none;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
@media (min-width: 520px) {
|
||||
@ -257,12 +256,21 @@ textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="password"], input[type="number"], textarea, select {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 0;
|
||||
background-color: var(--gray);
|
||||
input[type="text"], input[type="password"], input[type="number"], select, textarea {
|
||||
padding: 12px;
|
||||
color: var(--font-color);
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.light input[type="text"], .light input[type="password"], .light input[type="number"], .light select, .light textarea {
|
||||
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
input:placeholder, textarea:placeholder {
|
||||
color: var(--font-tertiary-color);
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
@ -275,10 +283,6 @@ input:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
textarea:placeholder {
|
||||
color: var(--gray-superlight);
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -451,6 +455,10 @@ body.scroll-lock {
|
||||
background-color: var(--error);
|
||||
}
|
||||
|
||||
.success {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.bg-success {
|
||||
background-color: var(--success);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user