problem: can't see how many products ar remaining

This commit is contained in:
gsovereignty
2024-08-16 17:32:40 +08:00
parent 4cb488ea68
commit 7d1d5e35c5
11 changed files with 163 additions and 69 deletions

View File

@@ -3,8 +3,17 @@
import { page } from '$app/stores';
import { Badge } from '@/components/ui/badge';
import Separator from '@/components/ui/separator/separator.svelte';
import { currentUser } from '@/stores/session';
import { GitBranch, HelpCircle, Mail, Package, Pyramid, Rocket, Users } from 'lucide-svelte';
import { currentUser, devmode } from '@/stores/session';
import {
Code,
GitBranch,
HelpCircle,
Mail,
Package,
Pyramid,
Rocket,
Users
} from 'lucide-svelte';
import { GitAltBrand, TelegramBrand } from 'svelte-awesome-icons';
let iconClass = 'h-5 w-5 md:h-4 md:w-4';
@@ -57,4 +66,16 @@
<HelpCircle class={iconClass} />
Help
</a>
<Separator class="my-2" />
<Separator class="dark:bg-slate-700" />
<a
href="#"
class={getClass('dev')}
on:click={() => {
devmode.update((dm) => {
return !dm;
});
}}
>
<Code class={iconClass} />
Toggle Dev Mode
</a>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import * as Card from '@/components/ui/card';
import * as Table from '@/components/ui/table';
import { Rocket } from '@/event_helpers/rockets';
import { Rocket, ZapPurchase } from '@/event_helpers/rockets';
import { writable } from 'svelte/store';
import Pie from './Pie.svelte';
import { Avatar, Name } from '@nostr-dev-kit/ndk-svelte-components';
@@ -9,7 +9,7 @@
import NumberIncrement from '@components/ui/number-increment';
export let rocket: Rocket;
export let unratifiedZaps: Map<string, number>;
export let unratifiedZaps: Map<string, ZapPurchase>;
let unratifiedZapsAmount = 0;
let dataLoaded = false;
@@ -17,7 +17,7 @@
$: {
unratifiedZapsAmount = 0;
for (let [_, a] of unratifiedZaps) {
unratifiedZapsAmount += a / 1000;
unratifiedZapsAmount += a.Amount / 1000;
}
unratifiedZapsAmount = unratifiedZapsAmount;
}

View File

@@ -1,27 +1,26 @@
<script lang="ts">
import { buttonVariants } from '$lib/components/ui/button/index.js';
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import * as Alert from '@/components/ui/alert';
import { Input } from '$lib/components/ui/input';
import { Button } from '$lib/components/ui/button/index.js';
import * as Alert from '@/components/ui/alert';
import type { Product, Rocket, RocketProduct } from '@/event_helpers/rockets';
import { formatSats } from '@/helpers';
import { ndk } from '@/ndk';
import { currentUser } from '@/stores/session';
import { NDKZap } from '@nostr-dev-kit/ndk';
import { Terminal } from 'lucide-svelte';
import { requestProvider } from 'webln';
import QrCodeSvg from './QrCodeSvg.svelte';
import CopyButton from './CopyButton.svelte';
import type { Product, Rocket, RocketProduct } from '@/event_helpers/rockets';
import { formatSats } from '@/helpers';
import { Spinner } from 'flowbite-svelte';
import { CheckCircleOutline } from 'flowbite-svelte-icons';
import { tweened, type Tweened } from 'svelte/motion';
import { Terminal } from 'lucide-svelte';
import { cubicOut } from 'svelte/easing';
import { fade, fly } from 'svelte/transition';
import { tweened } from 'svelte/motion';
import { requestProvider } from 'webln';
import CopyButton from './CopyButton.svelte';
import QrCodeSvg from './QrCodeSvg.svelte';
export let product: Product;
export let rocketProduct: RocketProduct | undefined;
export let rocket: Rocket;
export let disabled = false;
let invoice: string | null;
let paymentInitiated: boolean;
@@ -37,7 +36,7 @@
zappedUser: rocket.Event.author
});
invoice = await z.createZapRequest(
rocketProduct.Price * 1000,
rocketProduct.Price() * 1000,
`Purchase of ${product.Name()} from ${rocket.Event.dTag}`,
[['product', product.ID()]]
);
@@ -83,12 +82,16 @@
{#if rocketProduct}
<Dialog.Root bind:open>
<Dialog.Trigger class={buttonVariants({ variant: 'default' })}>
<Dialog.Trigger>
<Button {disabled}>
{#if open}
<Spinner class="me-2" color="white" size={4} /> Confirming...
{:else}
Buy Now for {formatSats(rocketProduct.Price)}
{:else if !disabled}
Buy Now for {formatSats(rocketProduct.Price())}
{:else if disabled}
Out of Stock!
{/if}
</Button>
</Dialog.Trigger>
<Dialog.Content class="sm:max-w-[425px]">
@@ -104,9 +107,9 @@
</Alert.Root>
{/if}
<Dialog.Description
>Pay {rocketProduct.Price === 1
? `${rocketProduct.Price} sat`
: `${rocketProduct.Price} sats`} now with Lightning</Dialog.Description
>Pay {rocketProduct.Price() === 1
? `${rocketProduct.Price()} sat`
: `${rocketProduct.Price()} sats`} now with Lightning</Dialog.Description
>
</Dialog.Header>
{#if invoice}

View File

@@ -1,11 +1,43 @@
<script lang="ts">
import * as Card from '$lib/components/ui/card/index.js';
import { Product, Rocket } from '@/event_helpers/rockets';
import {
Product as ProductEvent,
Rocket,
RocketProduct,
ZapPurchase
} from '@/event_helpers/rockets';
import AddProductToRocket from './AddProductToRocket.svelte';
import PayNow from './PayNow.svelte';
import { onMount } from 'svelte';
import { devmode } from '@/stores/session';
export let product: Product;
export let product: ProductEvent;
export let rocket: Rocket;
export let unratifiedZaps: Map<string, ZapPurchase> | undefined = undefined;
let productFromRocket = rocket.Products().get(product.ID());
onMount(() => {
if (!product.Validate()) {
throw new Error('this should not happen');
}
});
function remainingProducts(product: RocketProduct, zaps?: Map<string, ZapPurchase>): number {
let numberOfPurchases = 0;
if (zaps) {
for (let [_, zap] of zaps) {
if (zap.ProductID == product.ID()) {
numberOfPurchases++;
}
}
}
let remaining = product.MaxPurchases() - numberOfPurchases;
if (remaining < 0) {
remaining = 0;
}
return remaining;
}
</script>
{#if product.Validate()}
@@ -34,18 +66,30 @@
<img src={product.CoverImage()} alt="cover" class="aspect-square object-cover" />
</div>
{/if}
<Card.Footer class="flex items-center justify-center pt-2">
{#if !rocket.Products().get(product.ID())}
<Card.Footer class="flex flex-col items-center justify-center pt-2">
{#if !rocket.Products().get(product.ID()) && !productFromRocket}
<AddProductToRocket {product} {rocket} />
{:else}
<PayNow {product} rocketProduct={rocket.Products().get(product.ID())} {rocket} />
{:else if productFromRocket}
{#if productFromRocket.MaxPurchases() && unratifiedZaps}
<div class="flex flex-nowrap">
{remainingProducts(productFromRocket, unratifiedZaps)} available
</div>
{/if}
<PayNow
disabled={productFromRocket.MaxPurchases() > 0 &&
remainingProducts(productFromRocket, unratifiedZaps) == 0}
{product}
rocketProduct={rocket.Products().get(product.ID())}
{rocket}
/>
{/if}
{#if $devmode}
<a
href="#"
on:click={() => {
console.log(product);
}}>print to console</a
>
>{/if}
</Card.Footer>
</Card.Root>
{/if}

View File

@@ -3,10 +3,11 @@
import { ndk } from '@/ndk';
import ProductCard from './ProductCard.svelte';
import { fetchEvent } from '@/event_helpers/products';
import { Product, type Rocket } from '@/event_helpers/rockets';
import { Product, ZapPurchase, type Rocket } from '@/event_helpers/rockets';
export let productID: string | undefined = undefined;
export let rocket: Rocket;
export let product: Product | undefined = undefined;
export let unratifiedZaps: Map<string, ZapPurchase> | undefined = undefined;
onMount(() => {
if (!product && productID) {
@@ -16,7 +17,7 @@
</script>
{#if product}
<ProductCard {rocket} {product}>
<ProductCard {unratifiedZaps} {rocket} {product}>
<slot />
</ProductCard>
{/if}

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import * as Card from '$lib/components/ui/card/index.js';
import { Product, Rocket } from '@/event_helpers/rockets';
import { Product, Rocket, ZapPurchase } from '@/event_helpers/rockets';
import { fetchEvent } from '@/event_helpers/products';
import { ndk } from '@/ndk';
import { derived, writable } from 'svelte/store';
@@ -9,7 +9,7 @@
import CreateMeritRequest from './CreateMeritRequest.svelte';
export let rocket: Rocket;
export let unratifiedZaps: Map<string, number>;
export let unratifiedZaps: Map<string, ZapPurchase>;
let products = writable(new Map<string, Product>());
@@ -28,7 +28,6 @@
let groups = derived(products, ($products) => {
let productGroups = new Map<string, Map<string, Product>>();
for (let [id, p] of $products) {
console.log(p.Group());
if (!productGroups.get(p.Group())) {
productGroups.set(p.Group(), new Map());
}

View File

@@ -3,16 +3,16 @@
import ProductPurchases from './ProductPurchases.svelte';
import * as Pagination from '@/components/ui/pagination';
import { ChevronLeft, ChevronRight } from 'lucide-svelte';
import { Product, Rocket } from '@/event_helpers/rockets';
import { Product, Rocket, ZapPurchase } from '@/event_helpers/rockets';
export let rocket: Rocket;
export let products: Product[];
export let unratifiedZaps: Map<string, number> | undefined = undefined;
export let unratifiedZaps: Map<string, ZapPurchase> | undefined = undefined;
</script>
<Pagination.Root count={products.length} perPage={1} siblingCount={1} let:pages let:currentPage>
{#if currentPage}
<ProductCardFromId {rocket} product={products[currentPage - 1]}>
<ProductCardFromId {unratifiedZaps} {rocket} product={products[currentPage - 1]}>
{#if unratifiedZaps}
<ProductPurchases bind:unratifiedZaps {rocket} {products} />
{/if}

View File

@@ -11,7 +11,7 @@
//export let products: Product[];
export let rocket: Rocket;
export let unratifiedZaps: Map<string, number>;
export let unratifiedZaps: Map<string, ZapPurchase>;
let zaps = $ndk.storeSubscribe(
[{ '#a': [`31108:${rocket.Event.author.pubkey}:${rocket.Event.dTag}`], kinds: [9735] }],
@@ -85,7 +85,7 @@
validatedZapsNotInRocket.subscribe((zaps) => {
for (let [_, z] of zaps) {
unratifiedZaps.set(z.ZapReceipt.id, z.Amount);
unratifiedZaps.set(z.ZapReceipt.id, z);
}
unratifiedZaps = unratifiedZaps;
});

View File

@@ -2,7 +2,7 @@
import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js';
import Button from '@/components/ui/button/button.svelte';
import * as Card from '@/components/ui/card';
import { Rocket } from '@/event_helpers/rockets';
import { Rocket, ZapPurchase } from '@/event_helpers/rockets';
import type { NDKEvent } from '@nostr-dev-kit/ndk';
import BitcoinAssociations from './AssociatedBitcoinAddresses.svelte';
import MeritRequests from './MeritRequests.svelte';
@@ -14,7 +14,7 @@
export let rocket: NDKEvent;
$: unratifiedZaps = new Map<string, number>();
$: unratifiedZaps = new Map<string, ZapPurchase>();
</script>
<div class="flex flex-col gap-4">

View File

@@ -175,7 +175,7 @@ export class Rocket {
let _products = new Map<string, RocketProduct>();
for (let p of this.Event.getMatchingTags('product')) {
let rp = new RocketProduct(p);
_products.set(rp.ID, rp);
_products.set(rp.ID(), rp);
}
return _products;
}
@@ -551,28 +551,52 @@ export class RocketAMR {
}
export class RocketProduct {
ID: string;
Price: number;
ValidAfter: number; //unix time
MaxPurchases: number;
Purchases: Map<string, ProductPayment>;
tag: NDKTag;
ID(): string {
return this.tag[1].split(':')[0];
}
Price(): number {
return parseInt(this.tag[1].split(':')[1], 10);
}
ValidAfter(): number {
return parseInt(this.tag[1].split(':')[2], 10);
}
MaxPurchases(): number {
return parseInt(this.tag[1].split(':')[3], 10);
}
Purchases(): Map<string, ProductPayment> {
let result: Map<string, ProductPayment> = new Map();
let purchases = JSON.parse(this.tag[3]);
for (let p of purchases) {
let payment = new ProductPayment(p);
result.set(payment.ZapID, payment);
}
return result;
}
PurchasesJSON(): string {
let purchases = [];
for (let [_, p] of this.Purchases) {
for (let [_, p] of this.Purchases()) {
purchases.push(`${p.ZapID}:${p.BuyerPubkey}:${p.WitnessedAt}`);
}
return JSON.stringify(purchases);
}
Validate(): boolean {
try {
this.ID();
this.Price();
this.ValidAfter();
this.MaxPurchases();
this.Purchases();
this.PurchasesJSON();
return true;
} catch {
return false;
}
}
constructor(tag: NDKTag) {
this.Purchases = new Map();
this.ID = tag[1].split(':')[0];
this.Price = parseInt(tag[1].split(':')[1], 10);
this.ValidAfter = parseInt(tag[1].split(':')[2], 10);
this.MaxPurchases = parseInt(tag[1].split(':')[3], 10);
let purchases = JSON.parse(tag[3]);
for (let p of purchases) {
let payment = new ProductPayment(p);
this.Purchases.set(payment.ZapID, payment);
this.tag = tag;
if (!this.Validate()) {
throw new Error('bug!');
}
}
}
@@ -610,7 +634,7 @@ export class ZapPurchase {
IncludedInRocketState(rocket: NDKEvent): boolean {
let thisProduct = this.ProductFromRocket(rocket);
if (thisProduct) {
return thisProduct.Purchases.get(this.ZapReceipt.id) ? true : false;
return thisProduct.Purchases().get(this.ZapReceipt.id) ? true : false;
} else {
return false;
}
@@ -627,7 +651,7 @@ export class ZapPurchase {
return true;
}
let product = this.ProductFromRocket(rocket);
if (product && this.Amount / 1000 >= product.Price) {
if (product && this.Amount / 1000 >= product.Price()) {
return true;
}
return false;

View File

@@ -9,3 +9,5 @@ export async function prepareUserSession(ndk: NDKSvelte, user: NDKUser): Promise
//implement any session set up stuff here
});
}
export const devmode = writable(false);