Zap modal

This commit is contained in:
Alejandro Gomez 2023-02-07 11:38:34 +01:00
parent 41aa93a279
commit 01348a1d9f
No known key found for this signature in database
GPG Key ID: 4DF39E566658C817
13 changed files with 305 additions and 98 deletions

View File

@ -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>
)
}

View File

@ -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)} />
<LNURLTip title="Pay Invoice" invoice={invoice} show={showInvoice} onClose={() => setShowInvoice(false)} />
</>
)
}

View File

@ -1,5 +1,58 @@
.lnurl-modal .lnurl-tip .pfp .avatar {
width: 48px;
height: 48px;
}
.lnurl-modal .modal-body {
padding: 0;
max-width: 470px;
}
.lnurl-tip {
text-align: center;
padding: 24px 32px;
background-color: #1B1B1B;
border-radius: 16px;
position: relative;
}
.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 .header {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 32px;
}
.lnurl-tip .header h2 {
margin: 0;
flex-grow: 1;
font-weight: 600;
font-size: 16px;
line-height: 19px;
}
.lnurl-tip .btn {
@ -9,17 +62,39 @@
}
.lnurl-tip .btn:hover {
background-color: var(--gray);
}
.amounts {
display: flex;
width: 100%;
overflow-x: auto;
scrollbar-width: none; /* Firefox */
margin-bottom: 16px;
}
.amounts::--webkit-scrollbar {
display: none;
}
.sat-amount {
text-align: center;
display: inline-block;
background-color: var(--gray-secondary);
background-color: #2A2A2A;
color: var(--font-color);
padding: 2px 10px;
border-radius: 10px;
padding: 12px 16px;
border-radius: 100px;
user-select: none;
margin: 2px 5px;
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 {
@ -50,10 +125,45 @@
margin: 10px auto;
}
.lnurl-tip .invoice .actions .pay-actions {
margin: 10px auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.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;
}

View File

@ -1,8 +1,14 @@
import "./LNURLTip.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";
@ -34,6 +40,7 @@ export interface LNURLTipProps {
invoice?: string, // shortcut to invoice qr tab
title?: string,
notice?: string
target?: string
note?: HexKey
author?: HexKey
}
@ -42,10 +49,16 @@ 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];
const emojis: Record<number, string> = {
1_000: "👍",
5_000: "💜",
10_000: "😍",
20_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>();
@ -63,7 +76,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);
}
@ -158,9 +171,24 @@ export default function LNURLTip(props: LNURLTipProps) {
let min = (payService?.minSendable ?? 0) / 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 +210,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">
{serviceAmounts.map(a =>
<span className={`sat-amount ${amount === a ? "active" : ""}`} key={a} onClick={() => selectAmount(a)}>
{emojis[a] && <>{emojis[a]}&nbsp;</> }
{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 +250,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 +272,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"
>
yo{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="header">
{author && <ProfileImage pubkey={author} showUsername={false} />}
<h2>
{props.title || title}
</h2>
</div>
{invoiceForm()}
{error && <p className="error">{error}</p>}
{payInvoice()}
{successAction()}
</div>
</Modal>
)
}

View File

@ -12,6 +12,7 @@
}
.note-creator textarea {
border: none;
outline: none;
resize: none;
background-color: var(--note-bg);

View File

@ -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} />
<LNURLTip
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} />

View File

@ -12,7 +12,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 +22,6 @@
font-weight: 600;
font-size: 14px;
line-height: 17px;
color: #A3A3A3;
margin-right: 12px;
}

View File

@ -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 {

View File

@ -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} />
<LNURLTip target={profile?.display_name || profile?.name} svc={service} show={zap} onClose={() => setZap(false)} author={pubkey} />
</>
)
}

11
src/Icons/Close.tsx Normal file
View 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

View File

@ -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>
)
}

View File

@ -111,7 +111,13 @@ export default function ProfilePage() {
</div>
)}
<LNURLTip svc={lnurl} show={showLnQr} onClose={() => setShowLnQr(false)} author={id} />
<LNURLTip
svc={lnurl}
show={showLnQr}
onClose={() => setShowLnQr(false)}
author={id}
target={user?.display_name || user?.name}
/>
</div>
)
}

View File

@ -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);
}