Filter-out non-sats prices

This commit is contained in:
Bojan Mojsilovic 2024-06-07 16:24:16 +02:00
parent bd09ed7668
commit de05c5d0a1
7 changed files with 199 additions and 76 deletions

View File

@ -19,13 +19,7 @@ import Avatar from '../Avatar/Avatar';
import ButtonPrimary from '../Buttons/ButtonPrimary';
import ButtonSecondary from '../Buttons/ButtonSecondary';
import Loader from '../Loader/Loader';
import { NoteReactionsState } from '../Note/Note';
import NoteContextTrigger from '../Note/NoteContextTrigger';
import ArticleFooter from '../Note/NoteFooter/ArticleFooter';
import NoteFooter from '../Note/NoteFooter/NoteFooter';
import NoteTopZaps from '../Note/NoteTopZaps';
import NoteTopZapsCompact from '../Note/NoteTopZapsCompact';
import { Tier } from '../SubscribeToAuthorModal/SubscribeToAuthorModal';
import { Tier, TierCost } from '../SubscribeToAuthorModal/SubscribeToAuthorModal';
import VerificationCheck from '../VerificationCheck/VerificationCheck';
import styles from './AuthorSubscribe.module.scss';
@ -59,10 +53,10 @@ const AuthoreSubscribe: Component<{
getAuthorData();
});
const doSubscription = async (tier: Tier) => {
const doSubscription = async (tier: Tier, cost: TierCost) => {
const a = author();
if (!a || !account) return;
if (!a || !account || !cost) return;
const subEvent = {
kind: Kind.Subscribe,
@ -71,7 +65,7 @@ const AuthoreSubscribe: Component<{
tags: [
['p', a.pubkey],
['e', tier.id],
['amount', tier.costs[0].amount, tier.costs[0].unit, tier.costs[0].duration],
['amount', cost.amount, cost.unit, cost.cadence],
['event', JSON.stringify(tier.event)],
// Copy any zap splits
...(tier.event.tags?.filter(t => t[0] === 'zap') || []),

View File

@ -116,6 +116,9 @@ const ReadsSidebar: Component< { id?: string } > = (props) => {
// const author = '88cc134b1a65f54ef48acc1df3665063d3ea45f04eab8af4646e561c5ae99079';
// setFeautredAuthor(() => author);
// const author = 'fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52';
// setFeautredAuthor(() => author);
setFeautredAuthor(() => authors[Math.floor(Math.random() * authors.length)]);
},
onEose: () => {

View File

@ -103,27 +103,6 @@
line-height: 20px;
}
.cost {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.amount {
color: var(--text-primary);
font-size: 20px;
font-weight: 600;
line-height: 20px;
}
.duration {
color: var(--text-secondary);
font-size: 16px;
font-weight: 400;
line-height: 16px;
}
}
.content {
display: flex;
border-top: 1px solid var(--subtile-devider);
@ -219,3 +198,78 @@
-webkit-mask: url(../../assets/icons/explore/zaps_hollow.svg) no-repeat 2px 0 / 19px 22px;
mask: url(../../assets/icons/explore/zaps_hollow.svg) no-repeat 2px 0 / 19px 22px;
}
.selectCosts {
}
.selectTrigger {
background-color: var(--background-input);
width: 360px;
border: none;
outline: none;
margin: 0;
padding: 0;
.selectValue {
.cost {
.duration {
display: flex;
gap: 6px;
.chevIcon {
width: 6px;
height: 16px;
background-color: var(--text-tertiary);
-webkit-mask: url(../../assets/icons/chevron_right.svg) no-repeat 0 / 100%;
mask: url(../../assets/icons/chevron_right.svg) no-repeat 0 / 100%;
rotate: 90deg;
}
}
}
}
}
.selectContent {
background-color: var(--background-sheet);
z-index: 9999;
width: 390px;
.selectListbox {
border: 1px solid var(--subtile-devider);
border-radius: 8px;
background-color: var(--background-sheet);
padding: 0;
.cost {
border-radius: 8px;
padding-block: 12px;
padding-inline: 12px;
cursor: pointer;
&:hover {
background-color: var(--background-input);
}
}
}
}
.cost {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.amount {
color: var(--text-primary);
font-size: 20px;
font-weight: 600;
line-height: 20px;
}
.duration {
color: var(--text-secondary);
font-size: 16px;
font-weight: 400;
line-height: 16px;
}
}

View File

@ -1,18 +1,12 @@
import { useIntl } from '@cookbook/solid-intl';
// @ts-ignore
import { decode } from 'light-bolt11-decoder';
import { Component, createEffect, For, Show } from 'solid-js';
import { createStore, reconcile } from 'solid-js/store';
import { emptyInvoice, Kind } from '../../constants';
import { date, dateFuture } from '../../lib/dates';
import { createStore } from 'solid-js/store';
import { Kind } from '../../constants';
import { hookForDev } from '../../lib/devTools';
import { humanizeNumber } from '../../lib/stats';
import { cashuInvoice } from '../../translations';
import { LnbcInvoice, NostrTier, PrimalUser } from '../../types/primal';
import { NostrTier, PrimalUser } from '../../types/primal';
import ButtonPrimary from '../Buttons/ButtonPrimary';
import Modal from '../Modal/Modal';
import QrCode from '../QrCode/QrCode';
import { getDecodedToken, Token } from "@cashu/cashu-ts";
import styles from './SubscribeToAuthorModal.module.scss';
import { userName } from '../../stores/profile';
@ -22,13 +16,22 @@ import { APP_ID } from '../../App';
import { subsTo } from '../../sockets';
import { getAuthorSubscriptionTiers } from '../../lib/feed';
import ButtonSecondary from '../Buttons/ButtonSecondary';
import { Select } from '@kobalte/core';
export type TierCost = {
amount: string,
unit: string,
cadence: string,
id: string,
}
export type Tier = {
title: string,
content: string,
id: string,
perks: string[],
costs: { amount: string, unit: string, duration: string}[],
costs: TierCost[],
activeCost: TierCost | undefined,
client: string,
event: NostrTier,
};
@ -36,20 +39,22 @@ export type Tier = {
export type TierStore = {
tiers: Tier[],
selectedTier: Tier | undefined,
selectedCost: TierCost | undefined,
}
export const payUnits = ['sats', 'msats', ''];
export const payUnits = ['sats', 'msat', ''];
const SubscribeToAuthorModal: Component<{
id?: string,
author: PrimalUser | undefined,
onClose: () => void,
onSubscribe: (tier: Tier) => void,
onSubscribe: (tier: Tier, cost: TierCost) => void,
}> = (props) => {
const [store, updateStore] = createStore<TierStore>({
tiers: [],
selectedTier: undefined,
selectedCost: undefined,
})
createEffect(() => {
@ -76,14 +81,23 @@ const SubscribeToAuthorModal: Component<{
if (content.kind === Kind.Tier) {
const t = content as NostrTier;
const costs = t.tags?.filter((t: string[]) => t[0] === 'amount').map((t: string[]) => (
{
amount: t[1],
unit: t[2],
cadence: t[3],
id: `${t[1]}_${t[2]}_${t[3]}`
})) || [];
const tier = {
title: (t.tags?.find((t: string[]) => t[0] === 'title') || [])[1] || t.content || '',
id: t.id || '',
content: t.content || '',
perks: t.tags?.filter((t: string[]) => t[0] === 'perk').map((t: string[]) => t[1]) || [],
costs: t.tags?.filter((t: string[]) => t[0] === 'amount').map((t: string[]) => ({ amount: t[1], unit: t[2], duration: t[3]})) || [],
costs,
client: (t.tags?.find((t: string[]) => t[0] === 'client') || [])[1] || t.content || '',
event: t,
activeCost: costs[0],
}
tiers.push(tier)
@ -94,7 +108,9 @@ const SubscribeToAuthorModal: Component<{
onEose: () => {
unsub();
updateStore('tiers', () => [...tiers]);
updateStore('selectedTier', () => ( tiers.length > 0 ? { ...tiers[0]} : undefined))
const tier: Tier | undefined = tiers.length > 0 ? Object.assign(tiers[0]) : undefined;
updateStore('selectedTier', () => ({ ...tier }));
updateStore('selectedCost', () => ({ ...tier?.activeCost }))
},
})
@ -102,19 +118,22 @@ const SubscribeToAuthorModal: Component<{
}
const selectTier = (tier: Tier) => {
updateStore('selectedTier', () => ({ ...tier }));
if (tier.id !== store.selectedTier?.id) {
updateStore('selectedTier', () => ({ ...tier }));
updateStore('selectedCost', (sc) => ({ ...costOptions(tier)[0] }) );
}
}
const isSelectedTier = (tier: Tier) => tier.id === store.selectedTier?.id;
// const costForTier = (tier: Tier) => {
// const costs = tier.costs.filter(c => payUnits.includes(c.unit));
const costOptions = (tier: Tier) => {
return tier.costs.filter(cost => payUnits.includes(cost.unit));
}
// costs.reduce((acc, c) => {
// return
// }, [])
// }
const displayCost = (cost: TierCost | undefined) => {
return `${cost?.unit === 'msat' ? Math.ceil(parseInt(cost?.amount || '0') / 1_000) : cost?.amount} sats`;
};
return (
<Modal open={props.author !== undefined} onClose={props.onClose}>
@ -139,20 +158,74 @@ const SubscribeToAuthorModal: Component<{
<div class={styles.body}>
<div class={styles.tiers}>
<For each={store.tiers}>
{tier => (
{(tier) => (
<button
class={`${styles.tier} ${isSelectedTier(tier) ? styles.selected : ''}`}
onClick={() => selectTier(tier)}
>
<div class={styles.title}>{tier.title}</div>
<div class={styles.cost}>
<div class={styles.amount}>
{tier.costs[0].amount} {tier.costs[0].unit}
</div>
<div class={styles.duration}>
{tier.costs[0].duration}
</div>
</div>
<Show
when={costOptions(tier).length > 1 && store.selectedTier?.id === tier.id}
fallback={<div class={styles.cost}>
<div class={styles.amount}>
{displayCost(costOptions(tier)[0])}
</div>
<div class={styles.duration}>
{costOptions(tier)[0].cadence}
</div>
</div>}
>
<Select.Root
class={styles.selectCosts}
options={costOptions(tier)}
optionValue="id"
value={store.selectedCost}
onChange={(cost) => {
// updateStore('tiers', index(), 'activeCost', () => ({ ...cost }));
// updateStore('selectedTier', 'activeCost', () => ({ ...cost }));
updateStore('selectedCost', () => ({ ...cost }));
}}
itemComponent={props => (
<Select.Item item={props.item} class={styles.cost}>
<div class={styles.amount}>
{displayCost(props.item.rawValue)}
</div>
<div class={styles.duration}>
{props.item.rawValue.cadence}
</div>
</Select.Item>
)}
>
<Select.Trigger class={styles.selectTrigger}>
<Select.Value class={styles.selectValue}>
{state => {
const cost = state.selectedOption() as TierCost;
return (
<div class={styles.cost}>
<div class={styles.amount}>
{displayCost(cost)}
</div>
<div class={styles.duration}>
<div>{cost?.cadence}</div>
<div class={styles.chevIcon}></div>
</div>
</div>
)
}}
</Select.Value>
</Select.Trigger>
<Select.Portal>
<Select.Content class={styles.selectContent}>
<Select.Listbox class={styles.selectListbox} />
</Select.Content>
</Select.Portal>
</Select.Root>
</Show>
<div class={styles.content}>
{tier.content}
</div>
@ -186,7 +259,7 @@ const SubscribeToAuthorModal: Component<{
<div class={styles.payAction}>
<ButtonPrimary
onClick={() => store.selectedTier && props.onSubscribe(store.selectedTier)}
onClick={() => store.selectedTier && store.selectedCost && props.onSubscribe(store.selectedTier, store.selectedCost)}
>
subscribe
</ButtonPrimary>

View File

@ -9,7 +9,7 @@ import {
} from "solid-js";
import { PrimalArticle, PrimalNote, PrimalUser, ZapOption } from "../types/primal";
import { CashuMint } from "@cashu/cashu-ts";
import { Tier } from "../components/SubscribeToAuthorModal/SubscribeToAuthorModal";
import { Tier, TierCost } from "../components/SubscribeToAuthorModal/SubscribeToAuthorModal";
import { Kind } from "../constants";
import { sendEvent } from "../lib/notes";
@ -86,7 +86,7 @@ export type AppContextStore = {
openConfirmModal: (confirmInfo: ConfirmInfo) => void,
closeConfirmModal: () => void,
getCashuMint: (url: string) => CashuMint | undefined,
openAuthorSubscribeModal: (author: PrimalUser | undefined, subscribeTo: (tier: Tier) => void) => void,
openAuthorSubscribeModal: (author: PrimalUser | undefined, subscribeTo: (tier: Tier, cost: TierCost) => void) => void,
closeAuthorSubscribeModal: () => void,
},
}
@ -230,7 +230,7 @@ export const AppProvider = (props: { children: JSXElement }) => {
return store.cashuMints.get(formatted);
};
const openAuthorSubscribeModal = (author: PrimalUser | undefined, subscribeTo: (tier: Tier) => void) => {
const openAuthorSubscribeModal = (author: PrimalUser | undefined, subscribeTo: (tier: Tier, cost: TierCost) => void) => {
if (!author) return;
updateStore('subscribeToAuthor', () => ({ ...author }));

View File

@ -48,7 +48,7 @@ import ArticleSidebar from "../components/HomeSidebar/ArticleSidebar";
import ReplyToNote from "../components/ReplyToNote/ReplyToNote";
import { sanitize } from "dompurify";
import { fetchNotes } from "../handleNotes";
import { Tier } from "../components/SubscribeToAuthorModal/SubscribeToAuthorModal";
import { Tier, TierCost } from "../components/SubscribeToAuthorModal/SubscribeToAuthorModal";
import ButtonPrimary from "../components/Buttons/ButtonPrimary";
import { zapSubscription } from "../lib/zap";
@ -359,7 +359,6 @@ const Longform: Component< { naddr: string } > = (props) => {
fetchArticle();
});
createEffect(() => {
if (store.article?.user) {
getTiers(store.article.user);
@ -391,10 +390,10 @@ const Longform: Component< { naddr: string } > = (props) => {
getAuthorSubscriptionTiers(author.pubkey, subId)
}
const doSubscription = async (tier: Tier) => {
const doSubscription = async (tier: Tier, cost: TierCost) => {
const a = store.article?.user;
if (!a || !account) return;
if (!a || !account || !cost) return;
const subEvent = {
kind: Kind.Subscribe,
@ -403,7 +402,7 @@ const Longform: Component< { naddr: string } > = (props) => {
tags: [
['p', a.pubkey],
['e', tier.id],
['amount', tier.costs[0].amount, tier.costs[0].unit, tier.costs[0].duration],
['amount', cost.amount, cost.unit, cost.cadence],
['event', JSON.stringify(tier.event)],
// Copy any zap splits
...(tier.event.tags?.filter(t => t[0] === 'zap') || []),

View File

@ -45,7 +45,7 @@ import ProfileQrCodeModal from '../components/ProfileQrCodeModal/ProfileQrCodeMo
import { CustomZapInfo, useAppContext } from '../contexts/AppContext';
import ProfileAbout from '../components/ProfileAbout/ProfileAbout';
import ButtonPrimary from '../components/Buttons/ButtonPrimary';
import { Tier } from '../components/SubscribeToAuthorModal/SubscribeToAuthorModal';
import { Tier, TierCost } from '../components/SubscribeToAuthorModal/SubscribeToAuthorModal';
import { Kind } from '../constants';
import { getAuthorSubscriptionTiers } from '../lib/feed';
import { zapSubscription } from '../lib/zap';
@ -544,13 +544,13 @@ const Profile: Component = () => {
},
})
getAuthorSubscriptionTiers(author.pubkey, subId)
getAuthorSubscriptionTiers(author.pubkey, subId);
}
const doSubscription = async (tier: Tier) => {
const doSubscription = async (tier: Tier, cost: TierCost) => {
const a = profile?.userProfile;
if (!a || !account) return;
if (!a || !account || !cost) return;
const subEvent = {
kind: Kind.Subscribe,
@ -559,7 +559,7 @@ const Profile: Component = () => {
tags: [
['p', a.pubkey],
['e', tier.id],
['amount', tier.costs[0].amount, tier.costs[0].unit, tier.costs[0].duration],
['amount', cost.amount, cost.unit, cost.cadence],
['event', JSON.stringify(tier.event)],
// Copy any zap splits
...(tier.event.tags?.filter(t => t[0] === 'zap') || []),