mirror of
https://github.com/aljazceru/hypergolic.git
synced 2025-12-19 06:24:20 +01:00
Merge branch 'MASTER' into fix-style
This commit is contained in:
38
README.md
38
README.md
@@ -1,6 +1,17 @@
|
|||||||
# Hypergolic
|
# Hypergolic
|
||||||
|
|
||||||
A nostrocket client
|
A nostr client that implements the nostrocket [NIPS](https://github.com/nostrocket/NIPS).
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
|
|
||||||
@@ -12,6 +23,8 @@ Pull request should contain only ONE commit that solves exactly ONE problem. The
|
|||||||
|
|
||||||
The commit message MUST be a short summary of the problem being solved, usually this should be the same as title of the problem from the github or nostrocket issue tracker.
|
The commit message MUST be a short summary of the problem being solved, usually this should be the same as title of the problem from the github or nostrocket issue tracker.
|
||||||
|
|
||||||
|
Do not send pull requests unless they are ready to merge, no "work in progress" pull requests.
|
||||||
|
|
||||||
## Style Guide
|
## Style Guide
|
||||||
|
|
||||||
Avoid uneccessary whitespace changes. Whitespace changes make it difficult to see what code was really changed.
|
Avoid uneccessary whitespace changes. Whitespace changes make it difficult to see what code was really changed.
|
||||||
@@ -22,25 +35,8 @@ https://www.shadcn-svelte.com/docs
|
|||||||
|
|
||||||
## Getting Paid
|
## Getting Paid
|
||||||
|
|
||||||
If you want to get paid for working on this project, demonstrate your capability by solving some problems and adhering to the instructions in this readme. After you have 2-3 PR's merged, DM gsovereignty on nostr to have a conversation about paid work.
|
Once your pull request has been merged, go to nostrocket.org and submit a Merit Request.
|
||||||
|
|
||||||
## Developing
|
Upon your Merit Request being approved by the existing contributors to Nostrocket, you will be able to sell your Approved Merit Request (there are people wanting to buy these for sats).
|
||||||
|
|
||||||
`npm install`
|
Ask in the Telegram group or on Nostr if you need help or have questions.
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# or start the server and open the app in a new browser tab
|
|
||||||
npm run dev -- --open
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
To create a production version of your app:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
You can preview the production build with `npm run preview`.
|
|
||||||
|
|||||||
@@ -10,10 +10,29 @@
|
|||||||
import validate from 'bitcoin-address-validation';
|
import validate from 'bitcoin-address-validation';
|
||||||
import type { Rocket } from '@/event_helpers/rockets';
|
import type { Rocket } from '@/event_helpers/rockets';
|
||||||
import { derived } from 'svelte/store';
|
import { derived } from 'svelte/store';
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
import type { NDKEventStore, ExtendedBaseType } from '@nostr-dev-kit/ndk-svelte';
|
||||||
|
|
||||||
let bitcoinAddress: string;
|
let bitcoinAddress: string;
|
||||||
export let rocket: Rocket;
|
export let rocket: Rocket;
|
||||||
|
|
||||||
|
let associations: NDKEventStore<ExtendedBaseType<NDKEvent>> | undefined = undefined;
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if ($currentUser && !associations) {
|
||||||
|
associations = $ndk.storeSubscribe(
|
||||||
|
[{ authors: [$currentUser.pubkey], kinds: [1413 as number] }],
|
||||||
|
{
|
||||||
|
subId: `1413-${$currentUser.pubkey}`
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
associations?.unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
let associatedAddresses = derived(currentUser, ($currentUser) => {
|
let associatedAddresses = derived(currentUser, ($currentUser) => {
|
||||||
let addresses: Set<string> = new Set();
|
let addresses: Set<string> = new Set();
|
||||||
if ($currentUser) {
|
if ($currentUser) {
|
||||||
@@ -40,6 +59,7 @@
|
|||||||
let event = new NDKEvent($ndk);
|
let event = new NDKEvent($ndk);
|
||||||
event.kind = 1413;
|
event.kind = 1413;
|
||||||
event.tags.push(['onchain', address]);
|
event.tags.push(['onchain', address]);
|
||||||
|
event.tags.push(rocket.ATag());
|
||||||
//todo: let user specify a rocket
|
//todo: let user specify a rocket
|
||||||
console.log('todo: let user specify a rocket');
|
console.log('todo: let user specify a rocket');
|
||||||
event
|
event
|
||||||
@@ -63,13 +83,23 @@ their Merits.
|
|||||||
Merit purchases must be conducted with a Bitcoin address that is associated with your pubkey,
|
Merit purchases must be conducted with a Bitcoin address that is associated with your pubkey,
|
||||||
otherwise you will not recieve the Merits upon payment.
|
otherwise you will not recieve the Merits upon payment.
|
||||||
</div>
|
</div>
|
||||||
{#if $associatedAddresses.size == 0}You do not have any registered addresses{:else}
|
{#if ($associatedAddresses.size == 0 && !associations) || (associations && $associations.length == 0)}You
|
||||||
Your registered addresses:
|
do not have any registered addresses{:else if associations && $associations && $associations.length > 0}
|
||||||
|
<h4 class="text-lg font-bold dark:text-white">Pending Additions</h4>
|
||||||
|
<ul class="m-2 flex flex-col">
|
||||||
|
{#each $associations as event}<li class="list-item list-disc">
|
||||||
|
{event.getMatchingTags('onchain')[0][1]}
|
||||||
|
</li>{/each}
|
||||||
|
</ul>
|
||||||
|
{:else}
|
||||||
|
<h4 class="text-lg font-bold dark:text-white">Your registered addresses</h4>
|
||||||
|
|
||||||
<ul class="m-2 flex flex-col">
|
<ul class="m-2 flex flex-col">
|
||||||
{#each $associatedAddresses as address}<li class="list-item list-disc">{address}</li>{/each}
|
{#each $associatedAddresses as address}<li class="list-item list-disc">{address}</li>{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
Add a new address now
|
<h4 class="text-lg font-bold dark:text-white">Add a new address now</h4>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<InputBitcoinAddress bind:bitcoinAddress /><Button
|
<InputBitcoinAddress bind:bitcoinAddress /><Button
|
||||||
on:click={() => publish(bitcoinAddress)}
|
on:click={() => publish(bitcoinAddress)}
|
||||||
|
|||||||
122
src/components/AssociatedBitcoinAddresses.svelte
Normal file
122
src/components/AssociatedBitcoinAddresses.svelte
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as Card from '@/components/ui/card';
|
||||||
|
import * as Table from '@/components/ui/table';
|
||||||
|
import { BitcoinAssociation, Rocket } from '@/event_helpers/rockets';
|
||||||
|
import { getCuckPrice } from '@/helpers';
|
||||||
|
import { ndk } from '@/ndk';
|
||||||
|
import { getBalance } from '@/stores/bitcoin';
|
||||||
|
import { NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
|
import { Avatar, Name } from '@nostr-dev-kit/ndk-svelte-components';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import { derived, writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export let rocket: Rocket;
|
||||||
|
|
||||||
|
let cuckprice = 0;
|
||||||
|
|
||||||
|
let associations = writable(new Map<string, BitcoinAssociation>());
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
getCuckPrice().then((price) => {
|
||||||
|
if (price) cuckprice = price;
|
||||||
|
});
|
||||||
|
associations.set(rocket.BitcoinAssociations());
|
||||||
|
$associations.forEach((a) => {
|
||||||
|
if (a.Address) {
|
||||||
|
getBalance(a.Address)
|
||||||
|
.then((v) => {
|
||||||
|
a.Balance = v;
|
||||||
|
associations.update((existing) => {
|
||||||
|
existing.set(a.Address!, a);
|
||||||
|
return existing;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let amounts = derived(associations, ($associations) => {
|
||||||
|
let amounts = new Map<string, number>();
|
||||||
|
for (let [_, a] of $associations) {
|
||||||
|
if (a.Balance > 0) {
|
||||||
|
let existing = amounts.get(a.Pubkey);
|
||||||
|
if (!existing) {
|
||||||
|
existing = 0;
|
||||||
|
}
|
||||||
|
existing += a.Balance;
|
||||||
|
amounts.set(a.Pubkey, existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return amounts;
|
||||||
|
});
|
||||||
|
|
||||||
|
let total = derived(amounts, ($amounts) => {
|
||||||
|
let t = 0;
|
||||||
|
for (let [_, a] of $amounts) {
|
||||||
|
t += a;
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card.Root class="sm:col-span-3">
|
||||||
|
<Card.Header class="px-7">
|
||||||
|
<Card.Title>Sponsors</Card.Title>
|
||||||
|
<Card.Description
|
||||||
|
>These people want to sponsor Contributors working on {rocket.Name()}.</Card.Description
|
||||||
|
>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content>
|
||||||
|
<Table.Root>
|
||||||
|
<Table.Header>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Head class="w-[200px]">Sponsor</Table.Head>
|
||||||
|
<Table.Head class="hidden text-left md:table-cell">Amount (Sats)</Table.Head>
|
||||||
|
<Table.Head class="hidden text-left md:table-cell">CuckLoserBucks</Table.Head>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
<Table.Body>
|
||||||
|
{#each $amounts as [npub, amount], _ (npub)}
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell>
|
||||||
|
<div class="flex flex-nowrap">
|
||||||
|
<Avatar
|
||||||
|
ndk={$ndk}
|
||||||
|
pubkey={npub}
|
||||||
|
class="h-10 w-10 flex-none rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
<Name
|
||||||
|
ndk={$ndk}
|
||||||
|
pubkey={npub}
|
||||||
|
class="hidden max-w-32 truncate p-2 md:inline-block"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell class="hidden text-left md:table-cell">
|
||||||
|
{amount.toLocaleString()}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell class="hidden text-left md:table-cell">
|
||||||
|
${Math.floor((amount / 100000000) * cuckprice).toLocaleString()}
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
{/each}
|
||||||
|
{#if $total > 0}
|
||||||
|
<Table.Row
|
||||||
|
class=" bg-violet-200 hover:bg-violet-300 dark:bg-violet-950 dark:hover:bg-violet-900"
|
||||||
|
>
|
||||||
|
<Table.Cell>TOTAL</Table.Cell>
|
||||||
|
<Table.Cell class="hidden text-left md:table-cell">
|
||||||
|
{$total.toLocaleString()}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell class="hidden text-left md:table-cell">
|
||||||
|
${Math.floor(($total / 100000000) * cuckprice).toLocaleString()}
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
{/if}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import * as Card from '@/components/ui/card';
|
|
||||||
import * as Table from '@/components/ui/table';
|
|
||||||
import { BitcoinAssociation, Rocket } from '@/event_helpers/rockets';
|
|
||||||
import { ndk } from '@/ndk';
|
|
||||||
import { getBalance } from '@/stores/bitcoin';
|
|
||||||
import { NDKKind } from '@nostr-dev-kit/ndk';
|
|
||||||
import { Avatar, Name } from '@nostr-dev-kit/ndk-svelte-components';
|
|
||||||
import { onDestroy, onMount } from 'svelte';
|
|
||||||
|
|
||||||
export let rocket: Rocket;
|
|
||||||
|
|
||||||
let _associationRequests = $ndk.storeSubscribe(
|
|
||||||
[{ '#a': [`31108:${rocket.Event.author.pubkey}:${rocket.Name()}`], kinds: [1413 as NDKKind] }],
|
|
||||||
{
|
|
||||||
subId: `${rocket.Name()}_bitcoin_associations`
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
_associationRequests?.unsubscribe();
|
|
||||||
});
|
|
||||||
|
|
||||||
let addresses = new Map<string, BitcoinAssociation>();
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
addresses = rocket.BitcoinAssociations();
|
|
||||||
addresses.forEach((a) => {
|
|
||||||
if (a.Address) {
|
|
||||||
getBalance(a.Address)
|
|
||||||
.then((v) => {
|
|
||||||
a.Balance = v;
|
|
||||||
addresses.set(a.Address!, a);
|
|
||||||
addresses = addresses;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Card.Root class="sm:col-span-3">
|
|
||||||
<Card.Header class="px-7">
|
|
||||||
<Card.Title>Registered Bitcoin Addresses</Card.Title>
|
|
||||||
<Card.Description
|
|
||||||
>These people have registered a Bitcoin address and want to sponsor Contributors working on {rocket.Name()}</Card.Description
|
|
||||||
>
|
|
||||||
</Card.Header>
|
|
||||||
<Card.Content>
|
|
||||||
<Table.Root>
|
|
||||||
<Table.Header>
|
|
||||||
<Table.Row>
|
|
||||||
<Table.Head class="w-[200px]">Sponsor</Table.Head>
|
|
||||||
<Table.Head class="hidden text-left md:table-cell">Amount (Sats)</Table.Head>
|
|
||||||
<Table.Head class="table-cell">Address</Table.Head>
|
|
||||||
</Table.Row>
|
|
||||||
</Table.Header>
|
|
||||||
<Table.Body>
|
|
||||||
{#each addresses as [address, ba], _ (address)}
|
|
||||||
<Table.Row>
|
|
||||||
<Table.Cell>
|
|
||||||
<div class="flex flex-nowrap">
|
|
||||||
<Avatar
|
|
||||||
ndk={$ndk}
|
|
||||||
pubkey={ba.Pubkey}
|
|
||||||
class="h-10 w-10 flex-none rounded-full object-cover"
|
|
||||||
/>
|
|
||||||
<Name
|
|
||||||
ndk={$ndk}
|
|
||||||
pubkey={ba.Pubkey}
|
|
||||||
class="hidden max-w-32 truncate p-2 md:inline-block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Table.Cell>
|
|
||||||
<Table.Cell class="hidden text-left md:table-cell">
|
|
||||||
{ba.Balance.toLocaleString()}
|
|
||||||
</Table.Cell>
|
|
||||||
<Table.Cell class="table-cell">{ba.Address}</Table.Cell>
|
|
||||||
</Table.Row>
|
|
||||||
{/each}
|
|
||||||
</Table.Body>
|
|
||||||
</Table.Root>
|
|
||||||
</Card.Content>
|
|
||||||
</Card.Root>
|
|
||||||
@@ -12,11 +12,12 @@
|
|||||||
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 { isValidUrl } from '@/event_helpers/rockets';
|
import { isValidUrl, Rocket } from '@/event_helpers/rockets';
|
||||||
import CalculateSats from './CalculateSats.svelte';
|
import CalculateSats from './CalculateSats.svelte';
|
||||||
import { isGitHubUrl, parseProblem } from '@/helpers';
|
import { isGitHubUrl, parseProblem } from '@/helpers';
|
||||||
|
import Login from './Login.svelte';
|
||||||
|
|
||||||
export let rocketEvent: NDKEvent;
|
export let rocket: Rocket;
|
||||||
|
|
||||||
let problem: string = '';
|
let problem: string = '';
|
||||||
let solution: string = '';
|
let solution: string = '';
|
||||||
@@ -78,17 +79,29 @@
|
|||||||
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) {
|
if (solution.length > 0) {
|
||||||
e.tags.push(['solution', 'url', solution]);
|
try {
|
||||||
|
let url = new URL(solution);
|
||||||
|
e.tags.push(['solution', 'url', url.toString()]);
|
||||||
|
} catch {
|
||||||
|
e.tags.push(['solution', 'text', solution]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
e.tags.push(['a', `31108:${rocketEvent.pubkey}:${rocketEvent.dTag}`]);
|
e.tags.push(['a', `31108:${rocket.Event.pubkey}:${rocket.Event.dTag}`]);
|
||||||
e.tags.push(['merits', merits.toString(10)]);
|
e.tags.push(['merits', merits.toString(10)]);
|
||||||
e.tags.push(['sats', sats]);
|
e.tags.push(['sats', sats]);
|
||||||
console.log(e.rawEvent());
|
console.log(e.rawEvent());
|
||||||
e.publish().then((x) => {
|
e.publish()
|
||||||
console.log(x);
|
.then((x) => {
|
||||||
open = false;
|
console.log(x);
|
||||||
//goto(`${base}/rockets/${getRocketURL(e)}`);
|
console.log('todo: publish a kind 1 and tag the rocket and author');
|
||||||
});
|
open = false;
|
||||||
|
//goto(`${base}/rockets/${getRocketURL(e)}`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert(
|
||||||
|
"something went wrong, copy/paste your data and refresh then try again if you don't see your merit request in the rocket dashboard (we r so early, there will be blugs)"
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -140,7 +153,7 @@
|
|||||||
<CalculateSats on:result={(event) => (sats = event.detail)} />
|
<CalculateSats on:result={(event) => (sats = event.detail)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center space-x-2">
|
<!-- <div class="flex items-center space-x-2">
|
||||||
<Checkbox id="sell" bind:checked={wts} aria-labelledby="terms-label" />
|
<Checkbox id="sell" bind:checked={wts} aria-labelledby="terms-label" />
|
||||||
<Label
|
<Label
|
||||||
id="terms-label"
|
id="terms-label"
|
||||||
@@ -149,9 +162,9 @@
|
|||||||
>
|
>
|
||||||
I want Sats not Merits
|
I want Sats not Merits
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
{#if wts}
|
<!-- {#if wts}
|
||||||
Your Merits will be auctioned to potential sponsors as soon as it is approved, enabling you
|
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
|
to be paid in Sats for your work. Tip: you don't have to decide right now, you can do this
|
||||||
at any time.
|
at any time.
|
||||||
@@ -160,15 +173,19 @@
|
|||||||
<Label for="sats" class="col-span-2 text-right">Auction Reserve Price (Sats)</Label>
|
<Label for="sats" class="col-span-2 text-right">Auction Reserve Price (Sats)</Label>
|
||||||
<Input bind:value={minimum} id="price" placeholder="Reserve Price" class="col-span-1" />
|
<Input bind:value={minimum} id="price" placeholder="Reserve Price" class="col-span-1" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if} -->
|
||||||
</div>
|
</div>
|
||||||
<Dialog.Footer>
|
<Dialog.Footer>
|
||||||
<Button
|
{#if $currentUser}
|
||||||
on:click={() => {
|
<Button
|
||||||
publish($ndk);
|
on:click={() => {
|
||||||
}}
|
publish($ndk);
|
||||||
type="submit">Publish</Button
|
}}
|
||||||
>
|
type="submit">Publish</Button
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<Login />
|
||||||
|
{/if}
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
<Todo
|
<Todo
|
||||||
text={['remove white border on focus so that the validation indication color can be seen']}
|
text={['remove white border on focus so that the validation indication color can be seen']}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import Separator from '@/components/ui/separator/separator.svelte';
|
import Separator from '@/components/ui/separator/separator.svelte';
|
||||||
import { Mail, Package, Pyramid, Rocket, Users } from 'lucide-svelte';
|
import { currentUser } from '@/stores/session';
|
||||||
|
import { 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';
|
let iconClass = 'h-5 w-5 md:h-4 md:w-4';
|
||||||
|
|
||||||
@@ -16,22 +18,44 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if $currentUser}
|
||||||
|
<a href="{base}/inbox" class={getClass('inbox')}>
|
||||||
|
<Mail class={iconClass} />
|
||||||
|
Inbox
|
||||||
|
<Badge class="ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full">1</Badge>
|
||||||
|
</a>
|
||||||
|
<Separator class="dark:bg-slate-700" />
|
||||||
|
{/if}
|
||||||
<a href="{base}/rockets" class={getClass('rockets')}>
|
<a href="{base}/rockets" class={getClass('rockets')}>
|
||||||
<Rocket class={iconClass} />
|
<Rocket class={iconClass} />
|
||||||
Rockets
|
Rockets
|
||||||
</a>
|
</a>
|
||||||
<a href="##" class={getClass('problems')}>
|
|
||||||
<Pyramid class={iconClass} />
|
|
||||||
Problem Tracker
|
|
||||||
<Badge class="ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full">6</Badge>
|
|
||||||
</a>
|
|
||||||
<a href="{base}/products" class={getClass('products')}>
|
<a href="{base}/products" class={getClass('products')}>
|
||||||
<Package class={iconClass} />
|
<Package class={iconClass} />
|
||||||
Products
|
Products
|
||||||
</a>
|
</a>
|
||||||
<a href="##" class={getClass('people')}>
|
<a href="{base}/problems" class={getClass('problems')}>
|
||||||
|
<Pyramid class={iconClass} />
|
||||||
|
Problem Tracker
|
||||||
|
<!-- <Badge class="ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full">6</Badge> -->
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- <a href="##" class={getClass('people')}>
|
||||||
<Users class={iconClass} />
|
<Users class={iconClass} />
|
||||||
People
|
People
|
||||||
|
</a> -->
|
||||||
|
<Separator class="dark:bg-slate-700" />
|
||||||
|
<a href="https://github.com/nostrocket/hypergolic" class={getClass('_')}>
|
||||||
|
<GitAltBrand class={iconClass} />
|
||||||
|
Source
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/nostrocket" class={getClass('_')}>
|
||||||
|
<TelegramBrand class={iconClass} />
|
||||||
|
Telegram Group
|
||||||
|
</a>
|
||||||
|
<a href="{base}/help" class={getClass('help')}>
|
||||||
|
<HelpCircle class={iconClass} />
|
||||||
|
Help
|
||||||
</a>
|
</a>
|
||||||
<Separator class="my-2" />
|
<Separator class="my-2" />
|
||||||
<a href="##" class={getClass('inbox')}>
|
<a href="##" class={getClass('inbox')}>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
import { Avatar, Name } from '@nostr-dev-kit/ndk-svelte-components';
|
import { Avatar, Name } from '@nostr-dev-kit/ndk-svelte-components';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { derived, writable } from 'svelte/store';
|
import { derived, writable } from 'svelte/store';
|
||||||
|
import CreateMeritRequest from './CreateMeritRequest.svelte';
|
||||||
|
|
||||||
//export let rocket: NDKEvent;
|
//export let rocket: NDKEvent;
|
||||||
export let rocket: Rocket; // = new Rocket(rocket);
|
export let rocket: Rocket; // = new Rocket(rocket);
|
||||||
@@ -92,11 +93,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card.Root class="sm:col-span-3">
|
<Card.Root class="sm:col-span-3">
|
||||||
<Card.Header class="px-7">
|
<Card.Header class="pb-3">
|
||||||
<Card.Title>Merit Requests</Card.Title>
|
<Card.Title>Merit Requests</Card.Title>
|
||||||
<Card.Description>Merit Requests</Card.Description>
|
<Card.Description>Merit Requests</Card.Description>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
|
<CreateMeritRequest {rocket} />
|
||||||
<Table.Root>
|
<Table.Root>
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
import { Alert } from '@/components/ui/alert';
|
import { Alert } from '@/components/ui/alert';
|
||||||
import { currentUser } from '@/stores/session';
|
import { currentUser } from '@/stores/session';
|
||||||
import MeritComment from './MeritComment.svelte';
|
import MeritComment from './MeritComment.svelte';
|
||||||
|
import { Description } from 'formsnap';
|
||||||
|
|
||||||
export let merit: MeritRequest;
|
export let merit: MeritRequest;
|
||||||
//export let rocket: NDKEvent;
|
//export let rocket: NDKEvent;
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
|
|
||||||
let result: VoteDirection | undefined;
|
let result: VoteDirection | undefined;
|
||||||
|
|
||||||
export let parsedRocket:Rocket;
|
export let parsedRocket: Rocket;
|
||||||
|
|
||||||
let _votes = $ndk.storeSubscribe(
|
let _votes = $ndk.storeSubscribe(
|
||||||
{ '#a': [RocketATagFilter(parsedRocket.Event)], '#e': [merit.ID], kinds: [1410 as NDKKind] },
|
{ '#a': [RocketATagFilter(parsedRocket.Event)], '#e': [merit.ID], kinds: [1410 as NDKKind] },
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: referenceTime = cuckPrice
|
$: referenceTime = cuckPrice
|
||||||
? formatReferenceTime(((merit.Sats / 100000000) * cuckPrice) / 70)
|
? formatReferenceTime(((merit.Sats / 100000000) * cuckPrice) / 50)
|
||||||
: '...';
|
: '...';
|
||||||
|
|
||||||
let votes = derived(_votes, ($_votes) => {
|
let votes = derived(_votes, ($_votes) => {
|
||||||
@@ -118,9 +119,9 @@
|
|||||||
<div class="flex flex-nowrap justify-between">
|
<div class="flex flex-nowrap justify-between">
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
{merit.Problem().split('\n')[0]}
|
{merit.Problem().split('\n')[0]}
|
||||||
</Card.Title>{#if merit.Solution()}<a
|
</Card.Title>{#if merit.SolutionURL()}<a
|
||||||
class="flex flex-nowrap text-orange-500 underline decoration-orange-500"
|
class="flex flex-nowrap text-orange-500 underline decoration-orange-500"
|
||||||
href={merit.Solution()}>View Solution <ExternalLink size={18} class="m-1" /></a
|
href={merit.SolutionURL()}>View Solution <ExternalLink size={18} class="m-1" /></a
|
||||||
>{/if}
|
>{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-nowrap">
|
<div class="flex flex-nowrap">
|
||||||
@@ -131,87 +132,101 @@
|
|||||||
/>
|
/>
|
||||||
<Name ndk={$ndk} pubkey={merit.Pubkey} class="inline-block max-w-32 truncate p-2" />
|
<Name ndk={$ndk} pubkey={merit.Pubkey} class="inline-block max-w-32 truncate p-2" />
|
||||||
</div>
|
</div>
|
||||||
<Card.Content class="p-6 text-sm">
|
{#if merit.SolutionText()}
|
||||||
<div class="grid gap-3">
|
{merit.SolutionText()?.trim()}
|
||||||
<div class="font-semibold">Merit Request Details</div>
|
{/if}
|
||||||
<ul class="grid gap-3">
|
<Card.Description></Card.Description>
|
||||||
<li class="flex items-center justify-between">
|
|
||||||
<span class="text-muted-foreground"> Number of Merits being requested </span>
|
|
||||||
<span>{merit.Merits.toLocaleString()}</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-center justify-between">
|
|
||||||
<span class="text-muted-foreground">
|
|
||||||
Value in Sats at the time the request was made
|
|
||||||
</span>
|
|
||||||
<span>{merit.Sats.toLocaleString()}</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-center justify-between">
|
|
||||||
<span class="text-muted-foreground">
|
|
||||||
Approximate value of {merit.Sats.toLocaleString()} sats in CuckLoserBucks
|
|
||||||
</span>
|
|
||||||
<span>${cuckPrice ? ((merit.Sats / 100000000) * cuckPrice).toFixed(2) : 'Loading'}</span
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<Separator class="my-4" />
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
|
||||||
<div class="grid gap-3">
|
|
||||||
<div class="font-semibold">Analysis</div>
|
|
||||||
<span class="grid gap-0.5 not-italic text-muted-foreground">
|
|
||||||
A competent freelance developer earns $70 CuckLoserBucks an hour (on average). Using
|
|
||||||
this rate, the contributor is claiming to have spent about {referenceTime} working on this.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="grid auto-rows-max gap-3">
|
|
||||||
<div class="font-semibold">Reference Time</div>
|
|
||||||
<div class="text-muted-foreground">{referenceTime}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Separator class="my-4" />
|
|
||||||
<div class="font-semibold">Votes</div>
|
|
||||||
{#if $votes.size == 0}<Alert
|
|
||||||
><Info />Waiting for existing <span class="italic">{parsedRocket.Name()}</span> Merit
|
|
||||||
holders to vote</Alert
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
<Table.Root>
|
|
||||||
<Table.Body>
|
|
||||||
{#each $votes as [id, vote], _ (id)}
|
|
||||||
<Table.Row
|
|
||||||
on:click={() => {
|
|
||||||
console.log(vote.Event.rawEvent());
|
|
||||||
}}
|
|
||||||
class="cursor-pointer {vote.VoteDirection == 'ratify'
|
|
||||||
? 'bg-lime-600'
|
|
||||||
: 'bg-red-700'} {vote.VoteDirection == 'ratify'
|
|
||||||
? 'hover:bg-lime-700'
|
|
||||||
: 'hover:bg-red-800'}"
|
|
||||||
>
|
|
||||||
<Table.Cell>
|
|
||||||
<div class="flex flex-nowrap">
|
|
||||||
<Avatar
|
|
||||||
ndk={$ndk}
|
|
||||||
pubkey={vote.Pubkey}
|
|
||||||
class="h-10 w-10 flex-none rounded-full object-cover"
|
|
||||||
/>
|
|
||||||
<Name
|
|
||||||
ndk={$ndk}
|
|
||||||
pubkey={vote.Pubkey}
|
|
||||||
class="inline-block max-w-32 truncate p-2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Table.Cell>
|
|
||||||
<Table.Cell class="hidden text-left md:table-cell">{vote.VoteDirection}</Table.Cell>
|
|
||||||
<Table.Cell class="table-cell text-right"
|
|
||||||
>{unixToRelativeTime(vote.TimeStamp * 1000)}</Table.Cell
|
|
||||||
>
|
|
||||||
</Table.Row>
|
|
||||||
{/each}
|
|
||||||
</Table.Body>
|
|
||||||
</Table.Root>
|
|
||||||
</div></Card.Content
|
|
||||||
>
|
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
|
|
||||||
|
<Card.Content class="p-6 text-sm">
|
||||||
|
<div class="grid gap-3">
|
||||||
|
<div class="font-semibold">Merit Request Details</div>
|
||||||
|
<ul class="grid gap-3">
|
||||||
|
<li class="flex items-center justify-between">
|
||||||
|
<span class="text-muted-foreground"> Number of Merits being requested </span>
|
||||||
|
<span>{merit.Merits.toLocaleString()}</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center justify-between">
|
||||||
|
<span class="text-muted-foreground">
|
||||||
|
Value in Sats at the time the request was made
|
||||||
|
</span>
|
||||||
|
<span>{merit.Sats.toLocaleString()}</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center justify-between">
|
||||||
|
<span class="text-muted-foreground">
|
||||||
|
Approximate value of {merit.Sats.toLocaleString()} sats in CuckLoserBucks
|
||||||
|
</span>
|
||||||
|
<span>${cuckPrice ? ((merit.Sats / 100000000) * cuckPrice).toFixed(2) : 'Loading'}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<Separator class="my-4" />
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div class="grid gap-3">
|
||||||
|
<div class="font-semibold">Analysis</div>
|
||||||
|
<span class="grid gap-0.5 not-italic text-muted-foreground">
|
||||||
|
<p class="m-1 text-justify">
|
||||||
|
To make it easier to compare the value of each contribution we normalize the hourly
|
||||||
|
rate to $50 CuckLoserBucks an hour.
|
||||||
|
</p>
|
||||||
|
<p class="m-1 text-justify">
|
||||||
|
At this rate, the contributor is claiming to have worked for {referenceTime} solving this
|
||||||
|
problem.
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="grid auto-rows-max gap-3">
|
||||||
|
<div class="font-semibold">Reference Time</div>
|
||||||
|
<div class="text-muted-foreground">{referenceTime}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator class="my-4" />
|
||||||
|
<div class="font-semibold">Votes</div>
|
||||||
|
{#if $votes.size == 0}<Alert
|
||||||
|
><Info />Waiting for existing <span class="italic">{parsedRocket.Name()}</span> Merit holders
|
||||||
|
to vote</Alert
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
<Table.Root>
|
||||||
|
<Table.Body>
|
||||||
|
{#each $votes as [id, vote], _ (id)}
|
||||||
|
<Table.Row
|
||||||
|
on:click={() => {
|
||||||
|
console.log(vote.Event.rawEvent());
|
||||||
|
}}
|
||||||
|
class="cursor-pointer {vote.VoteDirection == 'ratify'
|
||||||
|
? 'bg-lime-600'
|
||||||
|
: 'bg-red-700'} {vote.VoteDirection == 'ratify'
|
||||||
|
? 'hover:bg-lime-700'
|
||||||
|
: 'hover:bg-red-800'}"
|
||||||
|
>
|
||||||
|
<Table.Cell>
|
||||||
|
<div class="flex flex-nowrap">
|
||||||
|
<Avatar
|
||||||
|
ndk={$ndk}
|
||||||
|
pubkey={vote.Pubkey}
|
||||||
|
class="h-10 w-10 flex-none rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
<Name
|
||||||
|
ndk={$ndk}
|
||||||
|
pubkey={vote.Pubkey}
|
||||||
|
class="inline-block max-w-32 truncate p-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell class="hidden text-left md:table-cell">{vote.VoteDirection}</Table.Cell>
|
||||||
|
<Table.Cell class="table-cell text-right"
|
||||||
|
>{unixToRelativeTime(vote.TimeStamp * 1000)}</Table.Cell
|
||||||
|
>
|
||||||
|
</Table.Row>
|
||||||
|
{/each}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="text-xs" on:click={() => console.log(merit.Event.rawEvent())}
|
||||||
|
>print to console</a
|
||||||
|
></Card.Content
|
||||||
|
>
|
||||||
|
|
||||||
<Card.Footer class="flex flex-row justify-center border-t px-6 py-3 text-center {background}">
|
<Card.Footer class="flex flex-row justify-center border-t px-6 py-3 text-center {background}">
|
||||||
{#if merit.IncludedInRocketState(parsedRocket)}
|
{#if merit.IncludedInRocketState(parsedRocket)}
|
||||||
<span class="scroll-m-20 text-lg font-semibold tracking-tight md:text-xl">APPROVED</span>
|
<span class="scroll-m-20 text-lg font-semibold tracking-tight md:text-xl">APPROVED</span>
|
||||||
|
|||||||
@@ -10,20 +10,19 @@
|
|||||||
import { currentUser } from '@/stores/session';
|
import { currentUser } from '@/stores/session';
|
||||||
import Login from './Login.svelte';
|
import Login from './Login.svelte';
|
||||||
import { NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk';
|
import { NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk';
|
||||||
|
import { TelegramBrand } from 'svelte-awesome-icons';
|
||||||
|
|
||||||
let email: string;
|
let email: string = '';
|
||||||
|
let fax: string = '';
|
||||||
$: emailInValid = true;
|
$: emailInValid = true;
|
||||||
$: emailError = '';
|
$: emailError = 'Email is invalid';
|
||||||
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
|
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
|
||||||
|
|
||||||
$: if (email) {
|
$: {
|
||||||
if (!emailRegex.test(email)) {
|
emailError = emailRegex.test(email) ? '' : 'Email is invalid';
|
||||||
emailInValid = true;
|
}
|
||||||
emailError = 'Email is invalid';
|
$: {
|
||||||
} else {
|
emailInValid = emailRegex.test(email) ? false : true;
|
||||||
emailInValid = false;
|
|
||||||
emailError = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function Subscribe() {
|
async function Subscribe() {
|
||||||
@@ -84,14 +83,15 @@
|
|||||||
variant="nostr"
|
variant="nostr"
|
||||||
class="flex h-8 shrink-0 items-center justify-center rounded-sm"
|
class="flex h-8 shrink-0 items-center justify-center rounded-sm"
|
||||||
>
|
>
|
||||||
Nostrocket is totally not ready yet but whatever
|
u r so early
|
||||||
</Badge>
|
</Badge>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
<Dialog.Content>
|
<Dialog.Content>
|
||||||
<Dialog.Header>
|
<Dialog.Header>
|
||||||
<Dialog.Title>Subscribe for Updates</Dialog.Title>
|
<Dialog.Title>Subscribe for Updates</Dialog.Title>
|
||||||
<Dialog.Description>
|
<Dialog.Description>
|
||||||
Subscribe now and we'll ping you when there are new releases/features
|
Nostrocket is under active development, many things are broken. Subscribe now and we'll ping
|
||||||
|
you when there are new releases and new features.
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
<div class="flex flex-col gap-4 py-4">
|
<div class="flex flex-col gap-4 py-4">
|
||||||
{#if $currentUser}
|
{#if $currentUser}
|
||||||
@@ -101,19 +101,32 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<Separator />
|
<Separator />
|
||||||
<span class="ml-auto mr-auto flex"
|
<span class="ml-auto mr-auto flex"
|
||||||
>If you don't use nostr, you can subscribe to updates with an email address instead</span
|
>If you don't use nostr, that's SAD! Whatever, I guess we can fax you or email you or
|
||||||
|
something</span
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-4 items-center gap-4">
|
<div class="grid grid-cols-4 items-center gap-4">
|
||||||
<Label for="email" class="text-right">Email</Label>
|
<Label for="email" class="text-right">Email</Label>
|
||||||
<Input bind:value={email} id="email" placeholder="Your email" class="col-span-3" />
|
<Input bind:value={email} id="email" placeholder="Your email" class="col-span-3" />
|
||||||
</div>
|
</div>
|
||||||
{#if emailError}
|
<!-- {#if emailError}
|
||||||
<div class="ml-4 p-0 text-sm text-red-500">{emailError}</div>
|
<div class="ml-4 p-0 text-sm text-red-500">{emailError}</div>
|
||||||
|
{/if} -->
|
||||||
|
<div class="grid grid-cols-4 items-center gap-4">
|
||||||
|
<Label for="fax" class="text-right">Fax number</Label>
|
||||||
|
<Input bind:value={fax} id="email" placeholder="Your fax number" class="col-span-3" />
|
||||||
|
</div>
|
||||||
|
{#if fax.length > 0}
|
||||||
|
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=1" allow="autoplay"
|
||||||
|
></iframe>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Button disabled={emailInValid} on:click={SubmitEmailAndSubscribe}
|
<Button disabled={emailInValid} on:click={SubmitEmailAndSubscribe}
|
||||||
>Please email me with updates</Button
|
>{emailError ? emailError : "I'm lame, please email me with updates"}</Button
|
||||||
>
|
>
|
||||||
|
<Separator />
|
||||||
|
<a href="https://t.me/nostrocket" class="flex flex-nowrap">
|
||||||
|
<TelegramBrand class="mr-2" /> Join the Telegram Group
|
||||||
|
</a>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
import { ndk } from '@/ndk';
|
import { ndk } from '@/ndk';
|
||||||
import { derived, writable } from 'svelte/store';
|
import { derived, writable } from 'svelte/store';
|
||||||
import ProductGroup from './ProductGroup.svelte';
|
import ProductGroup from './ProductGroup.svelte';
|
||||||
|
import CreateNewProduct from './CreateNewProduct.svelte';
|
||||||
|
import CreateMeritRequest from './CreateMeritRequest.svelte';
|
||||||
|
|
||||||
export let rocket: Rocket;
|
export let rocket: Rocket;
|
||||||
export let unratifiedZaps: Map<string, number>;
|
export let unratifiedZaps: Map<string, number>;
|
||||||
@@ -51,10 +53,11 @@
|
|||||||
<Card.Title>Products and Purchases</Card.Title>
|
<Card.Title>Products and Purchases</Card.Title>
|
||||||
<Card.Description></Card.Description>
|
<Card.Description></Card.Description>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
|
|
||||||
<Card.Content class="grid grid-cols-1 gap-2">
|
<Card.Content class="grid grid-cols-1 gap-2">
|
||||||
{#each $groups as [identifier, products] (identifier)}
|
{#each $groups as [identifier, products] (identifier)}
|
||||||
<ProductGroup {products} {rocket} bind:unratifiedZaps />
|
<ProductGroup {products} {rocket} bind:unratifiedZaps />
|
||||||
{/each}
|
{/each}
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
<Card.Footer></Card.Footer>
|
<Card.Footer><CreateNewProduct rocketEvent={rocket.Event} /></Card.Footer>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
|||||||
@@ -2,17 +2,15 @@
|
|||||||
import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js';
|
import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js';
|
||||||
import Button from '@/components/ui/button/button.svelte';
|
import Button from '@/components/ui/button/button.svelte';
|
||||||
import * as Card from '@/components/ui/card';
|
import * as Card from '@/components/ui/card';
|
||||||
|
import { Rocket } from '@/event_helpers/rockets';
|
||||||
import type { NDKEvent } from '@nostr-dev-kit/ndk';
|
import type { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import CreateMeritRequest from './CreateMeritRequest.svelte';
|
import BitcoinAssociations from './AssociatedBitcoinAddresses.svelte';
|
||||||
import CreateNewProduct from './CreateNewProduct.svelte';
|
|
||||||
import MeritRequests from './MeritRequests.svelte';
|
import MeritRequests from './MeritRequests.svelte';
|
||||||
import MeritsAndSatflow from './MeritsAndSatflow.svelte';
|
import MeritsAndSatflow from './MeritsAndSatflow.svelte';
|
||||||
import ProductFomo from './ProductFomo.svelte';
|
import ProductFomo from './ProductFomo.svelte';
|
||||||
import ProposedProducts from './ProposedProducts.svelte';
|
import ProposedProducts from './ProposedProducts.svelte';
|
||||||
import Todo from './Todo.svelte';
|
import Todo from './Todo.svelte';
|
||||||
import UpdateMission from './UpdateMission.svelte';
|
import UpdateMission from './UpdateMission.svelte';
|
||||||
import { Rocket } from '@/event_helpers/rockets';
|
|
||||||
import BitcoinAssociations from './BitcoinAssociations.svelte';
|
|
||||||
|
|
||||||
export let rocket: NDKEvent;
|
export let rocket: NDKEvent;
|
||||||
|
|
||||||
@@ -47,8 +45,6 @@
|
|||||||
<Card.Title class="pb-4">Actions</Card.Title>
|
<Card.Title class="pb-4">Actions</Card.Title>
|
||||||
<Card.Description class="flex flex-wrap gap-2">
|
<Card.Description class="flex flex-wrap gap-2">
|
||||||
<UpdateMission rocketEvent={rocket} />
|
<UpdateMission rocketEvent={rocket} />
|
||||||
<CreateNewProduct rocketEvent={rocket} />
|
|
||||||
<CreateMeritRequest rocketEvent={rocket} />
|
|
||||||
<Button
|
<Button
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
console.log(rocket.rawEvent());
|
console.log(rocket.rawEvent());
|
||||||
@@ -58,10 +54,5 @@
|
|||||||
</Card.Header>
|
</Card.Header>
|
||||||
<Card.Footer></Card.Footer>
|
<Card.Footer></Card.Footer>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
|
||||||
<Todo
|
|
||||||
className="sm:col-span-3"
|
|
||||||
text={['delete rocket (if current user is rocket creator) - publish deletion request']}
|
|
||||||
/>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,19 +32,19 @@
|
|||||||
.publish()
|
.publish()
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
console.log(x);
|
console.log(x);
|
||||||
|
if (direction === 'ratify') {
|
||||||
|
let content = `I've voted to ratify your merit request! ${merit.Problem()} \n\n ${merit.SolutionURL() ? merit.SolutionURL() : ''}`;
|
||||||
|
prepareMeritNoteEvent({
|
||||||
|
ndk,
|
||||||
|
merit,
|
||||||
|
content
|
||||||
|
})
|
||||||
|
.publish()
|
||||||
|
.then((x) => {
|
||||||
|
console.log(x);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (direction === 'ratify') {
|
|
||||||
let content = `I've voted to ratify your merit request! ${merit.Problem()} \n\n ${merit.Solution() ? merit.Solution() : ''}`;
|
|
||||||
prepareMeritNoteEvent({
|
|
||||||
ndk,
|
|
||||||
merit,
|
|
||||||
content
|
|
||||||
})
|
|
||||||
.publish()
|
|
||||||
.then((x) => {
|
|
||||||
console.log(x);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: currentUserHasVotepower = false;
|
$: currentUserHasVotepower = false;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class MeritRequest {
|
|||||||
}
|
}
|
||||||
return _problem;
|
return _problem;
|
||||||
}
|
}
|
||||||
Solution(): URL | undefined {
|
SolutionURL(): URL | undefined {
|
||||||
let _solution: URL | undefined = undefined;
|
let _solution: URL | undefined = undefined;
|
||||||
for (let solution of this.Event.getMatchingTags('solution')) {
|
for (let solution of this.Event.getMatchingTags('solution')) {
|
||||||
if (solution && solution.length > 2 && solution[1] == 'url') {
|
if (solution && solution.length > 2 && solution[1] == 'url') {
|
||||||
@@ -34,6 +34,15 @@ export class MeritRequest {
|
|||||||
}
|
}
|
||||||
return _solution;
|
return _solution;
|
||||||
}
|
}
|
||||||
|
SolutionText(): string | undefined {
|
||||||
|
let _solution: string | undefined = undefined;
|
||||||
|
for (let solution of this.Event.getMatchingTags('solution')) {
|
||||||
|
if (solution && solution.length > 2 && solution[1] == 'text') {
|
||||||
|
_solution = solution[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _solution;
|
||||||
|
}
|
||||||
IncludedInRocketState(rocket: Rocket): boolean {
|
IncludedInRocketState(rocket: Rocket): boolean {
|
||||||
let included = rocket.ApprovedMeritRequests();
|
let included = rocket.ApprovedMeritRequests();
|
||||||
return Boolean(included.get(this.ID));
|
return Boolean(included.get(this.ID));
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import { BloomFilter } from 'bloomfilter';
|
|||||||
|
|
||||||
export class Rocket {
|
export class Rocket {
|
||||||
Event: NDKEvent;
|
Event: NDKEvent;
|
||||||
|
ATag(): NDKTag {
|
||||||
|
return ['a', `31108:${this.Event.pubkey}:${this.Event.dTag}`];
|
||||||
|
}
|
||||||
private Bloom(): BloomFilter {
|
private Bloom(): BloomFilter {
|
||||||
let b = new BloomFilter(
|
let b = new BloomFilter(
|
||||||
64 * 256, // bits to allocate.
|
64 * 256, // bits to allocate.
|
||||||
|
|||||||
@@ -82,17 +82,24 @@ export function formatSats(sats: number): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCuckPrice(): Promise<number | Error> {
|
export async function getCuckPrice(): Promise<number> {
|
||||||
try {
|
return new Promise((resolve, reject) => {
|
||||||
var url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
|
var url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
|
||||||
var symbol = 'USD';
|
var symbol = 'USD';
|
||||||
const data = await fetch(url);
|
fetch(url)
|
||||||
const json = await data.json();
|
.then((r) => {
|
||||||
const cuckPrice = parseFloat(json.bpi[symbol].rate.replace(/,/g, ''));
|
r.json()
|
||||||
return cuckPrice;
|
.then((j) => {
|
||||||
} catch (e) {
|
resolve(parseFloat(j.bpi[symbol].rate.replace(/,/g, '')));
|
||||||
return e as Error;
|
})
|
||||||
}
|
.catch((e) => {
|
||||||
|
reject(e as Error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
reject(e as Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function parseProblem(problem: string) {
|
export async function parseProblem(problem: string) {
|
||||||
|
|||||||
@@ -7,20 +7,19 @@ import { browser } from '$app/environment';
|
|||||||
const _ndk = new NDKSvelte({
|
const _ndk = new NDKSvelte({
|
||||||
explicitRelayUrls: [
|
explicitRelayUrls: [
|
||||||
'wss://purplepag.es',
|
'wss://purplepag.es',
|
||||||
'wss://relay.higlighter.com',
|
|
||||||
'wss://relay.nostr.band',
|
'wss://relay.nostr.band',
|
||||||
'wss://nos.lol',
|
'wss://nos.lol',
|
||||||
'wss://relay.nostrocket.org',
|
'wss://relay.nostrocket.org',
|
||||||
'wss://nostr.mutinywallet.com',
|
'wss://nostr.mutinywallet.com',
|
||||||
'wss://relay.damus.io'
|
'wss://relay.damus.io'
|
||||||
],
|
],
|
||||||
enableOutboxModel: false,
|
enableOutboxModel: false
|
||||||
//clientNip89: "31990:fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52:1716498133442",
|
//clientNip89: "31990:fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52:1716498133442",
|
||||||
});
|
});
|
||||||
|
|
||||||
//we need to check for browser environment before calling window because svelte is slightly retarded when used client side only
|
//we need to check for browser environment before calling window because svelte is slightly retarded when used client side only
|
||||||
// if (browser && window.indexedDB) {
|
if (browser && window.indexedDB) {
|
||||||
// _ndk.cacheAdapter = new NDKCacheAdapterDexie({ dbName: 'gulag' });
|
_ndk.cacheAdapter = new NDKCacheAdapterDexie({ dbName: 'gulag' });
|
||||||
// }
|
}
|
||||||
|
|
||||||
export const ndk = writable(_ndk);
|
export const ndk = writable(_ndk);
|
||||||
|
|||||||
1
src/routes/help/+page.svelte
Normal file
1
src/routes/help/+page.svelte
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Wouldn't it be cool to have a nostr help thread here?
|
||||||
1
src/routes/inbox/+page.svelte
Normal file
1
src/routes/inbox/+page.svelte
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<img src="https://i.imgflip.com/908qyq.jpg" />
|
||||||
8
src/routes/problems/+page.svelte
Normal file
8
src/routes/problems/+page.svelte
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
This is a work in progress, for now please have a look at the <a
|
||||||
|
class="underline"
|
||||||
|
href="https://nostrocket.github.io/oxygen/nr/Nostrocket/problems/d0afd68b5cafa58382edb38b7ac7feef229a916f22330922e4be6cd22193b1a5?tab=problem"
|
||||||
|
>Oxygen problem tracker</a
|
||||||
|
>.
|
||||||
@@ -96,9 +96,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<Alert.Root>
|
<Alert.Root>
|
||||||
<Alert.Description
|
<Alert.Description>No products found!</Alert.Description>
|
||||||
>Currently, there are no products on the mainnet; you can check the testnet.</Alert.Description
|
|
||||||
>
|
|
||||||
</Alert.Root>
|
</Alert.Root>
|
||||||
{/if}
|
{/if}
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
@@ -106,8 +104,7 @@
|
|||||||
<Alert.Root class="my-2">
|
<Alert.Root class="my-2">
|
||||||
<ExclamationTriangle class="h-4 w-4" />
|
<ExclamationTriangle class="h-4 w-4" />
|
||||||
<Alert.Title>Note</Alert.Title>
|
<Alert.Title>Note</Alert.Title>
|
||||||
<Alert.Description
|
<Alert.Description>The following products are for testing and are not real.</Alert.Description
|
||||||
>The following products are for testing purposes only. Please do not send real Bitcoin.</Alert.Description
|
|
||||||
>
|
>
|
||||||
</Alert.Root>
|
</Alert.Root>
|
||||||
{#each testnet as [rocket, groups] (rocket.Event.id)}
|
{#each testnet as [rocket, groups] (rocket.Event.id)}
|
||||||
|
|||||||
@@ -14,50 +14,62 @@
|
|||||||
|
|
||||||
export let rockets: Readable<Rocket[]>;
|
export let rockets: Readable<Rocket[]>;
|
||||||
|
|
||||||
let validAssociationRequests = derived([associations, rockets], ([$associationEvents, $rockets]) => {
|
let validAssociationRequests = derived(
|
||||||
let valid = new Map<string, BitcoinAssociation>();
|
[associations, rockets],
|
||||||
for (let e of $associationEvents) {
|
([$associationEvents, $rockets]) => {
|
||||||
let a = new BitcoinAssociation(e)
|
let valid = new Map<string, BitcoinAssociation>();
|
||||||
if (a.Validate()) {
|
for (let e of $associationEvents) {
|
||||||
valid.set(a.Event.id, a);
|
let a = new BitcoinAssociation(e);
|
||||||
|
if (a.Validate()) {
|
||||||
|
valid.set(a.Event.id, a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return valid;
|
||||||
}
|
}
|
||||||
return valid
|
);
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
let validAgainstMyRocket = derived([currentUser, validAssociationRequests, rockets], ([$currentUser, $associationRequests, $rockets]) => {
|
let validAgainstMyRocket = derived(
|
||||||
let valid:{rocket:Rocket, association:BitcoinAssociation}[] = []
|
[currentUser, validAssociationRequests, rockets],
|
||||||
if ($currentUser) {
|
([$currentUser, $associationRequests, $rockets]) => {
|
||||||
if ($rockets.length > 0) {
|
let valid: { rocket: Rocket; association: BitcoinAssociation }[] = [];
|
||||||
for (let [_, a] of $associationRequests) {
|
if ($currentUser) {
|
||||||
for (let r of $rockets) {
|
if ($rockets.length > 0) {
|
||||||
if (r.VotePowerForPubkey($currentUser.pubkey)) {
|
for (let [_, a] of $associationRequests) {
|
||||||
if (!r.BitcoinAssociations().get(a.Pubkey)) { //todo: get current list of Bitcoin associations, if (this is not included)
|
for (let r of $rockets) {
|
||||||
valid.push({rocket:r, association:a})
|
if (r.VotePowerForPubkey($currentUser.pubkey)) {
|
||||||
|
if (!r.BitcoinAssociations().get(a.Pubkey)) {
|
||||||
|
//todo: get current list of Bitcoin associations, if (this is not included)
|
||||||
|
valid.push({ rocket: r, association: a });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
validAgainstMyRocket.subscribe((requests) => {
|
||||||
|
if ($rockets && $rockets.length > 0 && currentUser && $currentUser) {
|
||||||
|
for (let { rocket, association } of requests) {
|
||||||
|
if (
|
||||||
|
rocket.VotePowerForPubkey($currentUser.pubkey) &&
|
||||||
|
!rocket.Included(association.Event.id)
|
||||||
|
) {
|
||||||
|
for (let a of association.Event.getMatchingTags('a')) {
|
||||||
|
if (a.length == 2 && a[1] == rocket.ATag()[1]) {
|
||||||
|
let e = rocket.UpsertBitcoinAssociation(association);
|
||||||
|
if (e) {
|
||||||
|
e.ndk = $ndk;
|
||||||
|
e.publish().then((x) => {
|
||||||
|
console.log(x, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return valid;
|
|
||||||
})
|
|
||||||
|
|
||||||
validAgainstMyRocket.subscribe((requests) => {
|
|
||||||
if ($rockets && $rockets.length > 0 && currentUser && $currentUser) {
|
|
||||||
for (let {rocket,association} of requests) {
|
|
||||||
if (rocket.VotePowerForPubkey($currentUser.pubkey)) {
|
|
||||||
console.log("todo: check which rocket user has specified")
|
|
||||||
let e = rocket.UpsertBitcoinAssociation(association)
|
|
||||||
if (e) {
|
|
||||||
e.ndk = $ndk
|
|
||||||
e.publish().then(x=>{
|
|
||||||
console.log(x, e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
import { base } from '$app/paths';
|
import { base } from '$app/paths';
|
||||||
import { Button } from '$lib/components/ui/button/index.js';
|
import { Button } from '$lib/components/ui/button/index.js';
|
||||||
import * as Card from '$lib/components/ui/card/index.js';
|
import * as Card from '$lib/components/ui/card/index.js';
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
<Card.Root class="flex h-full w-full flex-col">
|
<Card.Root class="flex h-full w-full flex-col">
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
<Card.Title>Proof of Work</Card.Title>
|
<Card.Title>Proof of Work</Card.Title>
|
||||||
<Card.Description>I have skills and I'm ready to work for sats.</Card.Description>
|
<Card.Description>I want to work for sats</Card.Description>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<img src="{base}/_work.png" alt="Proof of Work" />
|
<img src="{base}/_work.png" alt="Proof of Work" />
|
||||||
<Card.Content class="grid gap-4 !pt-2">
|
<Card.Content class="grid gap-4 !pt-2">
|
||||||
@@ -33,38 +34,45 @@
|
|||||||
<ul class="ml-6 list-disc [&>li]:mt-2">
|
<ul class="ml-6 list-disc [&>li]:mt-2">
|
||||||
<li>Solve problems for fun and profit</li>
|
<li>Solve problems for fun and profit</li>
|
||||||
<li>No meetings, no scrum, no task assignment, no schedules</li>
|
<li>No meetings, no scrum, no task assignment, no schedules</li>
|
||||||
<li>Work only on what inspires you</li>
|
<li>Only work on fully open source projects</li>
|
||||||
<li>
|
<li>
|
||||||
No interview process or CV's required, apply by sending a pull request to solve a
|
No interview process! Apply by choosing a problem to solve and sending a pull request
|
||||||
small problem
|
|
||||||
</li>
|
</li>
|
||||||
<li>Get paid in Sats for every pull request</li>
|
<li>Get paid in Sats, or equity in the project's satflow</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
<Card.Footer class="mt-auto">
|
<Card.Footer class="mt-auto">
|
||||||
<Button class="w-full">Apply Now</Button>
|
<Button
|
||||||
|
on:click={() => {
|
||||||
|
window.location.href = 'https://github.com/nostrocket/hypergolic/issues';
|
||||||
|
}}
|
||||||
|
class="w-full">Apply Now</Button
|
||||||
|
>
|
||||||
</Card.Footer>
|
</Card.Footer>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
<Card.Root class="flex h-full w-full flex-col">
|
<Card.Root class="flex h-full w-full flex-col">
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
<Card.Title>Proof of Sats</Card.Title>
|
<Card.Title>Proof of Sats</Card.Title>
|
||||||
<Card.Description>I have Sats, and I want a slice of a Rocket's satflow.</Card.Description>
|
<Card.Description>I want to invest in the satflow of a Rocket</Card.Description>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<img src="{base}/_sats.png" width="100%" alt="Proof of Sats" />
|
<img src="{base}/_sats.png" width="100%" alt="Proof of Sats" />
|
||||||
<Card.Content class="grid gap-4 !pt-2">
|
<Card.Content class="grid gap-4 !pt-2">
|
||||||
<div>
|
<div>
|
||||||
<ul class="ml-6 list-disc [&>li]:mt-2">
|
<ul class="ml-6 list-disc [&>li]:mt-2">
|
||||||
<li>Nostrocket is the most efficient way (ever) to invest in emerging technologies</li>
|
|
||||||
<li>
|
<li>
|
||||||
Every Sat goes directly to a contributor for solving a problem in the critical path
|
Nostrocket is the most efficient way (ever) to invest in emerging technologies like
|
||||||
towards (more) revenue
|
nostr
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Revenue goes straight from the paying customer's wallet into your wallet as an equity
|
Your sats go directly to a contributor for work that has already been completed and
|
||||||
holder
|
solves a problem in the critical path to (more) revenue
|
||||||
</li>
|
</li>
|
||||||
<li>No KYC, ever.</li>
|
<li>
|
||||||
|
Nostrocket is non-custodial - revenue goes directly from the paying customer to your
|
||||||
|
wallet as an equity holder
|
||||||
|
</li>
|
||||||
|
<li>No KYC (ever).</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
@@ -76,7 +84,7 @@
|
|||||||
<Card.Header>
|
<Card.Header>
|
||||||
<Card.Title>Launch a new Rocket</Card.Title>
|
<Card.Title>Launch a new Rocket</Card.Title>
|
||||||
<Card.Description
|
<Card.Description
|
||||||
>I'm working on my own project and I want other people to join me.</Card.Description
|
>I have an open source project and I want to make it profitable</Card.Description
|
||||||
>
|
>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<img src="{base}/_rocket.png" width="100%" alt="Launch a new Rocket" />
|
<img src="{base}/_rocket.png" width="100%" alt="Launch a new Rocket" />
|
||||||
@@ -84,16 +92,16 @@
|
|||||||
<div>
|
<div>
|
||||||
<ul class="ml-6 list-disc [&>li]:mt-2">
|
<ul class="ml-6 list-disc [&>li]:mt-2">
|
||||||
<li>
|
<li>
|
||||||
Attract the people you need to make your project a success (despite your lack of
|
Attract the right people to make your project a success (despite your lack of funding)
|
||||||
funding)
|
</li>
|
||||||
|
<li>No financial accounting, capital raising, compliance, business plans, etc.</li>
|
||||||
|
<li>
|
||||||
|
Make your project socially scalable by becoming radically fair and meritocratic to all
|
||||||
|
contributors and investors.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Nostrocket obsoletes the need for financial accounting, capital raising, compliance,
|
Make your project immune to parasites by giving them nowhere to hide and nothing to
|
||||||
business plans, etc.
|
leech from.
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Nostrocket is radically meritocratic and fair to project creators, contributors, and
|
|
||||||
investors, for the entire life of the project
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -84,10 +84,7 @@
|
|||||||
<Alert.Root class="my-2">
|
<Alert.Root class="my-2">
|
||||||
<ExclamationTriangle class="h-4 w-4" />
|
<ExclamationTriangle class="h-4 w-4" />
|
||||||
<Alert.Title>Note</Alert.Title>
|
<Alert.Title>Note</Alert.Title>
|
||||||
<Alert.Description
|
<Alert.Description>The following rockets are for testing purposes only.</Alert.Description>
|
||||||
>The following rocket is for testing purposes only. Any rocket with "test" in its name is
|
|
||||||
intended solely for testing.</Alert.Description
|
|
||||||
>
|
|
||||||
</Alert.Root>
|
</Alert.Root>
|
||||||
<div class="grid grid-cols-1 gap-2 md:grid-cols-2 2xl:grid-cols-3 3xl:grid-cols-4">
|
<div class="grid grid-cols-1 gap-2 md:grid-cols-2 2xl:grid-cols-3 3xl:grid-cols-4">
|
||||||
{#each testnet as rocket (`${rocket.Event.pubkey}${rocket.Name()}`)}
|
{#each testnet as rocket (`${rocket.Event.pubkey}${rocket.Name()}`)}
|
||||||
|
|||||||
Reference in New Issue
Block a user