mirror of
https://github.com/aljazceru/hypergolic.git
synced 2025-12-18 14:04:21 +01:00
problem: can't see how many products ar remaining
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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' })}>
|
||||
{#if open}
|
||||
<Spinner class="me-2" color="white" size={4} /> Confirming...
|
||||
{:else}
|
||||
Buy Now for {formatSats(rocketProduct.Price)}
|
||||
{/if}
|
||||
<Dialog.Trigger>
|
||||
<Button {disabled}>
|
||||
{#if open}
|
||||
<Spinner class="me-2" color="white" size={4} /> Confirming...
|
||||
{: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}
|
||||
|
||||
@@ -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}
|
||||
<a
|
||||
href="#"
|
||||
on:click={() => {
|
||||
console.log(product);
|
||||
}}>print to console</a
|
||||
>
|
||||
{#if $devmode}
|
||||
<a
|
||||
href="#"
|
||||
on:click={() => {
|
||||
console.log(product);
|
||||
}}>print to console</a
|
||||
>{/if}
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
{/if}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user