From 7317bc4c350203b8aee0e7ddadc988802c684546 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 16 May 2023 22:30:52 +0100 Subject: [PATCH 1/4] feat: zap-pool --- packages/app/public/icons.svg | 4 + packages/app/src/Element/NoteFooter.tsx | 25 +-- packages/app/src/Element/SendSats.tsx | 7 +- packages/app/src/Pages/ZapPool.css | 3 + packages/app/src/Pages/ZapPool.tsx | 185 ++++++++++++++++++++++ packages/app/src/Pages/settings/Index.tsx | 6 +- packages/app/src/Upload/index.ts | 20 +++ packages/app/src/ZapPoolController.ts | 124 +++++++++++++++ packages/app/src/index.tsx | 6 +- 9 files changed, 357 insertions(+), 23 deletions(-) create mode 100644 packages/app/src/Pages/ZapPool.css create mode 100644 packages/app/src/Pages/ZapPool.tsx create mode 100644 packages/app/src/ZapPoolController.ts diff --git a/packages/app/public/icons.svg b/packages/app/public/icons.svg index 43c7b523..806a5df3 100644 --- a/packages/app/public/icons.svg +++ b/packages/app/public/icons.svg @@ -154,5 +154,9 @@ + + + + \ No newline at end of file diff --git a/packages/app/src/Element/NoteFooter.tsx b/packages/app/src/Element/NoteFooter.tsx index a9f523c4..219738bb 100644 --- a/packages/app/src/Element/NoteFooter.tsx +++ b/packages/app/src/Element/NoteFooter.tsx @@ -10,7 +10,7 @@ import Spinner from "Icons/Spinner"; import { formatShort } from "Number"; import useEventPublisher from "Feed/EventPublisher"; -import { bech32ToHex, delay, normalizeReaction, unwrap } from "Util"; +import { delay, normalizeReaction, unwrap } from "Util"; import { NoteCreator } from "Element/NoteCreator"; import { ReBroadcaster } from "Element/ReBroadcaster"; import Reactions from "Element/Reactions"; @@ -25,13 +25,13 @@ import { reset as resetReBroadcast, } from "State/ReBroadcast"; import useModeration from "Hooks/useModeration"; -import { SnortPubKey, TranslateHost } from "Const"; +import { TranslateHost } from "Const"; import { LNURL } from "LNURL"; -import { DonateLNURL } from "Pages/DonatePage"; import { useWallet } from "Wallet"; import useLogin from "Hooks/useLogin"; import { setBookmarked, setPinned } from "Login"; import { useInteractionCache } from "Hooks/useInteractionCache"; +import { ZapPoolController } from "ZapPoolController"; import messages from "./messages"; @@ -160,7 +160,6 @@ export default function NoteFooter(props: NoteFooterProps) { setZapping(true); try { await fastZapInner(lnurl, prefs.defaultZapAmount, ev.pubkey, ev.id); - fastZapDonate(); } catch (e) { console.warn("Fast zap failed", e); if (!(e instanceof Error) || e.message !== "User rejected") { @@ -184,26 +183,12 @@ export default function NoteFooter(props: NoteFooterProps) { const zap = handler.canZap && publisher ? await publisher.zap(amount * 1000, key, zr, id) : undefined; const invoice = await handler.getInvoice(amount, undefined, zap); await wallet?.payInvoice(unwrap(invoice.pr)); + ZapPoolController.allocate(amount); await interactionCache.zap(); }); } - function fastZapDonate() { - queueMicrotask(async () => { - if (prefs.fastZapDonate > 0) { - // spin off donate - const donateAmount = Math.floor(prefs.defaultZapAmount * prefs.fastZapDonate); - if (donateAmount > 0) { - console.debug(`Donating ${donateAmount} sats to ${DonateLNURL}`); - fastZapInner(DonateLNURL, donateAmount, bech32ToHex(SnortPubKey)) - .then(() => console.debug("Donation sent! Thank You!")) - .catch(() => console.debug("Failed to donate")); - } - } - }); - } - useEffect(() => { if (prefs.autoZap && !didZap && !isMine && !zapping) { const lnurl = getLNURL(); @@ -212,7 +197,6 @@ export default function NoteFooter(props: NoteFooterProps) { queueMicrotask(async () => { try { await fastZapInner(lnurl, prefs.defaultZapAmount, ev.pubkey, ev.id); - fastZapDonate(); } catch { // ignored } finally { @@ -457,6 +441,7 @@ export default function NoteFooter(props: NoteFooterProps) { author={author?.pubkey} target={getTargetName()} note={ev.id} + allocatePool={true} />
diff --git a/packages/app/src/Element/SendSats.tsx b/packages/app/src/Element/SendSats.tsx index 6228f7bb..60ac0949 100644 --- a/packages/app/src/Element/SendSats.tsx +++ b/packages/app/src/Element/SendSats.tsx @@ -16,6 +16,7 @@ import { useWallet } from "Wallet"; import useLogin from "Hooks/useLogin"; import { generateRandomKey } from "Login"; import { EventPublisher } from "System/EventPublisher"; +import { ZapPoolController } from "ZapPoolController"; import messages from "./messages"; @@ -36,6 +37,7 @@ export interface SendSatsProps { target?: string; note?: HexKey; author?: HexKey; + allocatePool?: boolean; } export default function SendSats(props: SendSatsProps) { @@ -194,9 +196,12 @@ export default function SendSats(props: SendSatsProps) { async function payWithWallet(invoice: LNURLInvoice) { try { - if (wallet?.isReady) { + if (wallet?.isReady()) { setPaying(true); const res = await wallet.payInvoice(invoice?.pr ?? ""); + if (props.allocatePool) { + ZapPoolController.allocate(amount); + } console.log(res); setSuccess(invoice?.successAction ?? {}); } diff --git a/packages/app/src/Pages/ZapPool.css b/packages/app/src/Pages/ZapPool.css new file mode 100644 index 00000000..611dfd07 --- /dev/null +++ b/packages/app/src/Pages/ZapPool.css @@ -0,0 +1,3 @@ +.zap-pool input[type="range"] { + width: 200px; +} diff --git a/packages/app/src/Pages/ZapPool.tsx b/packages/app/src/Pages/ZapPool.tsx new file mode 100644 index 00000000..7bc46e95 --- /dev/null +++ b/packages/app/src/Pages/ZapPool.tsx @@ -0,0 +1,185 @@ +import "./ZapPool.css"; + +import { useMemo, useSyncExternalStore } from "react"; +import { FormattedMessage, FormattedNumber } from "react-intl"; + +import { SnortPubKey } from "Const"; +import ProfilePreview from "Element/ProfilePreview"; +import useLogin from "Hooks/useLogin"; +import { System } from "System"; +import { UploaderServices } from "Upload"; +import { bech32ToHex, getRelayName, unwrap } from "Util"; +import { ZapPoolController, ZapPoolRecipient, ZapPoolRecipientType } from "ZapPoolController"; +import { useUserProfile } from "Hooks/useUserProfile"; +import AsyncButton from "Element/AsyncButton"; +import { useWallet } from "Wallet"; + +function ZapTarget({ target }: { target: ZapPoolRecipient }) { + const login = useLogin(); + const profile = useUserProfile(target.pubkey); + const hasAddress = profile?.lud16 || profile?.lud06; + const defaultZapMount = Math.ceil(login.preferences.defaultZapAmount * (target.split / 100)); + return ( + +
+ % ( + ) +
+ + ZapPoolController.set({ + ...target, + split: e.target.valueAsNumber, + }) + } + /> +
+ ) : ( + + ) + } + /> + ); +} + +export default function ZapPoolPage() { + const login = useLogin(); + const zapPool = useSyncExternalStore( + c => ZapPoolController.hook(c), + () => ZapPoolController.snapshot() + ); + const { wallet } = useWallet(); + + const relayConnections = useMemo(() => { + return [...System.Sockets.values()] + .map(a => { + if (a.Info?.pubkey) { + return { + address: a.Address, + pubkey: a.Info.pubkey, + }; + } + }) + .filter(a => a !== undefined) + .map(unwrap); + }, [login.relays]); + + const sumPending = zapPool.reduce((acc, v) => acc + v.sum, 0); + return ( +
+

+ +

+

+ +

+

+ +

+

+ + + + ), + }} + /> +

+

+ + + + ), + nOut: ( + + + + ), + }} + /> +

+

+ + + + ), + }} + /> +

+

+ {wallet && ( + ZapPoolController.payout(wallet)}> + + + )} +

+
+ b.pubkey === bech32ToHex(SnortPubKey) && b.type === ZapPoolRecipientType.Generic) ?? { + type: ZapPoolRecipientType.Generic, + pubkey: bech32ToHex(SnortPubKey), + split: 0, + sum: 0, + } + } + /> +
+

+ +

+ {relayConnections.map(a => ( +
+

{getRelayName(a.address)}

+ b.pubkey === a.pubkey && b.type === ZapPoolRecipientType.Relay) ?? { + type: ZapPoolRecipientType.Relay, + pubkey: a.pubkey, + split: 0, + sum: 0, + } + } + /> +
+ ))} +

+ +

+ {UploaderServices.map(a => ( +
+

{a.name}

+ b.pubkey === a.owner && b.type === ZapPoolRecipientType.FileHost) ?? { + type: ZapPoolRecipientType.FileHost, + pubkey: a.owner, + split: 0, + sum: 0, + } + } + /> +
+ ))} +
+ ); +} diff --git a/packages/app/src/Pages/settings/Index.tsx b/packages/app/src/Pages/settings/Index.tsx index 7ccc5d02..7c929d41 100644 --- a/packages/app/src/Pages/settings/Index.tsx +++ b/packages/app/src/Pages/settings/Index.tsx @@ -83,7 +83,11 @@ const SettingsIndex = () => { - +
navigate("/zap-pool")}> + + + +
diff --git a/packages/app/src/Upload/index.ts b/packages/app/src/Upload/index.ts index a93b67ec..9f286009 100644 --- a/packages/app/src/Upload/index.ts +++ b/packages/app/src/Upload/index.ts @@ -4,6 +4,8 @@ import { RawEvent } from "@snort/nostr"; import NostrBuild from "Upload/NostrBuild"; import VoidCat from "Upload/VoidCat"; import NostrImg from "Upload/NostrImg"; +import { KieranPubKey } from "Const"; +import { bech32ToHex } from "Util"; export interface UploadResult { url?: string; @@ -15,6 +17,24 @@ export interface UploadResult { header?: RawEvent; } +/** + * List of supported upload services and their owners on nostr + */ +export const UploaderServices = [ + { + name: "void.cat", + owner: bech32ToHex(KieranPubKey), + }, + { + name: "nostr.build", + owner: bech32ToHex("npub1nxy4qpqnld6kmpphjykvx2lqwvxmuxluddwjamm4nc29ds3elyzsm5avr7"), + }, + { + name: "nostrimg.com", + owner: bech32ToHex("npub1xv6axulxcx6mce5mfvfzpsy89r4gee3zuknulm45cqqpmyw7680q5pxea6"), + }, +]; + export interface Uploader { upload: (f: File | Blob, filename: string) => Promise; } diff --git a/packages/app/src/ZapPoolController.ts b/packages/app/src/ZapPoolController.ts new file mode 100644 index 00000000..d02dc997 --- /dev/null +++ b/packages/app/src/ZapPoolController.ts @@ -0,0 +1,124 @@ +import { UserCache } from "Cache"; +import ExternalStore from "ExternalStore"; +import { LNURL } from "LNURL"; +import { LNWallet, WalletInvoiceState } from "Wallet"; + +export enum ZapPoolRecipientType { + Generic = 0, + Relay = 1, + FileHost = 2, +} + +export interface ZapPoolRecipient { + type: ZapPoolRecipientType; + pubkey: string; + split: number; + sum: number; +} + +class ZapPool extends ExternalStore> { + #store: Map; + #isPayoutInProgress = false; + + constructor() { + super(); + this.#store = new Map(); + this.#load(); + } + + async payout(wallet: LNWallet) { + if (this.#isPayoutInProgress) { + throw new Error("Payout already in progress"); + } + this.#isPayoutInProgress = true; + for (const x of this.#store.values()) { + if (x.sum === 0) continue; + try { + const profile = await UserCache.get(x.pubkey); + if (!profile) { + throw new Error(`Failed to get profile for ${x.pubkey}`); + } + const svc = new LNURL(profile.lud16 || profile.lud06 || ""); + await svc.load(); + const amtSend = x.sum; + const invoice = await svc.getInvoice(amtSend, `SnortZapPool: ${x.split}%`); + if (invoice.pr) { + const result = await wallet.payInvoice(invoice.pr); + if (result.state === WalletInvoiceState.Paid) { + x.sum -= amtSend; + } else { + throw new Error("Payment failed"); + } + } else { + throw new Error(invoice.reason ?? "Failed to get invoice"); + } + } catch (e) { + console.error(e); + } + } + this.#save(); + this.notifyChange(); + this.#isPayoutInProgress = false; + } + + calcAllocation(n: number) { + let res = 0; + for (const x of this.#store.values()) { + res += Math.ceil(n * (x.split / 100)); + } + return res; + } + + allocate(n: number) { + if (this.#isPayoutInProgress) { + throw new Error("Payout is in progress, cannot allocate to pool"); + } + for (const x of this.#store.values()) { + x.sum += Math.ceil(n * (x.split / 100)); + } + this.#save(); + this.notifyChange(); + } + + getOrDefault(rcpt: ZapPoolRecipient) { + const k = this.#key(rcpt); + if (this.#store.has(k)) { + return { ...this.#store.get(k) }; + } + return rcpt; + } + + set(rcpt: ZapPoolRecipient) { + const k = this.#key(rcpt); + // delete entry if split is 0 and sum is 0 + if (rcpt.split === 0 && rcpt.sum === 0 && this.#store.has(k)) { + this.#store.delete(k); + } else { + this.#store.set(k, rcpt); + } + this.#save(); + this.notifyChange(); + } + + #key(rcpt: ZapPoolRecipient) { + return `${rcpt.pubkey}-${rcpt.type}`; + } + + #save() { + self.localStorage.setItem("zap-pool", JSON.stringify(this.takeSnapshot())); + } + + #load() { + const existing = self.localStorage.getItem("zap-pool"); + if (existing) { + const arr = JSON.parse(existing) as Array; + this.#store = new Map(arr.map(a => [`${a.pubkey}-${a.type}`, a])); + } + } + + takeSnapshot(): ZapPoolRecipient[] { + return [...this.#store.values()]; + } +} + +export const ZapPoolController = new ZapPool(); diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 788af705..2268c1fd 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -30,7 +30,7 @@ import { WalletRoutes } from "Pages/WalletPage"; import NostrLinkHandler from "Pages/NostrLinkHandler"; import Thread from "Element/Thread"; import { SubscribeRoutes } from "Pages/subscribe"; -import Discover from "Pages/Discover"; +import ZapPoolPage from "Pages/ZapPool"; /** * HTTP query provider @@ -94,6 +94,10 @@ export const router = createBrowserRouter([ path: "/search/:keyword?", element: , }, + { + path: "/zap-pool", + element: , + }, ...NewUserRoutes, ...WalletRoutes, ...SubscribeRoutes, -- 2.45.2 From 6856527a5199d3f2b1b96d2e4f5b9d0579ab04f1 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 16 May 2023 22:33:38 +0100 Subject: [PATCH 2/4] extract lang --- packages/app/src/lang.json | 43 ++++++++++++++++++++++++--- packages/app/src/translations/en.json | 16 ++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index b04a7399..dd7ce4ec 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -2,6 +2,9 @@ "+D82kt": { "defaultMessage": "Are you sure you want to repost: {id}" }, + "+PzQ9Y": { + "defaultMessage": "Payout Now" + }, "+aZY2h": { "defaultMessage": "Zap Type" }, @@ -271,6 +274,9 @@ "C81/uG": { "defaultMessage": "Logout" }, + "C8HhVE": { + "defaultMessage": "Suggested Follows" + }, "CHTbO3": { "defaultMessage": "Failed to load invoice" }, @@ -280,6 +286,9 @@ "CmZ9ls": { "defaultMessage": "{n} Muted" }, + "CsCUYo": { + "defaultMessage": "{n} sats" + }, "Cu/K85": { "defaultMessage": "Translated from {lang}" }, @@ -431,6 +440,9 @@ "JHEHCk": { "defaultMessage": "Zaps ({n})" }, + "JPFYIM": { + "defaultMessage": "No lightning address" + }, "JXtsQW": { "defaultMessage": "Fast Zap Donation" }, @@ -476,6 +488,9 @@ "LgbKvU": { "defaultMessage": "Comment" }, + "Lu5/Bj": { + "defaultMessage": "Open on Zapstr" + }, "M3Oirc": { "defaultMessage": "Debug Menus" }, @@ -570,6 +585,9 @@ "QDFTjG": { "defaultMessage": "{n} Relays" }, + "QWhotP": { + "defaultMessage": "Zap Pool only works if you use one of the supported wallet connections (WebLN, LNC, LNDHub or Nostr Wallet Connect)" + }, "QawghE": { "defaultMessage": "You can change your username at any point." }, @@ -577,6 +595,9 @@ "defaultMessage": "Art by {name}", "description": "Artwork attribution label" }, + "Qxv0B2": { + "defaultMessage": "You currently have {number} sats in your zap pool." + }, "R/6nsx": { "defaultMessage": "Subscription" }, @@ -699,9 +720,15 @@ "X7xU8J": { "defaultMessage": "nsec, npub, nip-05, hex, mnemonic" }, + "XICsE8": { + "defaultMessage": "File hosts" + }, "XgWvGA": { "defaultMessage": "Reactions" }, + "Xopqkl": { + "defaultMessage": "Your default zap amount is {number} sats, example values are calculated from this." + }, "XrSk2j": { "defaultMessage": "Redeem", "description": "Button: Redeem Cashu token" @@ -770,6 +797,9 @@ "c3g2hL": { "defaultMessage": "Broadcast Again" }, + "cE4Hfw": { + "defaultMessage": "Discover" + }, "cPIKU2": { "defaultMessage": "Following" }, @@ -817,6 +847,9 @@ "eR3YIn": { "defaultMessage": "Posts" }, + "eSzf2G": { + "defaultMessage": "A single zap of {nIn} sats will allocate {nOut} sats to the zap pool." + }, "fOksnD": { "defaultMessage": "Can't vote because LNURL service does not support zaps" }, @@ -877,6 +910,9 @@ "hniz8Z": { "defaultMessage": "here" }, + "i/dBAR": { + "defaultMessage": "Zap Pool" + }, "iCqGww": { "defaultMessage": "Reactions ({n})" }, @@ -1177,6 +1213,9 @@ "wvFw6Y": { "defaultMessage": "Hey, it looks like you dont have a NIP-05 handle yet, you should get one! Check out {link}" }, + "x/Fx2P": { + "defaultMessage": "Fund the services that you use by splitting a portion of all your zaps into a pool of funds!" + }, "x/q8d5": { "defaultMessage": "This note has been marked as sensitive, click here to reveal" }, @@ -1189,10 +1228,6 @@ "xJ9n2N": { "defaultMessage": "Your public key" }, - "xKdNPm": { - "defaultMessage": "Send", - "description": "Send DM button" - }, "xKflGN": { "defaultMessage": "{username}''s Follows on Nostr" }, diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index 22c9f91d..f4112f4f 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -1,5 +1,6 @@ { "+D82kt": "Are you sure you want to repost: {id}", + "+PzQ9Y": "Payout Now", "+aZY2h": "Zap Type", "+vA//S": "Logins", "+vIQlC": "Please make sure to save the following password in order to manage your handle in the future", @@ -88,9 +89,11 @@ "BcGMo+": "Notes hold text content, the most popular usage of these notes is to store \"tweet like\" messages.", "C5xzTC": "Premium", "C81/uG": "Logout", + "C8HhVE": "Suggested Follows", "CHTbO3": "Failed to load invoice", "CVWeJ6": "Trending People", "CmZ9ls": "{n} Muted", + "CsCUYo": "{n} sats", "Cu/K85": "Translated from {lang}", "D+KzKd": "Automatically zap every note when loaded", "D3idYv": "Settings", @@ -141,6 +144,7 @@ "J+dIsA": "Subscriptions", "JCIgkj": "Username", "JHEHCk": "Zaps ({n})", + "JPFYIM": "No lightning address", "JXtsQW": "Fast Zap Donation", "JkLHGw": "Website", "JymXbw": "Private Key", @@ -156,6 +160,7 @@ "LF5kYT": "Other Connections", "LXxsbk": "Anonymous", "LgbKvU": "Comment", + "Lu5/Bj": "Open on Zapstr", "M3Oirc": "Debug Menus", "MBAYRO": "Shows \"Copy ID\" and \"Copy Event JSON\" in the context menu on each message", "MI2jkA": "Not available:", @@ -187,8 +192,10 @@ "Pe0ogR": "Theme", "PrsIg7": "Reactions will be shown on every page, if disabled no reactions will be shown", "QDFTjG": "{n} Relays", + "QWhotP": "Zap Pool only works if you use one of the supported wallet connections (WebLN, LNC, LNDHub or Nostr Wallet Connect)", "QawghE": "You can change your username at any point.", "QxCuTo": "Art by {name}", + "Qxv0B2": "You currently have {number} sats in your zap pool.", "R/6nsx": "Subscription", "R1fEdZ": "Forward Zaps", "R2OqnW": "Delete Account", @@ -228,7 +235,9 @@ "WONP5O": "Find your twitter follows on nostr (Data provided by {provider})", "WxthCV": "e.g. Jack", "X7xU8J": "nsec, npub, nip-05, hex, mnemonic", + "XICsE8": "File hosts", "XgWvGA": "Reactions", + "Xopqkl": "Your default zap amount is {number} sats, example values are calculated from this.", "XrSk2j": "Redeem", "XzF0aC": "Key manager extensions are more secure and allow you to easily login to any Nostr client, here are some well known extensions:", "Y31HTH": "Help fund the development of Snort", @@ -251,6 +260,7 @@ "c+oiJe": "Install Extension", "c35bj2": "If you have an enquiry about your NIP-05 order please DM {link}", "c3g2hL": "Broadcast Again", + "cE4Hfw": "Discover", "cPIKU2": "Following", "cQfLWb": "URL..", "cWx9t8": "Mute all", @@ -266,6 +276,7 @@ "eHAneD": "Reaction emoji", "eJj8HD": "Get Verified", "eR3YIn": "Posts", + "eSzf2G": "A single zap of {nIn} sats will allocate {nOut} sats to the zap pool.", "fOksnD": "Can't vote because LNURL service does not support zaps", "fWZYP5": "Pinned", "filwqD": "Read", @@ -286,6 +297,7 @@ "hY4lzx": "Supports", "hicxcO": "Show replies", "hniz8Z": "here", + "i/dBAR": "Zap Pool", "iCqGww": "Reactions ({n})", "iDGAbc": "Get a Snort identifier", "iEoXYx": "DeepL translations", @@ -384,11 +396,11 @@ "wqyN/i": "Find out more info about {service} at {link}", "wtLjP6": "Copy ID", "wvFw6Y": "Hey, it looks like you dont have a NIP-05 handle yet, you should get one! Check out {link}", + "x/Fx2P": "Fund the services that you use by splitting a portion of all your zaps into a pool of funds!", "x/q8d5": "This note has been marked as sensitive, click here to reveal", "x82IOl": "Mute", "xIoGG9": "Go to", "xJ9n2N": "Your public key", - "xKdNPm": "Send", "xKflGN": "{username}''s Follows on Nostr", "xQtL3v": "Unlock", "xbVgIm": "Automatically load media", @@ -404,4 +416,4 @@ "zjJZBd": "You're ready!", "zonsdq": "Failed to load LNURL service", "zvCDao": "Automatically show latest notes" -} +} \ No newline at end of file -- 2.45.2 From 3a8125f0bb172a41428f633a87132f164194cfc2 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 16 May 2023 22:38:27 +0100 Subject: [PATCH 3/4] remove fastZapDonate --- packages/app/src/Login/Preferences.ts | 6 --- .../app/src/Pages/settings/Preferences.tsx | 37 ------------------- packages/app/src/lang.json | 12 ------ packages/app/src/translations/en.json | 4 -- 4 files changed, 59 deletions(-) diff --git a/packages/app/src/Login/Preferences.ts b/packages/app/src/Login/Preferences.ts index 774817e9..647dc90b 100644 --- a/packages/app/src/Login/Preferences.ts +++ b/packages/app/src/Login/Preferences.ts @@ -62,11 +62,6 @@ export interface UserPreferences { */ defaultZapAmount: number; - /** - * For each fast zap an additional X% will be sent to Snort donate address - */ - fastZapDonate: number; - /** * Auto-zap every post */ @@ -86,6 +81,5 @@ export const DefaultPreferences = { imgProxyConfig: DefaultImgProxy, defaultRootTab: "posts", defaultZapAmount: 50, - fastZapDonate: 0.0, autoZap: false, } as UserPreferences; diff --git a/packages/app/src/Pages/settings/Preferences.tsx b/packages/app/src/Pages/settings/Preferences.tsx index 9d98e2c6..47877023 100644 --- a/packages/app/src/Pages/settings/Preferences.tsx +++ b/packages/app/src/Pages/settings/Preferences.tsx @@ -1,7 +1,6 @@ import "./Preferences.css"; import { FormattedMessage, useIntl } from "react-intl"; -import { Link } from "react-router-dom"; import emoji from "@jukben/emoji-search"; import useLogin from "Hooks/useLogin"; @@ -165,42 +164,6 @@ const PreferencesPage = () => { />
-
-
-
- -
- - -
- - - - ), - }} - /> -
-
-
- updatePreferences(login, { ...perf, fastZapDonate: parseInt(e.target.value || "0") / 100 })} - /> -
-
diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index dd7ce4ec..19b2cce7 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -416,9 +416,6 @@ "IEwZvs": { "defaultMessage": "Are you sure you want to unpin this note?" }, - "IKOPx/": { - "defaultMessage": "Donate Page" - }, "INSqIz": { "defaultMessage": "Twitter username..." }, @@ -443,9 +440,6 @@ "JPFYIM": { "defaultMessage": "No lightning address" }, - "JXtsQW": { - "defaultMessage": "Fast Zap Donation" - }, "JkLHGw": { "defaultMessage": "Website" }, @@ -476,9 +470,6 @@ "KoFlZg": { "defaultMessage": "Enter mint URL" }, - "L7SZPr": { - "defaultMessage": "For more information about donations see {link}." - }, "LF5kYT": { "defaultMessage": "Other Connections" }, @@ -1133,9 +1124,6 @@ "rudscU": { "defaultMessage": "Failed to load follows, please try again later" }, - "sBz4+I": { - "defaultMessage": "For each Fast Zap an additional {percentage}% ({amount} sats) of the zap amount will be sent to the Snort developers as a donation." - }, "sWnYKw": { "defaultMessage": "Snort is designed to have a similar experience to Twitter." }, diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index f4112f4f..161660e3 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -136,7 +136,6 @@ "HbefNb": "Open Wallet", "IDjHJ6": "Thanks for using Snort, please consider donating if you can.", "IEwZvs": "Are you sure you want to unpin this note?", - "IKOPx/": "Donate Page", "INSqIz": "Twitter username...", "IUZC+0": "This means that nobody can modify notes which you have created and everybody can easily verify that the notes they are reading are created by you.", "Iwm6o2": "NIP-05 Shop", @@ -145,7 +144,6 @@ "JCIgkj": "Username", "JHEHCk": "Zaps ({n})", "JPFYIM": "No lightning address", - "JXtsQW": "Fast Zap Donation", "JkLHGw": "Website", "JymXbw": "Private Key", "K3r6DQ": "Delete", @@ -156,7 +154,6 @@ "KWuDfz": "I have saved my keys, continue", "KahimY": "Unknown event kind: {kind}", "KoFlZg": "Enter mint URL", - "L7SZPr": "For more information about donations see {link}.", "LF5kYT": "Other Connections", "LXxsbk": "Anonymous", "LgbKvU": "Comment", @@ -370,7 +367,6 @@ "rmdsT4": "{n} days", "rrfdTe": "This is the same technology which is used by Bitcoin and has been proven to be extremely secure.", "rudscU": "Failed to load follows, please try again later", - "sBz4+I": "For each Fast Zap an additional {percentage}% ({amount} sats) of the zap amount will be sent to the Snort developers as a donation.", "sWnYKw": "Snort is designed to have a similar experience to Twitter.", "svOoEH": "Name-squatting and impersonation is not allowed. Snort and our partners reserve the right to terminate your handle (not your account - nobody can take that away) for violating this rule.", "tOdNiY": "Dark", -- 2.45.2 From 154098b5dc863c7c53d613f1591b411dfe73dd3d Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 17 May 2023 10:17:26 +0100 Subject: [PATCH 4/4] toast --- packages/app/src/Pages/Layout.tsx | 2 + packages/app/src/Pages/ZapPool.tsx | 2 +- packages/app/src/Toaster.css | 12 ++++++ packages/app/src/Toaster.tsx | 53 +++++++++++++++++++++++++++ packages/app/src/ZapPoolController.ts | 11 ++++++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 packages/app/src/Toaster.css create mode 100644 packages/app/src/Toaster.tsx diff --git a/packages/app/src/Pages/Layout.tsx b/packages/app/src/Pages/Layout.tsx index bfe5a0a1..415299a8 100644 --- a/packages/app/src/Pages/Layout.tsx +++ b/packages/app/src/Pages/Layout.tsx @@ -25,6 +25,7 @@ import Avatar from "Element/Avatar"; import { useUserProfile } from "Hooks/useUserProfile"; import { profileLink } from "Util"; import { getCurrentSubscription } from "Subscription"; +import Toaster from "Toaster"; export default function Layout() { const location = useLocation(); @@ -170,6 +171,7 @@ export default function Layout() { )} {window.localStorage.getItem("debug") && } +
); } diff --git a/packages/app/src/Pages/ZapPool.tsx b/packages/app/src/Pages/ZapPool.tsx index 7bc46e95..c2fe1280 100644 --- a/packages/app/src/Pages/ZapPool.tsx +++ b/packages/app/src/Pages/ZapPool.tsx @@ -74,7 +74,7 @@ export default function ZapPoolPage() { const sumPending = zapPool.reduce((acc, v) => acc + v.sum, 0); return ( -
+

diff --git a/packages/app/src/Toaster.css b/packages/app/src/Toaster.css new file mode 100644 index 00000000..a5ada87d --- /dev/null +++ b/packages/app/src/Toaster.css @@ -0,0 +1,12 @@ +.toaster { + position: fixed; + bottom: 0; + left: 0; + display: flex; + flex-direction: column-reverse; + z-index: 9999; +} + +.toaster > .card { + border: 1px solid var(--gray); +} diff --git a/packages/app/src/Toaster.tsx b/packages/app/src/Toaster.tsx new file mode 100644 index 00000000..4b48c887 --- /dev/null +++ b/packages/app/src/Toaster.tsx @@ -0,0 +1,53 @@ +import ExternalStore from "ExternalStore"; +import Icon from "Icons/Icon"; +import { ReactNode, useSyncExternalStore } from "react"; +import { unixNow } from "Util"; + +import "./Toaster.css"; + +interface ToastNotification { + element: ReactNode; + expire?: number; + icon?: string; +} + +class ToasterSlots extends ExternalStore> { + #stack: Array = []; + #cleanup = setInterval(() => this.#eatToast(), 1000); + + push(n: ToastNotification) { + n.expire ??= unixNow() + 3; + this.#stack.push(n); + this.notifyChange(); + } + + takeSnapshot(): ToastNotification[] { + return [...this.#stack]; + } + + #eatToast() { + const now = unixNow(); + this.#stack = this.#stack.filter(a => (a.expire ?? 0) > now); + this.notifyChange(); + } +} + +export const Toastore = new ToasterSlots(); + +export default function Toaster() { + const toast = useSyncExternalStore( + c => Toastore.hook(c), + () => Toastore.snapshot() + ); + + return ( +
+ {toast.map(a => ( +
+ + {a.element} +
+ ))} +
+ ); +} diff --git a/packages/app/src/ZapPoolController.ts b/packages/app/src/ZapPoolController.ts index d02dc997..8ee240f2 100644 --- a/packages/app/src/ZapPoolController.ts +++ b/packages/app/src/ZapPoolController.ts @@ -1,6 +1,9 @@ import { UserCache } from "Cache"; +import { getDisplayName } from "Element/ProfileImage"; import ExternalStore from "ExternalStore"; import { LNURL } from "LNURL"; +import { Toastore } from "Toaster"; +import { unixNow } from "Util"; import { LNWallet, WalletInvoiceState } from "Wallet"; export enum ZapPoolRecipientType { @@ -46,6 +49,14 @@ class ZapPool extends ExternalStore> { const result = await wallet.payInvoice(invoice.pr); if (result.state === WalletInvoiceState.Paid) { x.sum -= amtSend; + Toastore.push({ + element: `Sent ${amtSend.toLocaleString()} sats to ${getDisplayName( + profile, + x.pubkey + )} from your zap pool`, + expire: unixNow() + 10, + icon: "zap", + }); } else { throw new Error("Payment failed"); } -- 2.45.2