problem: can't view merit requests

This commit is contained in:
gsovereignty
2024-07-13 16:45:36 +08:00
parent 3207587c6c
commit 26e8aee158
5 changed files with 222 additions and 58 deletions

View File

@@ -1,35 +1,65 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { Button, 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 Dialog from '$lib/components/ui/dialog/index.js';
import { Input } from '$lib/components/ui/input/index.js'; import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js'; import { Label } from '$lib/components/ui/label/index.js';
import * as Alert from '@/components/ui/alert'; import * as Alert from '@/components/ui/alert';
import { Checkbox } from '@/components/ui/checkbox';
import Textarea from '@/components/ui/textarea/textarea.svelte'; import Textarea from '@/components/ui/textarea/textarea.svelte';
import { getRocketURL } from '@/helpers';
import { ndk } from '@/ndk'; import { ndk } from '@/ndk';
import { currentUser } from '@/stores/session'; import { currentUser } from '@/stores/session';
import { NDKEvent } from '@nostr-dev-kit/ndk'; import { NDKEvent } from '@nostr-dev-kit/ndk';
import type NDKSvelte from '@nostr-dev-kit/ndk-svelte'; import type NDKSvelte from '@nostr-dev-kit/ndk-svelte';
import { Terminal } from 'lucide-svelte'; import { Terminal } from 'lucide-svelte';
import Todo from './Todo.svelte'; import Todo from './Todo.svelte';
import { Checkbox } from '@/components/ui/checkbox';
export let rocketEvent: NDKEvent; export let rocketEvent: NDKEvent;
let problem: string; let problem: string = '';
let solution: string; let solution: string = '';
let image: string;
let merits: number = 0; let merits: number = 0;
let sats: number = 0; let sats: string = '';
let wts = false; let wts = false;
let minimum:number = 0; let minimum: string = '';
let _minimum_from_sats = false;
let last_sale_price = 1 / 1;
let num = /^\d+$/;
let open = false;
$: { $: {
if (wts && minimum == 0) { if (!num.test(sats)) {
minimum = sats sats = '';
} }
if (!num.test(minimum)) {
minimum = '';
}
merits = parseInt(sats, 10) * last_sale_price;
}
$: {
if (wts && minimum == '' && !_minimum_from_sats) {
_minimum_from_sats = true;
minimum = sats;
}
}
function isValidUrl(string:string):boolean {
try {
new URL(string);
return true;
} catch (err) {
return false;
}
}
function validateSolution(solution:string) {
if (solution.length > 0) {
return isValidUrl(solution)
}
return true
} }
function publish(ndk: NDKSvelte) { function publish(ndk: NDKSvelte) {
@@ -45,23 +75,26 @@
e.kind = 1409; e.kind = 1409;
e.created_at = Math.floor(new Date().getTime() / 1000); e.created_at = Math.floor(new Date().getTime() / 1000);
e.tags.push(['problem', 'text', problem]); e.tags.push(['problem', 'text', problem]);
if (solution.length > 0) {
e.tags.push(['solution', 'url', solution]); e.tags.push(['solution', 'url', solution]);
}
e.tags.push(['a', `31108:${rocketEvent.pubkey}:${rocketEvent.dTag}`]); e.tags.push(['a', `31108:${rocketEvent.pubkey}:${rocketEvent.dTag}`]);
e.tags.push(['merits', sats.toString(10)]); e.tags.push(['merits', merits.toString(10)]);
e.tags.push(['sats', sats.toString(10)]); e.tags.push(['sats', sats]);
console.log(e.rawEvent()); console.log(e.rawEvent());
e.publish().then((x) => { e.publish().then((x) => {
console.log(x); console.log(x);
goto(`${base}/rockets/${getRocketURL(e)}`); open = false
//goto(`${base}/rockets/${getRocketURL(e)}`);
}); });
} }
</script> </script>
<Dialog.Root> <Dialog.Root bind:open>
<Dialog.Trigger class={buttonVariants({ variant: 'default' })} <Dialog.Trigger class={buttonVariants({ variant: 'default' })}
>Create a Merit Request</Dialog.Trigger >Create a Merit Request</Dialog.Trigger
> >
<Dialog.Content class="sm:max-w-[425px]"> <Dialog.Content class="sm:max-w-[625px]">
{#if !currentUser} {#if !currentUser}
<Alert.Root> <Alert.Root>
<Terminal class="h-4 w-4" /> <Terminal class="h-4 w-4" />
@@ -80,7 +113,7 @@
bind:value={problem} bind:value={problem}
id="name" id="name"
placeholder="Describe the problem you solved, links to github are also acceptable" placeholder="Describe the problem you solved, links to github are also acceptable"
class="col-span-3" class="col-span-3 {problem.length < 10 ? 'border-red-600' : 'border-green-700'}"
/> />
</div> </div>
<div class="grid grid-cols-4 items-center gap-4"> <div class="grid grid-cols-4 items-center gap-4">
@@ -89,20 +122,14 @@
bind:value={solution} bind:value={solution}
id="desc" id="desc"
placeholder="Link to your solution (e.g. a merged PR or some other evidence)" placeholder="Link to your solution (e.g. a merged PR or some other evidence)"
class="col-span-3" class="col-span-3 {validateSolution(solution)? 'border-green-700':'border-red-600'}"
/> />
</div> </div>
<div class="grid grid-cols-4 items-center gap-4"> <div class="grid grid-cols-4 items-center gap-4">
<Label for="sats" class="text-right">Sats</Label> <Label for="sats" class="text-right">Value of your work (Sats)</Label>
<Input <Input bind:value={sats} id="price" placeholder="Sats" class="col-span-1 {parseInt(sats, 10) > 0?'border-green-700':'border-red-600'}" />
bind:value={sats} {#if parseInt(sats, 10) > 0}<Label class="text-left">({merits.toString()} Merits)</Label>
id="price" {/if}
placeholder="Number of Merits you are requesting"
class="col-span-3"
/>
</div>
<div class="grid items-center gap-4">
<span>You are requesting {sats} Merits</span>
</div> </div>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
@@ -117,20 +144,15 @@
</div> </div>
{#if wts} {#if wts}
If your Merit Request is approved, it will be auctioned to potential sponsors. You can set a minimum amount below which your Approved Merit Request will not be sold. Your Merits will be auctioned to potential sponsors as soon as it is approved, enabling you
to be paid in Sats for your work. Tip: you don't have to decide right now, you can do this
at any time.
<div class="grid grid-cols-4 items-center gap-4"> <div class="grid grid-cols-4 items-center gap-4">
<Label for="sats" class="text-right col-span-2">Minimum Price (Sats)</Label> <Label for="sats" class="col-span-2 text-right">Auction Reserve Price (Sats)</Label>
<Input <Input bind:value={minimum} id="price" placeholder="Reserve Price" class="col-span-1" />
bind:value={minimum}
id="price"
placeholder="Number of Merits you are requesting"
class="col-span-2"
/>
</div> </div>
{/if} {/if}
</div> </div>
<Dialog.Footer> <Dialog.Footer>
<Button <Button
@@ -139,10 +161,9 @@
}} }}
type="submit">Publish</Button type="submit">Publish</Button
> >
</Dialog.Footer> </Dialog.Footer>
<Todo <Todo
text={['validate sane field entries', 'Sats must be number', 'highlight errors somehow']} text={['remove white border on focus so that the validation indication color can be seen']}
/> />
</Dialog.Content> </Dialog.Content>
</Dialog.Root> </Dialog.Root>

View File

@@ -0,0 +1,96 @@
<script lang="ts">
import * as Card from '@/components/ui/card';
import * as Table from '@/components/ui/table';
import { MeritRequest } from '@/event_helpers/merits';
import { ZapPurchase, type RocketProduct } from '@/event_helpers/rockets';
import { unixToRelativeTime } from '@/helpers';
import { ndk } from '@/ndk';
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { Avatar, Name } from '@nostr-dev-kit/ndk-svelte-components';
import { onDestroy, onMount } from 'svelte';
import { derived } from 'svelte/store';
export let rocket: NDKEvent;
let _merits = $ndk.storeSubscribe(
[{ '#a': [`31108:${rocket.author.pubkey}:${rocket.dTag}`], kinds: [1409 as NDKKind] }],
{
subId: `${rocket.dTag}_merits`
}
);
onDestroy(() => {
_merits?.unsubscribe();
});
let merits = derived(_merits, ($merits) => {
let map = new Map<string, MeritRequest>();
for (let z of $merits) {
let meritRequest = new MeritRequest(z);
if (meritRequest.Valid(rocket)) {
map.set(meritRequest.ID, meritRequest);
}
}
return map;
});
//todo: update rocket event with confirmed zaps if we have votepower
</script>
<Card.Root class="sm:col-span-3">
<Card.Header class="px-7">
<Card.Title>Merit Requests</Card.Title>
<Card.Description
>Merit Requests</Card.Description
>
</Card.Header>
<Card.Content>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>Contributor</Table.Head>
<Table.Head class="hidden md:table-cell text-left">Problem</Table.Head>
<Table.Head class="table-cell">Amount (Sats)</Table.Head>
<Table.Head class="table-cell">Merits</Table.Head>
<Table.Head class="hidden md:table-cell text-right">When</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each $merits as [id, merit], _ (id)}
<Table.Row
on:click={() => {
console.log(merit.Request.rawEvent());
}}
class="bg-accent cursor-pointer"
>
<Table.Cell>
<div class="flex flex-nowrap">
<Avatar
ndk={$ndk}
pubkey={merit.Pubkey}
class="h-10 w-10 flex-none rounded-full object-cover"
/>
<Name
ndk={$ndk}
pubkey={merit.Pubkey}
class="hidden md:inline-block max-w-32 truncate p-2"
/>
</div>
</Table.Cell>
<Table.Cell class="hidden md:table-cell text-left"
>{merit.Problem()}</Table.Cell
>
<Table.Cell class="table-cell">{merit.Sats}</Table.Cell>
<Table.Cell class="table-cell">{merit.Merits}</Table.Cell>
<Table.Cell class="hidden md:table-cell text-right"
>{unixToRelativeTime(merit.TimeStamp * 1000)}</Table.Cell
>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</Card.Content>
</Card.Root>

View File

@@ -1,17 +1,14 @@
<script lang="ts"> <script lang="ts">
import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js'; import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js';
import { RocketProduct } from '@/event_helpers/rockets'; import * as Card from '@/components/ui/card';
import type { NDKEvent } from '@nostr-dev-kit/ndk'; import type { NDKEvent } from '@nostr-dev-kit/ndk';
import { writable, type Readable, type Writable } from 'svelte/store'; import CreateMeritRequest from './CreateMeritRequest.svelte';
import CreateNewProduct from './CreateNewProduct.svelte';
import MeritsAndSatflow from './MeritsAndSatflow.svelte'; import MeritsAndSatflow from './MeritsAndSatflow.svelte';
import ProductFomo from './ProductFomo.svelte'; import ProductFomo from './ProductFomo.svelte';
import Todo from './Todo.svelte';
import ProductCard from './ProductCard.svelte';
import CreateNewProduct from './CreateNewProduct.svelte';
import type { ExtendedBaseType } from '@nostr-dev-kit/ndk-svelte';
import ProposedProducts from './ProposedProducts.svelte'; import ProposedProducts from './ProposedProducts.svelte';
import * as Card from '@/components/ui/card'; import Todo from './Todo.svelte';
import CreateMeritRequest from './CreateMeritRequest.svelte'; import MeritRequests from './MeritRequests.svelte';
export let rocket: NDKEvent; export let rocket: NDKEvent;
</script> </script>
@@ -41,6 +38,8 @@
<ProposedProducts {rocket} /> <ProposedProducts {rocket} />
<MeritRequests {rocket} />
<Card.Root class="sm:col-span-3"> <Card.Root class="sm:col-span-3">
<Card.Header class="pb-3"> <Card.Header class="pb-3">
<Card.Title>Actions</Card.Title> <Card.Title>Actions</Card.Title>

View File

@@ -0,0 +1,40 @@
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { getNumberFromTag } from "./rockets";
export class MeritRequest {
ID: string;
Sats: number;
Merits: number;
Request: NDKEvent;
Pubkey: string;
TimeStamp: number;
Problem():string {
let _problem = ""
//todo: handle 1971 problem tracker event tags somehow
for (let problem of this.Request.getMatchingTags("problem")) {
if (problem && problem.length > 2) {
_problem = problem[2]
}
}
return _problem
}
IncludedInRocketState(rocket: NDKEvent): boolean {
return true
}
Valid(rocket: NDKEvent): boolean {
//todo: validate pubkey is in WoT
let valid = true;
return valid;
}
constructor(request: NDKEvent) {
this.Request = request;
this.ID = request.id;
this.Pubkey = request.pubkey
if (this.Request.created_at) {
this.TimeStamp = this.Request.created_at
}
this.Sats = getNumberFromTag("sats", request)
this.Merits = getNumberFromTag("merits", request)
}
}

View File

@@ -120,10 +120,18 @@ function getZapRequest(zapReceipt: NDKEvent): NDKEvent | undefined {
} }
function getZapAmount(zapRequest?: NDKEvent): number { function getZapAmount(zapRequest?: NDKEvent): number {
let amount = 0; return getNumberFromTag("amount", zapRequest)
let amountTag = zapRequest?.getMatchingTags('amount');
if (amountTag?.length == 1) {
amount = parseInt(amountTag[0][1], 10);
} }
return amount;
export function getNumberFromTag(tag:string, event?: NDKEvent): number {
let amountTag = event?.getMatchingTags(tag);
if (amountTag && amountTag[0] && amountTag[0][1]) {
try {
let amount = parseInt(amountTag[0][1], 10);
return amount
} catch {
console.log("ERROR: could not find number in tag: ", tag, event)
}
}
return 0
} }