Merge branch 'main' into fix/currency-config-116
This commit is contained in:
commit
9b95ae22a9
9 changed files with 486 additions and 70 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -23,3 +23,6 @@ node_modules
|
||||||
*.pyo
|
*.pyo
|
||||||
*.pyc
|
*.pyc
|
||||||
*.env
|
*.env
|
||||||
|
|
||||||
|
# Claude Code config
|
||||||
|
CLAUDE.md
|
||||||
104
CLAUDE.md
Normal file
104
CLAUDE.md
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Nostr Market is an LNbits extension implementing NIP-15 (decentralized marketplace protocol) on Nostr. It enables merchants to create webshops (stalls) and sell products with Lightning Network payments, featuring encrypted customer-merchant communication via NIP-04.
|
||||||
|
|
||||||
|
**Prerequisites:** Requires the LNbits [nostrclient](https://github.com/lnbits/nostrclient) extension to be installed and configured.
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
All commands are in the Makefile:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make format # Run prettier, black, and ruff formatters
|
||||||
|
make check # Run mypy, pyright, black check, ruff check, prettier check
|
||||||
|
make test # Run pytest with debug mode
|
||||||
|
make all # Run format and check
|
||||||
|
```
|
||||||
|
|
||||||
|
Individual tools:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make black # Format Python files
|
||||||
|
make ruff # Check and fix Python linting
|
||||||
|
make mypy # Static type checking
|
||||||
|
make pyright # Python static type checker
|
||||||
|
make prettier # Format JS/HTML/CSS files
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local Development Setup
|
||||||
|
|
||||||
|
To run checks locally, install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Python autotools dependencies (needed for secp256k1)
|
||||||
|
sudo apt-get install -y automake autoconf libtool
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
uv sync
|
||||||
|
|
||||||
|
# Install Node dependencies (for prettier)
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Run all checks
|
||||||
|
make check
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Core Layers
|
||||||
|
|
||||||
|
1. **API Layer** (`views_api.py`) - REST endpoints for merchants, stalls, products, zones, orders, direct messages
|
||||||
|
2. **Business Logic** (`services.py`) - Order processing, Nostr event signing/publishing, message routing, invoice handling
|
||||||
|
3. **Data Layer** (`crud.py`) - Async SQLite operations via LNbits db module
|
||||||
|
4. **Models** (`models.py`) - Pydantic models for all entities
|
||||||
|
|
||||||
|
### Nostr Integration (`nostr/`)
|
||||||
|
|
||||||
|
- `nostr_client.py` - WebSocket client connecting to nostrclient extension for relay communication
|
||||||
|
- `event.py` - Nostr event model, serialization, ID computation (SHA256), Schnorr signatures
|
||||||
|
|
||||||
|
### Background Tasks (`__init__.py`, `tasks.py`)
|
||||||
|
|
||||||
|
Three permanent async tasks:
|
||||||
|
|
||||||
|
- `wait_for_paid_invoices()` - Lightning payment listener
|
||||||
|
- `wait_for_nostr_events()` - Incoming Nostr message processor
|
||||||
|
- `_subscribe_to_nostr_client()` - WebSocket connection manager
|
||||||
|
|
||||||
|
### Frontend (`static/`, `templates/`)
|
||||||
|
|
||||||
|
- Merchant dashboard: `templates/nostrmarket/index.html`
|
||||||
|
- Customer marketplace: `templates/nostrmarket/market.html` with Vue.js/Quasar in `static/market/`
|
||||||
|
- Use Quasar UI components when possible: https://quasar.dev/components
|
||||||
|
|
||||||
|
### Key Data Models
|
||||||
|
|
||||||
|
- **Merchant** - Shop owner with Nostr keypair, handles event signing and DM encryption
|
||||||
|
- **Stall** - Individual shop with products and shipping zones (kind 30017)
|
||||||
|
- **Product** - Items for sale with categories, images, quantity (kind 30018)
|
||||||
|
- **Zone** - Shipping configuration by region
|
||||||
|
- **Order** - Customer purchases with Lightning invoice tracking
|
||||||
|
- **DirectMessage** - Encrypted chat (NIP-04)
|
||||||
|
- **Customer** - Buyer profile with Nostr pubkey
|
||||||
|
|
||||||
|
### Key Patterns
|
||||||
|
|
||||||
|
- **Nostrable Interface** - Base class for models convertible to Nostr events (`to_nostr_event()`, `to_nostr_delete_event()`)
|
||||||
|
- **Parameterized Replaceable Events** - Stalls (kind 30017) and Products (kind 30018) per NIP-33
|
||||||
|
- **AES-256 Encryption** - Customer-merchant DMs use shared secret from ECDH
|
||||||
|
- **JSON Meta Fields** - Complex data (zones, items, config) stored as JSON in database
|
||||||
|
|
||||||
|
### Cryptography (`helpers.py`)
|
||||||
|
|
||||||
|
- Schnorr signatures for Nostr events
|
||||||
|
- NIP-04 encryption/decryption
|
||||||
|
- Key derivation and bech32 encoding (npub/nsec)
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
- Always check GitHub Actions after pushing to verify CI passes
|
||||||
|
- Run `make check` locally before pushing to catch issues early
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Nostr Market",
|
"name": "Nostr Market",
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"short_description": "Nostr Webshop/market on LNbits",
|
"short_description": "Nostr Webshop/market on LNbits",
|
||||||
"tile": "/nostrmarket/static/images/bitcoin-shop.png",
|
"tile": "/nostrmarket/static/images/nostr-market.png",
|
||||||
"min_lnbits_version": "1.4.0",
|
"min_lnbits_version": "1.4.0",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ window.app.component('shipping-zones', {
|
||||||
currencies: [],
|
currencies: [],
|
||||||
shippingZoneOptions: [
|
shippingZoneOptions: [
|
||||||
'Free (digital)',
|
'Free (digital)',
|
||||||
'Flat rate',
|
|
||||||
'Worldwide',
|
'Worldwide',
|
||||||
'Europe',
|
'Europe',
|
||||||
'Australia',
|
'Australia',
|
||||||
|
|
@ -27,6 +26,7 @@ window.app.component('shipping-zones', {
|
||||||
'Belgium',
|
'Belgium',
|
||||||
'Brazil',
|
'Brazil',
|
||||||
'Canada',
|
'Canada',
|
||||||
|
'China',
|
||||||
'Denmark',
|
'Denmark',
|
||||||
'Finland',
|
'Finland',
|
||||||
'France',
|
'France',
|
||||||
|
|
@ -34,8 +34,8 @@ window.app.component('shipping-zones', {
|
||||||
'Greece',
|
'Greece',
|
||||||
'Hong Kong',
|
'Hong Kong',
|
||||||
'Hungary',
|
'Hungary',
|
||||||
'Ireland',
|
|
||||||
'Indonesia',
|
'Indonesia',
|
||||||
|
'Ireland',
|
||||||
'Israel',
|
'Israel',
|
||||||
'Italy',
|
'Italy',
|
||||||
'Japan',
|
'Japan',
|
||||||
|
|
@ -59,10 +59,9 @@ window.app.component('shipping-zones', {
|
||||||
'Thailand',
|
'Thailand',
|
||||||
'Turkey',
|
'Turkey',
|
||||||
'Ukraine',
|
'Ukraine',
|
||||||
'United Kingdom**',
|
'United Kingdom',
|
||||||
'United States***',
|
'United States',
|
||||||
'Vietnam',
|
'Vietnam'
|
||||||
'China'
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
123
static/images/generate_logo.py
Normal file
123
static/images/generate_logo.py
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate the Nostr Market logo.
|
||||||
|
Requires: pip install Pillow
|
||||||
|
"""
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw # type: ignore[import-not-found]
|
||||||
|
|
||||||
|
# Render at 4x size for antialiasing
|
||||||
|
scale = 4
|
||||||
|
size = 128 * scale
|
||||||
|
final_size = 128
|
||||||
|
|
||||||
|
# Consistent color scheme with Nostr Proxy
|
||||||
|
dark_purple = (80, 40, 120)
|
||||||
|
light_purple = (140, 100, 180)
|
||||||
|
white = (255, 255, 255)
|
||||||
|
|
||||||
|
margin = 4 * scale
|
||||||
|
|
||||||
|
swoosh_center = ((128 + 100) * scale, -90 * scale)
|
||||||
|
swoosh_radius = 220 * scale
|
||||||
|
|
||||||
|
# Create rounded rectangle mask
|
||||||
|
mask = Image.new("L", (size, size), 0)
|
||||||
|
mask_draw = ImageDraw.Draw(mask)
|
||||||
|
corner_radius = 20 * scale
|
||||||
|
mask_draw.rounded_rectangle(
|
||||||
|
[margin, margin, size - margin, size - margin],
|
||||||
|
radius=corner_radius,
|
||||||
|
fill=255,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create background with swoosh
|
||||||
|
bg = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||||
|
bg_draw = ImageDraw.Draw(bg)
|
||||||
|
bg_draw.rounded_rectangle(
|
||||||
|
[margin, margin, size - margin, size - margin],
|
||||||
|
radius=corner_radius,
|
||||||
|
fill=dark_purple,
|
||||||
|
)
|
||||||
|
bg_draw.ellipse(
|
||||||
|
[
|
||||||
|
swoosh_center[0] - swoosh_radius,
|
||||||
|
swoosh_center[1] - swoosh_radius,
|
||||||
|
swoosh_center[0] + swoosh_radius,
|
||||||
|
swoosh_center[1] + swoosh_radius,
|
||||||
|
],
|
||||||
|
fill=light_purple,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply rounded rectangle mask
|
||||||
|
final = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||||
|
final.paste(bg, mask=mask)
|
||||||
|
draw = ImageDraw.Draw(final)
|
||||||
|
|
||||||
|
center_x, center_y = size // 2, size // 2
|
||||||
|
|
||||||
|
# Shop/storefront - wider and shorter for shop look
|
||||||
|
shop_width = 80 * scale
|
||||||
|
awning_height = 18 * scale
|
||||||
|
body_height = 45 * scale
|
||||||
|
total_height = awning_height + body_height
|
||||||
|
|
||||||
|
shop_left = center_x - shop_width // 2
|
||||||
|
shop_right = center_x + shop_width // 2
|
||||||
|
|
||||||
|
# Center vertically
|
||||||
|
awning_top = center_y - total_height // 2
|
||||||
|
awning_bottom = awning_top + awning_height
|
||||||
|
shop_bottom = awning_bottom + body_height
|
||||||
|
awning_extend = 5 * scale
|
||||||
|
|
||||||
|
# Draw awning background (white base)
|
||||||
|
draw.rectangle(
|
||||||
|
[shop_left - awning_extend, awning_top, shop_right + awning_extend, awning_bottom],
|
||||||
|
fill=white,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Vertical stripes on awning (alternating dark purple)
|
||||||
|
stripe_count = 8
|
||||||
|
stripe_width = (shop_width + 2 * awning_extend) // stripe_count
|
||||||
|
for i in range(1, stripe_count, 2):
|
||||||
|
x_left = shop_left - awning_extend + i * stripe_width
|
||||||
|
draw.rectangle(
|
||||||
|
[x_left, awning_top, x_left + stripe_width, awning_bottom],
|
||||||
|
fill=dark_purple,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Shop body (below awning)
|
||||||
|
draw.rectangle(
|
||||||
|
[shop_left, awning_bottom, shop_right, shop_bottom],
|
||||||
|
fill=white,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Large display window (shop style)
|
||||||
|
window_margin = 8 * scale
|
||||||
|
window_top = awning_bottom + 6 * scale
|
||||||
|
window_bottom = shop_bottom - 6 * scale
|
||||||
|
# Left display window
|
||||||
|
draw.rectangle(
|
||||||
|
[shop_left + window_margin, window_top, center_x - 10 * scale, window_bottom],
|
||||||
|
fill=dark_purple,
|
||||||
|
)
|
||||||
|
# Right display window
|
||||||
|
draw.rectangle(
|
||||||
|
[center_x + 10 * scale, window_top, shop_right - window_margin, window_bottom],
|
||||||
|
fill=dark_purple,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Door (center, dark purple cutout)
|
||||||
|
door_width = 14 * scale
|
||||||
|
door_left = center_x - door_width // 2
|
||||||
|
draw.rectangle(
|
||||||
|
[door_left, window_top, door_left + door_width, shop_bottom],
|
||||||
|
fill=dark_purple,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Downscale with LANCZOS for antialiasing
|
||||||
|
final = final.resize((final_size, final_size), Image.LANCZOS)
|
||||||
|
|
||||||
|
final.save("nostr-market.png")
|
||||||
|
print("Logo saved to nostr-market.png")
|
||||||
BIN
static/images/nostr-market.png
Normal file
BIN
static/images/nostr-market.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
|
|
@ -1,44 +1,213 @@
|
||||||
|
<q-expansion-item
|
||||||
|
icon="help_outline"
|
||||||
|
label="What is Nostr?"
|
||||||
|
header-class="text-weight-medium"
|
||||||
|
>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section class="text-body2">
|
||||||
<p>
|
<p>
|
||||||
Nostr Market<br />
|
<strong>Nostr</strong> (Notes and Other Stuff Transmitted by Relays) is
|
||||||
<small>
|
a decentralized protocol for censorship-resistant communication. Unlike
|
||||||
Created by,
|
traditional platforms, your identity and data aren't controlled by any
|
||||||
<a
|
single company.
|
||||||
class="text-secondary"
|
</p>
|
||||||
target="_blank"
|
<p class="q-mb-none">
|
||||||
style="color: unset"
|
Your Nostr identity is a cryptographic key pair - a public key (npub)
|
||||||
href="https://github.com/talvasconcelos"
|
that others use to find you, and a private key (nsec) that proves you
|
||||||
>Tal Vasconcelos</a
|
are you. Keep your nsec safe and never share it!
|
||||||
>
|
|
||||||
<a
|
|
||||||
class="text-secondary"
|
|
||||||
target="_blank"
|
|
||||||
style="color: unset"
|
|
||||||
href="https://github.com/benarc"
|
|
||||||
>Ben Arc</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
class="text-secondary"
|
|
||||||
target="_blank"
|
|
||||||
style="color: unset"
|
|
||||||
href="https://github.com/motorina0"
|
|
||||||
>motorina0</a
|
|
||||||
></small
|
|
||||||
>
|
|
||||||
</p>
|
</p>
|
||||||
<a
|
|
||||||
class="text-secondary"
|
|
||||||
target="_blank"
|
|
||||||
href="/docs#/nostrmarket"
|
|
||||||
class="text-white"
|
|
||||||
>Swagger REST API Documentation</a
|
|
||||||
>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<a class="text-secondary" target="_blank" href="/nostrmarket/market"
|
|
||||||
><q-tooltip>Visit the market client</q-tooltip
|
|
||||||
><q-icon name="storefront" class="q-mr-sm"></q-icon>Market client</a
|
|
||||||
>
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
|
||||||
|
<q-expansion-item
|
||||||
|
icon="flag"
|
||||||
|
label="Getting Started"
|
||||||
|
header-class="text-weight-medium"
|
||||||
|
>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section class="text-body2">
|
||||||
|
<p><strong>1. Generate or Import Keys</strong></p>
|
||||||
|
<p class="q-mb-md">
|
||||||
|
Create a new Nostr identity or import an existing one using your nsec.
|
||||||
|
Your keys are used to sign all marketplace events.
|
||||||
|
</p>
|
||||||
|
<p><strong>2. Create a Stall</strong></p>
|
||||||
|
<p class="q-mb-md">
|
||||||
|
A stall is your shop. Give it a name, description, and configure
|
||||||
|
shipping zones for delivery.
|
||||||
|
</p>
|
||||||
|
<p><strong>3. Add Products</strong></p>
|
||||||
|
<p class="q-mb-md">
|
||||||
|
List items for sale with images, descriptions, and prices in your
|
||||||
|
preferred currency.
|
||||||
|
</p>
|
||||||
|
<p><strong>4. Publish to Nostr</strong></p>
|
||||||
|
<p class="q-mb-none">
|
||||||
|
Your stall and products are published to Nostr relays where customers
|
||||||
|
can discover them using any compatible marketplace client.
|
||||||
|
</p>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
|
||||||
|
<q-expansion-item
|
||||||
|
icon="storefront"
|
||||||
|
label="For Merchants"
|
||||||
|
header-class="text-weight-medium"
|
||||||
|
>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section class="text-body2">
|
||||||
|
<p>
|
||||||
|
<strong>Decentralized Commerce</strong> - Your shop exists on Nostr
|
||||||
|
relays, not a single server. No platform fees, no deplatforming risk.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Lightning Payments</strong> - Accept instant, low-fee Bitcoin
|
||||||
|
payments via the Lightning Network.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Encrypted Messages</strong> - Communicate privately with
|
||||||
|
customers using NIP-04 encrypted direct messages.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Portable Identity</strong> - Your merchant reputation travels
|
||||||
|
with your Nostr keys across any compatible marketplace.
|
||||||
|
</p>
|
||||||
|
<p class="q-mb-none">
|
||||||
|
<strong>Global Reach</strong> - Your stalls and products are
|
||||||
|
automatically visible on any Nostr marketplace client that supports
|
||||||
|
NIP-15, including Amethyst, Plebeian Market, and others.
|
||||||
|
</p>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
|
||||||
|
<q-expansion-item
|
||||||
|
icon="shopping_cart"
|
||||||
|
label="For Customers"
|
||||||
|
header-class="text-weight-medium"
|
||||||
|
>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section class="text-body2">
|
||||||
|
<p>
|
||||||
|
<strong>Browse the Market</strong> - Use the Market Client to discover
|
||||||
|
stalls and products from merchants around the world.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Pay with Lightning</strong> - Fast, private payments with
|
||||||
|
minimal fees using Bitcoin's Lightning Network.
|
||||||
|
</p>
|
||||||
|
<p class="q-mb-none">
|
||||||
|
<strong>Direct Communication</strong> - Message merchants directly via
|
||||||
|
encrypted Nostr DMs for questions, custom orders, or support.
|
||||||
|
</p>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
|
||||||
|
<q-expansion-item
|
||||||
|
icon="people"
|
||||||
|
label="Contributors"
|
||||||
|
header-class="text-weight-medium"
|
||||||
|
>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section class="text-body2">
|
||||||
|
<p class="q-mb-sm">This extension was created by:</p>
|
||||||
|
<div class="q-gutter-sm">
|
||||||
|
<a
|
||||||
|
href="https://github.com/talvasconcelos"
|
||||||
|
target="_blank"
|
||||||
|
class="text-decoration-none"
|
||||||
|
>
|
||||||
|
<q-chip clickable icon="person">Tal Vasconcelos</q-chip>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/arcbtc"
|
||||||
|
target="_blank"
|
||||||
|
class="text-decoration-none"
|
||||||
|
>
|
||||||
|
<q-chip clickable icon="person">Ben Arc</q-chip>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/motorina0"
|
||||||
|
target="_blank"
|
||||||
|
class="text-decoration-none"
|
||||||
|
>
|
||||||
|
<q-chip clickable icon="person">motorina0</q-chip>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/BenGWeeks"
|
||||||
|
target="_blank"
|
||||||
|
class="text-decoration-none"
|
||||||
|
>
|
||||||
|
<q-chip clickable icon="person">Ben Weeks</q-chip>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
|
||||||
|
<q-separator></q-separator>
|
||||||
|
|
||||||
|
<q-item clickable tag="a" target="_blank" href="/nostrmarket/market">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="storefront" color="primary"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Market Client</q-item-label>
|
||||||
|
<q-item-label caption>Browse and shop from stalls</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-icon name="open_in_new" size="xs"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item clickable tag="a" target="_blank" href="/docs#/nostrmarket">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="api" color="primary"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>API Documentation</q-item-label>
|
||||||
|
<q-item-label caption>Swagger REST API reference</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-icon name="open_in_new" size="xs"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
tag="a"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/nostr-protocol/nips/blob/master/15.md"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="description" color="secondary"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>NIP-15 Specification</q-item-label>
|
||||||
|
<q-item-label caption>Nostr Marketplace protocol</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-icon name="open_in_new" size="xs"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
tag="a"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/lnbits/nostrmarket/issues"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="bug_report" color="warning"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Report Issues / Feedback</q-item-label>
|
||||||
|
<q-item-label caption>GitHub Issues</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-icon name="open_in_new" size="xs"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,23 @@
|
||||||
label="Countries"
|
label="Countries"
|
||||||
v-model="zoneDialog.data.countries"
|
v-model="zoneDialog.data.countries"
|
||||||
></q-select>
|
></q-select>
|
||||||
|
<div class="row items-start">
|
||||||
|
<div class="col q-mr-sm">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
label="Default shipping cost"
|
||||||
|
fill-mask="0"
|
||||||
|
reverse-fill-mask
|
||||||
|
:step="zoneDialog.data.currency != 'sat' ? '0.01' : '1'"
|
||||||
|
type="number"
|
||||||
|
v-model.trim="zoneDialog.data.cost"
|
||||||
|
:error="(zoneDialog.data.currency === 'sat' && zoneDialog.data.cost % 1 !== 0) || (zoneDialog.data.currency !== 'sat' && (zoneDialog.data.cost * 100) % 1 !== 0)"
|
||||||
|
:error-message="zoneDialog.data.currency === 'sat' ? 'Satoshis must be whole numbers' : 'Maximum 2 decimal places allowed'"
|
||||||
|
hint="Additional costs can be set per product"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
<q-select
|
<q-select
|
||||||
:disabled="!!zoneDialog.data.id"
|
:disabled="!!zoneDialog.data.id"
|
||||||
:readonly="!!zoneDialog.data.id"
|
:readonly="!!zoneDialog.data.id"
|
||||||
|
|
@ -55,19 +72,12 @@
|
||||||
dense
|
dense
|
||||||
v-model="zoneDialog.data.currency"
|
v-model="zoneDialog.data.currency"
|
||||||
type="text"
|
type="text"
|
||||||
label="Unit"
|
label="Currency"
|
||||||
:options="currencies"
|
:options="currencies"
|
||||||
|
style="min-width: 100px"
|
||||||
></q-select>
|
></q-select>
|
||||||
<q-input
|
</div>
|
||||||
filled
|
</div>
|
||||||
dense
|
|
||||||
:label="'Cost of Shipping (' + zoneDialog.data.currency + ') *'"
|
|
||||||
fill-mask="0"
|
|
||||||
reverse-fill-mask
|
|
||||||
:step="zoneDialog.data.currency != 'sat' ? '0.01' : '1'"
|
|
||||||
type="number"
|
|
||||||
v-model.trim="zoneDialog.data.cost"
|
|
||||||
></q-input>
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<div v-if="zoneDialog.data.id">
|
<div v-if="zoneDialog.data.id">
|
||||||
<q-btn unelevated color="primary" type="submit">Update</q-btn>
|
<q-btn unelevated color="primary" type="submit">Update</q-btn>
|
||||||
|
|
@ -83,7 +93,7 @@
|
||||||
<q-btn
|
<q-btn
|
||||||
unelevated
|
unelevated
|
||||||
color="primary"
|
color="primary"
|
||||||
:disable="!zoneDialog.data.countries || !zoneDialog.data.countries.length"
|
:disable="!zoneDialog.data.name || !zoneDialog.data.countries || !zoneDialog.data.countries.length || (zoneDialog.data.currency === 'sat' && zoneDialog.data.cost % 1 !== 0) || (zoneDialog.data.currency !== 'sat' && (zoneDialog.data.cost * 100) % 1 !== 0)"
|
||||||
type="submit"
|
type="submit"
|
||||||
>Create Shipping Zone</q-btn
|
>Create Shipping Zone</q-btn
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -166,13 +166,21 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<q-card>
|
<q-card>
|
||||||
|
<q-img
|
||||||
|
src="/nostrmarket/static/market/images/nostr-cover.png"
|
||||||
|
:ratio="3"
|
||||||
|
fit="cover"
|
||||||
|
></q-img>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<h6 class="text-subtitle1 q-my-none">
|
<div class="text-h6 q-mb-sm">Nostr Market</div>
|
||||||
{{SITE_TITLE}} Nostr Market Extension
|
<div class="text-body2 text-grey">
|
||||||
</h6>
|
A decentralized marketplace extension for LNbits implementing the
|
||||||
|
NIP-15 protocol. Create stalls, list products, and accept Lightning
|
||||||
|
payments while communicating with customers via encrypted Nostr
|
||||||
|
direct messages.
|
||||||
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<q-separator></q-separator>
|
|
||||||
<q-list> {% include "nostrmarket/_api_docs.html" %} </q-list>
|
<q-list> {% include "nostrmarket/_api_docs.html" %} </q-list>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue