make markets great again

This commit is contained in:
Tiago Vasconcelos
2022-12-30 16:51:09 +00:00
parent 715709ce25
commit 7c1a639796
7 changed files with 101 additions and 367 deletions

View File

@@ -399,8 +399,18 @@ async def create_shop_market_stalls(market_id: str, data: List[CreateMarketStall
return market_stalls
async def update_shop_market(market_id):
pass
async def update_shop_market(market_id: str, name: str):
await db.execute(
"UPDATE shop.markets SET name = ? WHERE id = ?",
(name, market_id),
)
await db.execute(
"DELETE FROM shop.market_stalls WHERE marketid = ?",
(market_id,),
)
market = await get_shop_market(market_id)
return market
### CHAT / MESSAGES

View File

@@ -195,12 +195,12 @@
></q-toggle>
<q-select
filled
dense
multiple
emit-value
:options="stalls.map(s => ({label: s.name, value: s.id}))"
label="Stalls"
v-model.trim="marketDialog.data.stalls"
v-model="marketDialog.data.stalls"
map-options
></q-select>
<q-input
filled

View File

@@ -354,14 +354,14 @@
<q-tooltip> Link to pass to stall relay </q-tooltip>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
{{ col.name == 'stalls' ? stallName(col.value) : col.value }}
</q-td>
<q-td auto-width>
<q-btn
flat
dense
size="xs"
@click="openStallUpdateDialog(props.row.id)"
@click="openMarketUpdateDialog(props.row.id)"
icon="edit"
color="light-blue"
></q-btn>
@@ -369,7 +369,7 @@
flat
dense
size="xs"
@click="deleteStall(props.row.id)"
@click="deleteMarket(props.row.id)"
icon="cancel"
color="pink"
></q-btn>

View File

@@ -213,12 +213,12 @@
const mapMarkets = obj => {
obj._data = _.clone(obj)
obj.stores = []
obj.stalls = []
LNbits.api
.request('GET', `/shop/api/v1/markets/${obj.id}/stalls`, null)
.then(response => {
if (response.data) {
obj.stores = response.data.map(s => s.name).toString()
obj.stalls = response.data.map(s => s.id) //.toString()
}
})
.catch(error => {
@@ -460,10 +460,10 @@
field: 'name'
},
{
name: 'stores',
name: 'stalls',
align: 'left',
label: 'Stalls',
field: 'stores'
field: 'stalls'
}
],
pagination: {
@@ -637,6 +637,9 @@
////////////////////////////////////////
////////////////STALLS//////////////////
////////////////////////////////////////
stallName(id) {
return id.map(c => this.stalls.find(s => s.id == c).name).toString()
},
getStalls: function () {
var self = this
LNbits.api
@@ -1035,44 +1038,38 @@
})
},
openShopUpdateDialog: function (linkId) {
var self = this
var link = _.findWhere(self.markets, {id: linkId})
this.marketDialog.data = _.clone(link._data)
openMarketUpdateDialog(linkId) {
var link = _.findWhere(this.markets, {id: linkId})
this.marketDialog.data = link
this.marketDialog.show = true
},
sendMarketplaceFormData: function () {
sendMarketplaceFormData() {
let data = {...this.marketDialog.data}
if (!data.usr) {
data.usr = this.g.user.id
}
if (data.id) {
this.updateZone(data)
this.updateMarketplace(data)
} else {
this.createMarketplace(data)
}
},
updateShop: function (data) {
var self = this
updateMarketplace(data) {
LNbits.api
.request(
'PUT',
'/shop/api/v1/shops' + data.id,
_.findWhere(self.g.user.wallets, {
id: self.marketDialog.data.wallet
}).inkey,
_.pick(data, 'countries', 'cost')
'/shop/api/v1/markets/' + data.id,
this.g.user.wallets[0].inkey,
data
)
.then(function (response) {
self.markets = _.reject(self.markets, function (obj) {
.then(response => {
this.markets = _.reject(this.markets, obj => {
return obj.id == data.id
})
self.markets.push(mapShops(response.data))
self.marketDialog.show = false
self.marketDialog.data = {}
this.markets.push(mapMarkets(response.data))
this.marketDialog.show = false
this.marketDialog.data = {}
data = {}
})
.catch(function (error) {
@@ -1097,21 +1094,20 @@
LNbits.utils.notifyApiError(error)
})
},
deleteShop: function (shopId) {
var self = this
var shop = _.findWhere(self.markets, {id: shopId})
deleteMarket(shopId) {
let market = _.findWhere(this.markets, {id: shopId})
LNbits.utils
.confirmDialog('Are you sure you want to delete this Shop link?')
.onOk(function () {
.confirmDialog('Are you sure you want to delete this Marketplace?')
.onOk(() => {
LNbits.api
.request(
'DELETE',
'/shop/api/v1/shops/' + shopId,
_.findWhere(self.g.user.wallets, {id: shop.wallet}).inkey
'/shop/api/v1/markets/' + shopId,
this.g.user.wallets[0].inkey
)
.then(function (response) {
self.markets = _.reject(self.markets, function (obj) {
.then(response => {
this.markets = _.reject(this.markets, obj => {
return obj.id == shopId
})
})
@@ -1144,32 +1140,6 @@
LNbits.utils.notifyApiError(error)
})
},
/*createOrder: function () {
var data = {
address: this.orderDialog.data.address,
email: this.orderDialog.data.email,
quantity: this.orderDialog.data.quantity,
shippingzone: this.orderDialog.data.shippingzone
}
var self = this
LNbits.api
.request(
'POST',
'/shop/api/v1/orders',
_.findWhere(self.g.user.wallets, {id: self.orderDialog.data.wallet})
.inkey,
data
)
.then(function (response) {
self.orders.push(mapOrders(response.data))
self.orderDialog.show = false
self.orderDialog.data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},*/
deleteOrder: function (orderId) {
var self = this
var order = _.findWhere(self.orders, {id: orderId})
@@ -1195,7 +1165,6 @@
},
shipOrder(order_id) {
let shipped = this.orders.find(o => o.id == order_id).shipped
console.log(this.orders, order_id, shipped)
LNbits.api
.request(
'GET',
@@ -1383,7 +1352,6 @@
this.diagonAlley =
this.$q.localStorage.getItem('lnbits.DAmode') || false
this.currencies.unit = '{{ currency }}'
console.log(this.currencies.unit, '{{currency}}')
await this.getCurrencies()
this.getStalls()
this.getProducts()

View File

@@ -21,49 +21,6 @@
</template>
</q-input>
</div>
<q-btn dense round flat icon="shopping_cart">
{% raw %}
<q-badge v-if="cart.size" color="red" class="text-bold" floating>
{{ cart.size }}
</q-badge>
{% endraw %}
<q-menu auto-close v-if="cart.size">
<q-list style="min-width: 100px">
{% raw %}
<q-item clickable :key="p.id" v-for="p in cartMenu">
<q-item-section side>
<span>{{p.quantity}} x </span>
</q-item-section>
<q-item-section avatar>
<q-avatar color="primary">
<img
size="sm"
:src="products.find(f => f.id == p.id).image"
/>
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label>{{ p.name }}</q-item-label>
</q-item-section>
<q-item-section side>
<span> {{p.price}} sats</span>
</q-item-section>
</q-item>
{% endraw %}
<q-separator />
</q-list>
<div class="q-pa-md q-gutter-md">
<q-btn
color="primary"
icon-right="checkout"
label="Checkout"
@click="checkoutDialog.show = true"
/>
</div>
</q-menu>
</q-btn>
</q-toolbar>
</div>
</div>
@@ -85,22 +42,6 @@
></q-img>
<q-card-section class="q-pb-xs q-pt-md">
<q-btn
round
:disabled="item.quantity < 1"
color="primary"
icon="shopping_cart"
size="lg"
style="
position: absolute;
top: 0;
right: 0;
transform: translate(-50%, -50%);
"
@click="addToCart(item)"
><q-tooltip> Add to cart </q-tooltip></q-btn
>
<div class="row no-wrap items-center">
<div class="col text-subtitle2 ellipsis-2-lines">
{{ item.product }}
@@ -112,13 +53,23 @@
<q-card-section class="q-py-sm">
<div>
<!-- <div class="text-caption text-green-8 text-weight-bolder">
{{ stall.name }}
</div> -->
<span class="text-h6">{{ item.price }} sats</span
><span class="q-ml-sm text-grey-6"
>BTC {{ (item.price / 1e8).toFixed(8) }}</span
>
<div class="text-caption text-weight-bolder">
{{ item.stallName }}
</div>
<span v-if="item.currency == 'sat'">
<span class="text-h6">{{ item.price }} sats</span
><span class="q-ml-sm text-grey-6"
>BTC {{ (item.price / 1e8).toFixed(8) }}</span
>
</span>
<span v-else>
<span class="text-h6"
>{{ getAmountFormated(item.price, item.currency) }}</span
>
<span v-if="exchangeRates" class="q-ml-sm text-grey-6"
>({{ getValueInSats(item.price, item.currency) }} sats)</span
>
</span>
<span
class="q-ml-md text-caption text-green-8 text-weight-bolder q-mt-md"
>{{item.quantity}} left</span
@@ -140,7 +91,7 @@
<q-separator></q-separator>
<q-card-actions>
<span>{{ stall.find(s => s.id == item.stall).name }}</span>
<span>{{ item.stallName }}</span>
<q-btn
flat
class="text-weight-bold text-capitalize q-ml-auto"
@@ -156,110 +107,6 @@
{% endraw %}
</q-card>
</div>
<!-- CHECKOUT DIALOG -->
<q-dialog v-model="checkoutDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="placeOrder" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="checkoutDialog.data.username"
label="Name *optional"
></q-input>
<q-input
filled
dense
v-model.trim="checkoutDialog.data.address"
label="Address"
></q-input>
<!-- <q-input
filled
dense
v-model.trim="checkoutDialog.data.address_2"
label="Address (line 2)"
></q-input> -->
<q-input
v-model="checkoutDialog.data.email"
filled
dense
type="email"
label="Email"
></q-input>
<p>Select the shipping zone:</p>
<div class="row q-mt-lg">
<q-option-group
:options="stall.zones"
type="radio"
emit-value
v-model="checkoutDialog.data.shippingzone"
/>
</div>
<div class="row q-mt-lg">
{% raw %} Total: {{ finalCost }} {% endraw %}
</div>
<div class="row q-mt-lg">
<q-btn
unelevated
color="primary"
:disable="checkoutDialog.data.address == null
|| checkoutDialog.data.email == null
|| checkoutDialog.data.shippingzone == null"
type="submit"
>Checkout</q-btn
>
<q-btn
v-close-popup
flat
@click="checkoutDialog = {show: false, data: {}}"
color="grey"
class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<!-- INVOICE DIALOG -->
<q-dialog
v-model="qrCodeDialog.show"
position="top"
@hide="closeQrCodeDialog"
>
<q-card
v-if="!qrCodeDialog.data.payment_request"
class="q-pa-lg q-pt-xl lnbits__dialog-card"
>
</q-card>
<q-card v-else class="q-pa-lg q-pt-xl lnbits__dialog-card">
<div class="text-center q-mb-lg">
<a :href="'lightning:' + qrCodeDialog.data.payment_request">
<q-responsive :ratio="1" class="q-mx-xl">
<qrcode
:value="qrCodeDialog.data.payment_request"
:options="{width: 340}"
class="rounded-borders"
></qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg">
<q-btn
outline
color="grey"
@click="copyText(qrCodeDialog.data.payment_request)"
>Copy invoice</q-btn
>
<q-btn
@click="closeQrCodeDialog"
v-close-popup
flat
color="grey"
class="q-ml-auto"
>Close</q-btn
>
</div>
</q-card>
</q-dialog>
</div>
{% endblock %} {% block scripts %}
<script>
@@ -269,25 +116,10 @@
mixins: [windowMixin],
data: function () {
return {
stall: null,
stalls: null,
products: [],
searchText: null,
cart: {
total: 0,
size: 0,
products: new Map()
},
cartMenu: [],
checkoutDialog: {
show: false,
data: {}
},
qrCodeDialog: {
data: {
payment_request: null
},
show: false
}
exchangeRates: null
}
},
computed: {
@@ -300,124 +132,47 @@
p.categories.includes(this.searchText)
)
})
},
finalCost() {
if (!this.checkoutDialog.data.shippingzone) return this.cart.total
let zoneCost = this.stall.zones.find(
z => z.value == this.checkoutDialog.data.shippingzone
)
return this.cart.total + zoneCost.cost
}
},
methods: {
closeQrCodeDialog() {
this.qrCodeDialog.dismissMsg()
this.qrCodeDialog.show = false
},
resetCart() {
this.cart = {
total: 0,
size: 0,
products: new Map()
async getRates() {
let hasFiat = this.stalls.map(s => s.currency).every(c => c != 'sat')
if (!hasFiat) return
try {
let rates = await axios.get('https://api.opennode.co/v1/rates')
this.exchangeRates = rates.data.data
console.log(this.exchangeRates)
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
addToCart(item) {
let prod = this.cart.products
if (prod.has(item.id)) {
let qty = prod.get(item.id).quantity
prod.set(item.id, {
...prod.get(item.id),
quantity: qty + 1
})
} else {
prod.set(item.id, {
name: item.product,
quantity: 1,
price: item.price
})
}
this.cart.products = prod
this.updateCart(item.price)
getValueInSats(amount, unit = 'USD') {
if (!this.exchangeRates) return 0
return Math.ceil(
(amount / this.exchangeRates[`BTC${unit}`][unit]) * 1e8
)
},
removeFromCart() {},
updateCart(price) {
this.cart.total += price
this.cart.size++
this.cartMenu = Array.from(this.cart.products, item => {
return {id: item[0], ...item[1]}
})
console.log(this.cartMenu, this.cart)
},
placeOrder() {
let dialog = this.checkoutDialog.data
let data = {
...this.checkoutDialog.data,
wallet: this.stall.wallet,
total: this.finalCost,
products: Array.from(this.cart.products, p => {
return {product_id: p[0], quantity: p[1].quantity}
})
}
LNbits.api
.request('POST', '/shop/api/v1/orders', null, data)
.then(res => {
this.checkoutDialog = {show: false, data: {}}
return res.data
})
.then(data => {
this.qrCodeDialog.data = data
this.qrCodeDialog.show = true
this.qrCodeDialog.dismissMsg = this.$q.notify({
timeout: 0,
message: 'Waiting for payment...'
})
return data
})
.then(data => {
this.qrCodeDialog.paymentChecker = setInterval(() => {
LNbits.api
.request(
'GET',
`/shop/api/v1/orders/payments/${this.qrCodeDialog.data.payment_hash}`
)
.then(res => {
if (res.data.paid) {
this.$q.notify({
type: 'positive',
message: 'Sats received, thanks!',
icon: 'thumb_up'
})
clearInterval(this.qrCodeDialog.paymentChecker)
this.resetCart()
this.closeQrCodeDialog()
}
})
.catch(error => {
console.error(error)
LNbits.utils.notifyApiError(error)
})
}, 3000)
})
.catch(error => {
console.error(error)
LNbits.utils.notifyApiError(error)
})
getAmountFormated(amount, unit = 'USD') {
return LNbits.utils.formatCurrency(amount, unit)
}
},
created() {
this.stall = JSON.parse('{{ stalls | tojson }}')
this.products = JSON.parse('{{ products | tojson }}')
async created() {
this.stalls = JSON.parse('{{ stalls | tojson }}')
let products = JSON.parse('{{ products | tojson }}')
console.log(this.stall, this.products)
this.products = products.map(obj => {
let stall = this.stalls.find(s => s.id == obj.stall)
obj.currency = stall.currency
if (obj.currency != 'sat') {
obj.price = parseFloat((obj.price / 100).toFixed(2))
}
obj.stallName = stall.name
return obj
})
await this.getRates()
console.log(this.stalls, this.products)
}
})
</script>
<style scoped>
.card--product {
}
</style>
{% endblock %}

View File

@@ -300,7 +300,7 @@
{% endblock %} {% block scripts %}
<script>
const mapProductsItems = obj => {
obj.price = (obj.price / 100).toFixed(2)
obj.price = parseFloat((obj.price / 100).toFixed(2))
return obj
}

View File

@@ -439,10 +439,11 @@ async def api_shop_stall_create(
if market.usr != wallet.wallet.user:
return {"message": "Not your market."}
market = await update_shop_market(market_id, **data.dict())
market = await update_shop_market(market_id, data.name)
else:
market = await create_shop_market(data=data)
await create_shop_market_stalls(market_id=market.id, data=data.stalls)
await create_shop_market_stalls(market_id=market.id, data=data.stalls)
return market.dict()