feat: invite codes
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Kieran 2023-12-02 21:27:46 +00:00
parent 6f8e8eca0f
commit f26155ddd8
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
18 changed files with 5609 additions and 22 deletions

View File

@ -24,6 +24,7 @@
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"highlight.js": "^11.8.0", "highlight.js": "^11.8.0",
"light-bolt11-decoder": "^2.1.0", "light-bolt11-decoder": "^2.1.0",
"lottie-react": "^2.4.0",
"marked": "^9.1.0", "marked": "^9.1.0",
"marked-footnote": "^1.0.0", "marked-footnote": "^1.0.0",
"match-sorter": "^6.3.1", "match-sorter": "^6.3.1",

View File

@ -0,0 +1,51 @@
import SnortApi from "@/External/SnortApi";
import { getCurrentRefCode, getDisplayName } from "@/SnortUtils";
import { useUserProfile } from "@snort/system-react";
import Lottie from "lottie-react";
import { useState, useEffect } from "react";
import { FormattedMessage } from "react-intl";
import { Link, useNavigate } from "react-router-dom";
import Modal from "./Modal";
import Hugs from "@/hug.json";
const InviteModal = () => {
const [pubkey, setPubkey] = useState("");
const code = getCurrentRefCode();
const navigate = useNavigate();
useEffect(() => {
if (code) {
const api = new SnortApi();
api.getRefCodeInfo(code).then(a => setPubkey(a.pubkey));
}
}, []);
const profile = useUserProfile(pubkey);
if (!code) return;
function close() {
navigate("/");
}
return (
<Modal id="invite-modal" onClose={close}>
<div className="flex flex-col gap-4 items-center">
<Lottie animationData={Hugs} />
<p className="text-3xl font-semibold">
<FormattedMessage
defaultMessage="{name} invited you to {app}"
id="ZlmK/p"
values={{
name: <span className="text-primary">{getDisplayName(profile, pubkey)}</span>,
app: CONFIG.appNameCapitalized,
}}
/>
</p>
<Link to="/login/sign-up">
<button className="primary">
<FormattedMessage defaultMessage="Sign Up" id="39AHJm" />
</button>
</Link>
</div>
</Modal>
);
};
export default InviteModal;

View File

@ -79,7 +79,8 @@ export interface RelayDistance {
} }
export interface RefCodeResponse { export interface RefCodeResponse {
code: string code: string;
pubkey: string;
} }
export default class SnortApi { export default class SnortApi {
@ -143,6 +144,10 @@ export default class SnortApi {
return this.#getJsonAuthd<RefCodeResponse>("api/v1/referral", "GET"); return this.#getJsonAuthd<RefCodeResponse>("api/v1/referral", "GET");
} }
getRefCodeInfo(code: string) {
return this.#getJson<RefCodeResponse>(`api/v1/referral/${code}`, "GET");
}
async #getJsonAuthd<T>( async #getJsonAuthd<T>(
path: string, path: string,
method?: "GET" | string, method?: "GET" | string,

View File

@ -15,7 +15,7 @@ import * as utils from "@noble/curves/abstract/utils";
import { Blasters, SnortPubKey } from "@/Const"; import { Blasters, SnortPubKey } from "@/Const";
import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData, Newest } from "@/Login"; import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData, Newest } from "@/Login";
import { generateBip39Entropy, entropyToPrivateKey } from "@/nip6"; import { generateBip39Entropy, entropyToPrivateKey } from "@/nip6";
import { bech32ToHex, dedupeById, getCountry, sanitizeRelayUrl, unwrap } from "@/SnortUtils"; import { bech32ToHex, dedupeById, deleteRefCode, getCountry, sanitizeRelayUrl, unwrap } from "@/SnortUtils";
import { SubscriptionEvent } from "@/Subscription"; import { SubscriptionEvent } from "@/Subscription";
import { Chats, FollowsFeed, GiftsCache, Notifications } from "@/Cache"; import { Chats, FollowsFeed, GiftsCache, Notifications } from "@/Cache";
import { Nip7OsSigner } from "./Nip7OsSigner"; import { Nip7OsSigner } from "./Nip7OsSigner";
@ -70,6 +70,7 @@ export function logout(id: string) {
Notifications.clear(); Notifications.clear();
FollowsFeed.clear(); FollowsFeed.clear();
Chats.clear(); Chats.clear();
deleteRefCode();
} }
export function markNotificationsRead(state: LoginSession) { export function markNotificationsRead(state: LoginSession) {

View File

@ -14,7 +14,7 @@ export function ListFeedPage() {
const { data } = useEventFeed(link); const { data } = useEventFeed(link);
if (!data) return <PageSpinner />; if (!data) return <PageSpinner />;
if (data.kind !== EventKind.ContactList && data.kind !== EventKind.CategorizedPeople) { if (data.kind !== EventKind.ContactList && data.kind !== EventKind.FollowSet) {
return ( return (
<b> <b>
<FormattedMessage defaultMessage="Must be a contact list or pubkey list" id="vB3oQ/" /> <FormattedMessage defaultMessage="Must be a contact list or pubkey list" id="vB3oQ/" />

View File

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from "react"; import { lazy, useContext, useEffect, useState } from "react";
import { Link, Outlet, RouteObject, useParams } from "react-router-dom"; import { Link, Outlet, RouteObject, useParams } from "react-router-dom";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import { unixNow } from "@snort/shared"; import { unixNow } from "@snort/shared";
@ -7,7 +7,7 @@ import { SnortContext } from "@snort/system-react";
import Timeline from "@/Element/Feed/Timeline"; import Timeline from "@/Element/Feed/Timeline";
import { TimelineSubject } from "@/Feed/TimelineFeed"; import { TimelineSubject } from "@/Feed/TimelineFeed";
import { debounce, getRelayName, sha256 } from "@/SnortUtils"; import { debounce, getCurrentRefCode, getRelayName, sha256 } from "@/SnortUtils";
import useLogin from "@/Hooks/useLogin"; import useLogin from "@/Hooks/useLogin";
import Discover from "@/Pages/Discover"; import Discover from "@/Pages/Discover";
import TrendingUsers from "@/Element/Trending/TrendingUsers"; import TrendingUsers from "@/Element/Trending/TrendingUsers";
@ -20,6 +20,7 @@ import { RootTabs } from "@/Element/Feed/RootTabs";
import { DeckContext } from "@/Pages/DeckLayout"; import { DeckContext } from "@/Pages/DeckLayout";
import { TopicsPage } from "./TopicsPage"; import { TopicsPage } from "./TopicsPage";
import TrendingHashtags from "@/Element/Trending/TrendingHashtags"; import TrendingHashtags from "@/Element/Trending/TrendingHashtags";
const InviteModal = lazy(() => import("@/Element/Invite"));
import messages from "./messages"; import messages from "./messages";
@ -29,6 +30,7 @@ interface RelayOption {
} }
export default function RootPage() { export default function RootPage() {
const code = getCurrentRefCode();
return ( return (
<> <>
<div className="main-content p"> <div className="main-content p">
@ -37,6 +39,7 @@ export default function RootPage() {
<div className="main-content"> <div className="main-content">
<Outlet /> <Outlet />
</div> </div>
{code && <InviteModal />}
</> </>
); );
} }

View File

@ -13,6 +13,7 @@ import ModerationSettings from "@/Pages/settings/Moderation";
import { CacheSettings } from "./settings/Cache"; import { CacheSettings } from "./settings/Cache";
import messages from "./messages"; import messages from "./messages";
import { ReferralsPage } from "./settings/Referrals";
export default function SettingsPage() { export default function SettingsPage() {
const navigate = useNavigate(); const navigate = useNavigate();
@ -66,6 +67,10 @@ export const SettingsRoutes: RouteObject[] = [
path: "cache", path: "cache",
element: <CacheSettings />, element: <CacheSettings />,
}, },
{
path: "invite",
element: <ReferralsPage />,
},
...ManageHandleRoutes, ...ManageHandleRoutes,
...WalletSettingsRoutes, ...WalletSettingsRoutes,
], ],

View File

@ -1,6 +1,7 @@
import { FeedCache } from "@snort/shared"; import { FeedCache } from "@snort/shared";
import { import {
Chats, Chats,
FollowLists,
FollowsFeed, FollowsFeed,
GiftsCache, GiftsCache,
InteractionCache, InteractionCache,
@ -29,6 +30,7 @@ export function CacheSettings() {
<CacheDetails cache={PaymentsCache} name={<FormattedMessage defaultMessage="Payments" id="iYc3Ld" />} /> <CacheDetails cache={PaymentsCache} name={<FormattedMessage defaultMessage="Payments" id="iYc3Ld" />} />
<CacheDetails cache={InteractionCache} name={<FormattedMessage defaultMessage="Interactions" id="u+LyXc" />} /> <CacheDetails cache={InteractionCache} name={<FormattedMessage defaultMessage="Interactions" id="u+LyXc" />} />
<CacheDetails cache={GiftsCache} name={<FormattedMessage defaultMessage="Gift Wraps" id="fjAcWo" />} /> <CacheDetails cache={GiftsCache} name={<FormattedMessage defaultMessage="Gift Wraps" id="fjAcWo" />} />
<CacheDetails cache={FollowLists} name={<FormattedMessage defaultMessage="Social Graph" id="CzHZoc" />} />
</div> </div>
); );
} }

View File

@ -0,0 +1,42 @@
import Copy from "@/Element/Copy";
import SnortApi from "@/External/SnortApi";
import useEventPublisher from "@/Hooks/useEventPublisher";
import { useEffect, useState } from "react";
import { FormattedMessage } from "react-intl";
export function ReferralsPage() {
const [refCode, setRefCode] = useState("");
const { publisher } = useEventPublisher();
useEffect(() => {
const api = new SnortApi(undefined, publisher);
api.getRefCode().then(v => setRefCode(v.code));
}, [publisher]);
return (
<>
<h1>
<FormattedMessage defaultMessage="Invite your friends" id="l3H1EK" />
</h1>
<p>
<FormattedMessage
defaultMessage="Your referral code is {code}"
id="UxgyeY"
values={{
code: <span className="font-mono text-highlight select-all">{refCode}</span>,
}}
/>
</p>
<p>
<FormattedMessage
defaultMessage="Send this link to your friends and share the magic of the nostr."
id="Ml7+RS"
/>
</p>
<div className="border border-zinc-900 rounded-2xl px-3 py-2">
<Copy text={`https://${window.location.host}?ref=${refCode}`} maxSize={Number.MAX_VALUE} />
</div>
</>
);
}

View File

@ -33,27 +33,40 @@ const SettingsIndex = () => {
]; ];
const menuItems = [ const menuItems = [
{ icon: "profile", message: messages.Profile, path: "profile" }, { icon: "profile", message: <FormattedMessage defaultMessage="Profile" id="itPgxd" />, path: "profile" },
{ icon: "relay", message: messages.Relays, path: "relays" }, { icon: "relay", message: <FormattedMessage defaultMessage="Relays" id="RoOyAh" />, path: "relays" },
{ icon: "key", message: "Export Keys", id: "08zn6O", path: "keys" }, { icon: "key", message: <FormattedMessage defaultMessage="Export Keys" id="08zn6O" />, path: "keys" },
{ icon: "shield-tick", message: "Moderation", id: "wofVHy", path: "moderation" }, { icon: "shield-tick", message: <FormattedMessage defaultMessage="Moderation" id="wofVHy" />, path: "moderation" },
{ icon: "badge", message: "Nostr Address", id: "9pMqYs", path: "handle" }, { icon: "badge", message: <FormattedMessage defaultMessage="Nostr Address" id="9pMqYs" />, path: "handle" },
{ icon: "gear", message: messages.Preferences, path: "preferences" }, { icon: "gear", message: <FormattedMessage defaultMessage="Preferences" id="PCSt5T" />, path: "preferences" },
{ icon: "wallet", message: "Wallet", id: "3yk8fB", path: "wallet" }, { icon: "wallet", message: <FormattedMessage defaultMessage="Wallet" id="3yk8fB" />, path: "wallet" },
{ icon: "heart", message: messages.Donate, path: "/donate" }, { icon: "heart", message: <FormattedMessage defaultMessage="Donate" id="2IFGap" />, path: "/donate" },
{ icon: "hard-drive", message: "Cache", id: "DBiVK1", path: "cache" }, { icon: "hard-drive", message: <FormattedMessage defaultMessage="Cache" id="DBiVK1" />, path: "cache" },
{ icon: "link", message: <FormattedMessage defaultMessage="Invite" id="hYOE+U" />, path: "invite" },
]; ];
if (CONFIG.features.subscriptions) { if (CONFIG.features.subscriptions) {
menuItems.push({ icon: "diamond", message: "Subscription", id: "R/6nsx", path: "/subscribe/manage" }); menuItems.push({
icon: "diamond",
message: <FormattedMessage defaultMessage="Subscription" id="R/6nsx" />,
path: "/subscribe/manage",
});
} }
if (CONFIG.features.zapPool) { if (CONFIG.features.zapPool) {
menuItems.push({ icon: "piggy-bank", message: "Zap Pool", id: "i/dBAR", path: "/zap-pool" }); menuItems.push({
icon: "piggy-bank",
message: <FormattedMessage defaultMessage="Zap Pool" id="i/dBAR" />,
path: "/zap-pool",
});
} }
if (sub) { if (sub) {
menuItems.push({ icon: "code-circle", message: "Account Switcher", id: "7BX/yC", path: "accounts" }); menuItems.push({
icon: "code-circle",
message: <FormattedMessage defaultMessage="Accounts" id="FvanT6" />,
path: "accounts",
});
} }
const getNavLinkClass = ({ isActive }: { isActive: boolean }) => { const getNavLinkClass = ({ isActive }: { isActive: boolean }) => {
@ -64,10 +77,10 @@ const SettingsIndex = () => {
<div className="settings-nav"> <div className="settings-nav">
{!hideMenu && ( {!hideMenu && (
<div> <div>
{menuItems.map(({ icon, message, id, path }) => ( {menuItems.map(({ icon, message, path }) => (
<NavLink to={path} key={path} className={getNavLinkClass} end> <NavLink to={path} key={path} className={getNavLinkClass} end>
<Icon name={icon} size={24} /> <Icon name={icon} size={24} />
<FormattedMessage {...(id ? { defaultMessage: message, id } : message)} /> {message}
<Icon name="arrowFront" size={16} /> <Icon name="arrowFront" size={16} />
</NavLink> </NavLink>
))} ))}

View File

@ -12,6 +12,7 @@ import useEventPublisher from "@/Hooks/useEventPublisher";
import SnortApi, { SubscriptionError, SubscriptionErrorCode } from "@/External/SnortApi"; import SnortApi, { SubscriptionError, SubscriptionErrorCode } from "@/External/SnortApi";
import SendSats from "@/Element/SendSats"; import SendSats from "@/Element/SendSats";
import classNames from "classnames"; import classNames from "classnames";
import { getRefCode } from "@/SnortUtils";
export function mapPlanName(id: number) { export function mapPlanName(id: number) {
switch (id) { switch (id) {
@ -74,7 +75,8 @@ export function SubscribePage() {
async function subscribe(type: number) { async function subscribe(type: number) {
setError(undefined); setError(undefined);
try { try {
const rsp = await api.createSubscription(type); const ref = getRefCode();
const rsp = await api.createSubscription(type, ref);
setInvoice(rsp.pr); setInvoice(rsp.pr);
} catch (e) { } catch (e) {
if (e instanceof SubscriptionError) { if (e instanceof SubscriptionError) {

View File

@ -536,3 +536,29 @@ export function getCountry() {
export function trackEvent(event: string) { export function trackEvent(event: string) {
window.plausible?.(event); window.plausible?.(event);
} }
export function storeRefCode() {
const ref = getCurrentRefCode();
if (ref) {
window.localStorage.setItem("ref", ref);
}
}
export function getCurrentRefCode() {
if (window.location.search) {
const q = new URLSearchParams(window.location.search);
const ref = q.get("ref");
if (ref) {
return ref;
}
}
}
export function getRefCode() {
const r = window.localStorage.getItem("ref");
if (r) return r;
}
export function deleteRefCode() {
window.localStorage.removeItem("ref");
}

5365
packages/app/src/hug.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -50,7 +50,7 @@
--expired-invoice-gradient: linear-gradient(45deg, var(--gray-superdark) 50%, var(--gray), var(--gray-superdark)); --expired-invoice-gradient: linear-gradient(45deg, var(--gray-superdark) 50%, var(--gray), var(--gray-superdark));
--sub-bg: #111; --sub-bg: #111;
--btn-color: #fff; --btn-color: #fff;
--primary-gradient: linear-gradient(90deg, rgba(239, 150, 68, 1) 0%, rgba(123, 65, 246, 1) 100%); --primary-gradient: linear-gradient(90deg, #ef9644 0%, #7b41f6 100%);
--cashu-gradient: linear-gradient(90deg, #40b039, #adff2a); --cashu-gradient: linear-gradient(90deg, #40b039, #adff2a);
--pro: #ffdd65; --pro: #ffdd65;
} }
@ -153,6 +153,20 @@ a.ext {
color: var(--pro); color: var(--pro);
} }
.text-snort-gradient {
background: var(--snort-gradient);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.text-primary {
background: var(--primary-gradient);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.br { .br {
border-radius: 16px; border-radius: 16px;
} }

View File

@ -27,7 +27,7 @@ import { removeUndefined, throwIfOffline } from "@snort/shared";
import * as serviceWorkerRegistration from "@/serviceWorkerRegistration"; import * as serviceWorkerRegistration from "@/serviceWorkerRegistration";
import { IntlProvider } from "@/IntlProvider"; import { IntlProvider } from "@/IntlProvider";
import { getCountry, unwrap } from "@/SnortUtils"; import { getCountry, storeRefCode, unwrap } from "@/SnortUtils";
import Layout from "@/Pages/Layout"; import Layout from "@/Pages/Layout";
import ProfilePage from "@/Pages/Profile/ProfilePage"; import ProfilePage from "@/Pages/Profile/ProfilePage";
import { RootRoutes, RootTabRoutes } from "@/Pages/Root"; import { RootRoutes, RootTabRoutes } from "@/Pages/Root";
@ -183,6 +183,7 @@ serviceWorkerRegistration.register();
async function initSite() { async function initSite() {
console.debug(getCountry()); console.debug(getCountry());
storeRefCode();
if (hasWasm) { if (hasWasm) {
await wasmInit(WasmPath); await wasmInit(WasmPath);
} }

View File

@ -57,6 +57,9 @@
"00LcfG": { "00LcfG": {
"defaultMessage": "Load more" "defaultMessage": "Load more"
}, },
"08zn6O": {
"defaultMessage": "Export Keys"
},
"0Azlrb": { "0Azlrb": {
"defaultMessage": "Manage" "defaultMessage": "Manage"
}, },
@ -162,6 +165,9 @@
"3tVy+Z": { "3tVy+Z": {
"defaultMessage": "{n} Followers" "defaultMessage": "{n} Followers"
}, },
"3yk8fB": {
"defaultMessage": "Wallet"
},
"450Fty": { "450Fty": {
"defaultMessage": "None" "defaultMessage": "None"
}, },
@ -470,6 +476,9 @@
"FmXUJg": { "FmXUJg": {
"defaultMessage": "follows you" "defaultMessage": "follows you"
}, },
"FvanT6": {
"defaultMessage": "Accounts"
},
"G/yZLu": { "G/yZLu": {
"defaultMessage": "Remove" "defaultMessage": "Remove"
}, },
@ -656,6 +665,9 @@
"MiMipu": { "MiMipu": {
"defaultMessage": "Set as primary Nostr address (nip05)" "defaultMessage": "Set as primary Nostr address (nip05)"
}, },
"Ml7+RS": {
"defaultMessage": "Send this link to your friends and share the magic of the nostr."
},
"Mrpkot": { "Mrpkot": {
"defaultMessage": "Pay for subscription" "defaultMessage": "Pay for subscription"
}, },
@ -741,6 +753,9 @@
"Qxv0B2": { "Qxv0B2": {
"defaultMessage": "You currently have {number} sats in your zap pool." "defaultMessage": "You currently have {number} sats in your zap pool."
}, },
"R/6nsx": {
"defaultMessage": "Subscription"
},
"R81upa": { "R81upa": {
"defaultMessage": "People you follow" "defaultMessage": "People you follow"
}, },
@ -849,6 +864,9 @@
"UrKTqQ": { "UrKTqQ": {
"defaultMessage": "You have an active iris.to account" "defaultMessage": "You have an active iris.to account"
}, },
"UxgyeY": {
"defaultMessage": "Your referral code is {code}"
},
"VL900k": { "VL900k": {
"defaultMessage": "Recommended Relays" "defaultMessage": "Recommended Relays"
}, },
@ -940,6 +958,9 @@
"Zff6lu": { "Zff6lu": {
"defaultMessage": "Username iris.to/<b>{name}</b> is reserved for you!" "defaultMessage": "Username iris.to/<b>{name}</b> is reserved for you!"
}, },
"ZlmK/p": {
"defaultMessage": "{name} invited you to {app}"
},
"a5UPxh": { "a5UPxh": {
"defaultMessage": "Fund developers and platforms providing NIP-05 verification services" "defaultMessage": "Fund developers and platforms providing NIP-05 verification services"
}, },
@ -1140,6 +1161,9 @@
"hY4lzx": { "hY4lzx": {
"defaultMessage": "Supports" "defaultMessage": "Supports"
}, },
"hYOE+U": {
"defaultMessage": "Invite"
},
"ha8JKG": { "ha8JKG": {
"defaultMessage": "Show graph" "defaultMessage": "Show graph"
}, },
@ -1234,6 +1258,9 @@
"l+ikU1": { "l+ikU1": {
"defaultMessage": "Everything in {plan}" "defaultMessage": "Everything in {plan}"
}, },
"l3H1EK": {
"defaultMessage": "Invite your friends"
},
"lCILNz": { "lCILNz": {
"defaultMessage": "Buy Now" "defaultMessage": "Buy Now"
}, },

View File

@ -18,6 +18,7 @@
"/d6vEc": "Make your profile easier to find and share", "/d6vEc": "Make your profile easier to find and share",
"/n5KSF": "{n} ms", "/n5KSF": "{n} ms",
"00LcfG": "Load more", "00LcfG": "Load more",
"08zn6O": "Export Keys",
"0Azlrb": "Manage", "0Azlrb": "Manage",
"0BUTMv": "Search...", "0BUTMv": "Search...",
"0HFX0T": "Use Exact Location", "0HFX0T": "Use Exact Location",
@ -53,6 +54,7 @@
"3qnJlS": "You are voting with {amount} sats", "3qnJlS": "You are voting with {amount} sats",
"3t3kok": "{n,plural,=1{{n} new note} other{{n} new notes}}", "3t3kok": "{n,plural,=1{{n} new note} other{{n} new notes}}",
"3tVy+Z": "{n} Followers", "3tVy+Z": "{n} Followers",
"3yk8fB": "Wallet",
"450Fty": "None", "450Fty": "None",
"47FYwb": "Cancel", "47FYwb": "Cancel",
"4IPzdn": "Primary Developers", "4IPzdn": "Primary Developers",
@ -155,6 +157,7 @@
"FdhSU2": "Claim Now", "FdhSU2": "Claim Now",
"FfYsOb": "An error has occured!", "FfYsOb": "An error has occured!",
"FmXUJg": "follows you", "FmXUJg": "follows you",
"FvanT6": "Accounts",
"G/yZLu": "Remove", "G/yZLu": "Remove",
"G1BGCg": "Select Wallet", "G1BGCg": "Select Wallet",
"GFOoEE": "Salt", "GFOoEE": "Salt",
@ -216,6 +219,7 @@
"MP54GY": "Wallet password", "MP54GY": "Wallet password",
"MWTx65": "Default Page", "MWTx65": "Default Page",
"MiMipu": "Set as primary Nostr address (nip05)", "MiMipu": "Set as primary Nostr address (nip05)",
"Ml7+RS": "Send this link to your friends and share the magic of the nostr.",
"Mrpkot": "Pay for subscription", "Mrpkot": "Pay for subscription",
"MuVeKe": "Buy nostr address", "MuVeKe": "Buy nostr address",
"MzRYWH": "Buying {item}", "MzRYWH": "Buying {item}",
@ -244,6 +248,7 @@
"QDFTjG": "{n} Relays", "QDFTjG": "{n} Relays",
"QWhotP": "Zap Pool only works if you use one of the supported wallet connections (WebLN, LNC, LNDHub or Nostr Wallet Connect)", "QWhotP": "Zap Pool only works if you use one of the supported wallet connections (WebLN, LNC, LNDHub or Nostr Wallet Connect)",
"Qxv0B2": "You currently have {number} sats in your zap pool.", "Qxv0B2": "You currently have {number} sats in your zap pool.",
"R/6nsx": "Subscription",
"R81upa": "People you follow", "R81upa": "People you follow",
"RSr2uB": "Username must only contain lowercase letters and numbers", "RSr2uB": "Username must only contain lowercase letters and numbers",
"RahCRH": "Expired", "RahCRH": "Expired",
@ -279,6 +284,7 @@
"Ub+AGc": "Sign In", "Ub+AGc": "Sign In",
"Up5U7K": "Block", "Up5U7K": "Block",
"UrKTqQ": "You have an active iris.to account", "UrKTqQ": "You have an active iris.to account",
"UxgyeY": "Your referral code is {code}",
"VL900k": "Recommended Relays", "VL900k": "Recommended Relays",
"VN0+Fz": "Balance: {amount} sats", "VN0+Fz": "Balance: {amount} sats",
"VOjC1i": "Pick which upload service you want to upload attachments to", "VOjC1i": "Pick which upload service you want to upload attachments to",
@ -309,6 +315,7 @@
"ZLmyG9": "Contributors", "ZLmyG9": "Contributors",
"ZS+jRE": "Send zap splits to", "ZS+jRE": "Send zap splits to",
"Zff6lu": "Username iris.to/<b>{name}</b> is reserved for you!", "Zff6lu": "Username iris.to/<b>{name}</b> is reserved for you!",
"ZlmK/p": "{name} invited you to {app}",
"a5UPxh": "Fund developers and platforms providing NIP-05 verification services", "a5UPxh": "Fund developers and platforms providing NIP-05 verification services",
"a7TDNm": "Notes will stream in real time into global and notes tab", "a7TDNm": "Notes will stream in real time into global and notes tab",
"aHje0o": "Name or nym", "aHje0o": "Name or nym",
@ -375,6 +382,7 @@
"hMzcSq": "Messages", "hMzcSq": "Messages",
"hRTfTR": "PRO", "hRTfTR": "PRO",
"hY4lzx": "Supports", "hY4lzx": "Supports",
"hYOE+U": "Invite",
"ha8JKG": "Show graph", "ha8JKG": "Show graph",
"hicxcO": "Show replies", "hicxcO": "Show replies",
"hmZ3Bz": "Media", "hmZ3Bz": "Media",
@ -406,6 +414,7 @@
"kc79d3": "Topics", "kc79d3": "Topics",
"kuPHYE": "{n,plural,=0{{name} liked} other{{name} & {n} others liked}}", "kuPHYE": "{n,plural,=0{{name} liked} other{{name} & {n} others liked}}",
"l+ikU1": "Everything in {plan}", "l+ikU1": "Everything in {plan}",
"l3H1EK": "Invite your friends",
"lCILNz": "Buy Now", "lCILNz": "Buy Now",
"lD3+8a": "Pay", "lD3+8a": "Pay",
"lPWASz": "Snort nostr address", "lPWASz": "Snort nostr address",

View File

@ -2924,6 +2924,7 @@ __metadata:
fuse.js: ^7.0.0 fuse.js: ^7.0.0
highlight.js: ^11.8.0 highlight.js: ^11.8.0
light-bolt11-decoder: ^2.1.0 light-bolt11-decoder: ^2.1.0
lottie-react: ^2.4.0
marked: ^9.1.0 marked: ^9.1.0
marked-footnote: ^1.0.0 marked-footnote: ^1.0.0
match-sorter: ^6.3.1 match-sorter: ^6.3.1
@ -7799,6 +7800,25 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"lottie-react@npm:^2.4.0":
version: 2.4.0
resolution: "lottie-react@npm:2.4.0"
dependencies:
lottie-web: ^5.10.2
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
checksum: e9ea4a89be90a29bde4a83956f76a80d1f8031882f18ea38ef5271d2aafd8e68348ae6297f185ed85b149ca4896fe33aee7faf9780b88a1b289b8e146f477448
languageName: node
linkType: hard
"lottie-web@npm:^5.10.2":
version: 5.12.2
resolution: "lottie-web@npm:5.12.2"
checksum: af5bc3bc405fd760de8b17a36158d5a8c3e8c586c711d0c63681ddf056b65bc6b54ea36b1fcad782fb02dbe12e696a40e0ba72daf41b8f10ab5b5d1113793636
languageName: node
linkType: hard
"loupe@npm:^2.3.6": "loupe@npm:^2.3.6":
version: 2.3.7 version: 2.3.7
resolution: "loupe@npm:2.3.7" resolution: "loupe@npm:2.3.7"