mirror of
https://github.com/PrimalHQ/primal-web-app.git
synced 2024-09-30 00:41:09 +00:00
Filter-out non-sats prices
This commit is contained in:
parent
bd09ed7668
commit
de05c5d0a1
@ -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') || []),
|
||||
|
@ -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: () => {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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 }));
|
||||
|
@ -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') || []),
|
||||
|
@ -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') || []),
|
||||
|
Loading…
Reference in New Issue
Block a user