add: products ui
This commit is contained in:
parent
8f76578f1b
commit
fd529d3847
31 changed files with 1041 additions and 49 deletions
|
@ -2681,3 +2681,23 @@
|
|||
(td (text "{{ ((post.likes + 1) / (post.dislikes + 1))|round(method=\"ceil\", precision=2) }}"))
|
||||
(td (span ("class" "date short") (text "{{ post.created }}"))))
|
||||
(text "{%- endmacro %}")
|
||||
|
||||
(text "{% macro product_listing_card(product, owner=false, edit=false) -%}")
|
||||
(a
|
||||
("class" "card button lowered w_full flex flex_col gap_2")
|
||||
("href" "/product/{{ product.id }}{% if edit -%} /edit {%- endif %}")
|
||||
(text "{% if owner -%}")
|
||||
(text "{{ self::full_username(user=owner) }}")
|
||||
(text "{%- endif %}")
|
||||
|
||||
(h3
|
||||
("class" "flex gap_2 items_center {% if not product.on_sale -%} fade {%- endif %}")
|
||||
("style" "height: 24px; text-decoration: {% if not product.on_sale -%} line-through {%- else -%} none {%- endif %}")
|
||||
(icon (text "package"))
|
||||
(text "{{ product.title }}"))
|
||||
(h4
|
||||
("class" "flex gap_2 items_center")
|
||||
("style" "height: 18px")
|
||||
(icon (text "badge-cent"))
|
||||
(text "{{ product.price }}")))
|
||||
(text "{%- endmacro %}")
|
||||
|
|
322
crates/app/src/public/html/economy/edit.lisp
Normal file
322
crates/app/src/public/html/economy/edit.lisp
Normal file
|
@ -0,0 +1,322 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Manage product - {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"\") }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
(div
|
||||
("class" "card_nest")
|
||||
(div
|
||||
("class" "card small flex gap_2 items_center")
|
||||
(icon (text "pencil-line"))
|
||||
(b
|
||||
(str (text "economy:label.title"))))
|
||||
(form
|
||||
("class" "card flex flex_col gap_2")
|
||||
("onsubmit" "update_title_from_form(event)")
|
||||
(div
|
||||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
(str (text "economy:label.title")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "title")
|
||||
("id" "title")
|
||||
("placeholder" "title")
|
||||
("required" "")
|
||||
("minlength" "2")
|
||||
("maxlength" "128")
|
||||
("value" "{{ product.title }}")))
|
||||
(button (str (text "general:action.save")))))
|
||||
|
||||
(div
|
||||
("class" "card_nest")
|
||||
(div
|
||||
("class" "card small flex gap_2 items_center")
|
||||
(icon (text "pencil-line"))
|
||||
(b
|
||||
(str (text "economy:label.description"))))
|
||||
(form
|
||||
("class" "card flex flex_col gap_2")
|
||||
("onsubmit" "update_description_from_form(event)")
|
||||
(div
|
||||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "description")
|
||||
(str (text "economy:label.description")))
|
||||
(textarea
|
||||
("name" "description")
|
||||
("id" "description")
|
||||
("placeholder" "description")
|
||||
("required" "")
|
||||
("minlength" "2")
|
||||
("maxlength" "1024")
|
||||
(text "{{ product.description }}")))
|
||||
(button (str (text "general:action.save")))))
|
||||
|
||||
(div
|
||||
("class" "card_nest")
|
||||
(div
|
||||
("class" "card small flex gap_2 items_center")
|
||||
(icon (text "badge-cent"))
|
||||
(b
|
||||
(str (text "economy:label.price"))))
|
||||
(form
|
||||
("class" "card flex flex_col gap_2")
|
||||
("onsubmit" "update_price_from_form(event)")
|
||||
(label
|
||||
("for" "on_sale")
|
||||
("class" "flex items_center gap_2")
|
||||
(input
|
||||
("type" "checkbox")
|
||||
("id" "on_sale")
|
||||
("name" "on_sale")
|
||||
("class" "w_content")
|
||||
("checked" "{{ product.on_sale }}")
|
||||
("oninput" "event.preventDefault(); update_on_sale_from_form(event.target.checked)"))
|
||||
(span
|
||||
(str (text "economy:label.on_sale"))))
|
||||
(div
|
||||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
(str (text "economy:label.price")))
|
||||
(input
|
||||
("type" "number")
|
||||
("name" "price")
|
||||
("id" "price")
|
||||
("placeholder" "price")
|
||||
("required" "")
|
||||
("min" "0")
|
||||
("max" "1000000")
|
||||
("value" "{{ product.price }}")))
|
||||
(button (str (text "general:action.save")))))
|
||||
|
||||
(div
|
||||
("class" "card_nest")
|
||||
(div
|
||||
("class" "card small flex gap_2 items_center")
|
||||
(icon (text "weight"))
|
||||
(b
|
||||
(str (text "economy:label.stock"))))
|
||||
(form
|
||||
("class" "card flex flex_col gap_2")
|
||||
("onsubmit" "update_stock_from_form(event)")
|
||||
(label
|
||||
("for" "unlimited")
|
||||
("class" "flex items_center gap_2")
|
||||
(input
|
||||
("type" "checkbox")
|
||||
("id" "unlimited")
|
||||
("name" "unlimited")
|
||||
("class" "w_content")
|
||||
("checked" "{{ product.stock == -1 }}")
|
||||
("oninput" "event.preventDefault(); event.target.checked ? document.getElementById('stock').value = -1 : document.getElementById('stock').value = 0"))
|
||||
(span
|
||||
(str (text "economy:label.unlimited"))))
|
||||
(div
|
||||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
(str (text "economy:label.stock")))
|
||||
(input
|
||||
("type" "number")
|
||||
("name" "stock")
|
||||
("id" "stock")
|
||||
("placeholder" "stock")
|
||||
("required" "")
|
||||
("min" "-1")
|
||||
("max" "1000000")
|
||||
("value" "{{ product.stock }}")))
|
||||
(button (str (text "general:action.save")))))
|
||||
|
||||
(div
|
||||
("class" "card_nest")
|
||||
(div
|
||||
("class" "card small flex gap_2 items_center")
|
||||
(icon (text "package-check"))
|
||||
(b
|
||||
(str (text "economy:label.fulfillment_style"))))
|
||||
(form
|
||||
("class" "card flex flex_col gap_2")
|
||||
("onsubmit" "update_method_from_form(event)")
|
||||
(p (text "If you choose to send an automated mail letter upon purchase, users will automatically receive the message you supply below."))
|
||||
(p (text "If you disable automail, you'll be required to manually mail users who have purchased your product before the transfer is finalized."))
|
||||
|
||||
(label
|
||||
("for" "use_automail")
|
||||
("class" "flex items_center gap_2")
|
||||
(input
|
||||
("type" "checkbox")
|
||||
("id" "use_automail")
|
||||
("name" "use_automail")
|
||||
("class" "w_content")
|
||||
("oninput" "mirror_use_automail()")
|
||||
("checked" "{% if product.method != \"ManualMail\" -%} true {%- else -%} false {%- endif %}"))
|
||||
(span
|
||||
(str (text "economy:label.use_automail"))))
|
||||
(div
|
||||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "automail_message")
|
||||
(str (text "economy:label.automail_message")))
|
||||
(textarea
|
||||
("name" "automail_message")
|
||||
("id" "automail_message")
|
||||
("placeholder" "automail_message")
|
||||
(text "{% if product.method != \"ManualMail\" -%} {{ product.method.AutoMail }} {%- endif %}")))
|
||||
(button (str (text "general:action.save")))))
|
||||
|
||||
(a
|
||||
("class" "button secondary")
|
||||
("href" "/product/{{ product.id }}")
|
||||
(icon (text "arrow-left"))
|
||||
(str (text "general:action.back"))))
|
||||
|
||||
(script
|
||||
(text "async function update_title_from_form(e) {
|
||||
e.preventDefault();
|
||||
await trigger(\"atto::debounce\", [\"products::update\"]);
|
||||
|
||||
fetch(\"/api/v1/products/{{ product.id }}/title\", {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: e.target.title.value,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
async function update_description_from_form(e) {
|
||||
e.preventDefault();
|
||||
await trigger(\"atto::debounce\", [\"products::update\"]);
|
||||
|
||||
fetch(\"/api/v1/products/{{ product.id }}/description\", {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
description: e.target.description.value,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
async function update_on_sale_from_form(on_sale) {
|
||||
await trigger(\"atto::debounce\", [\"products::update\"]);
|
||||
|
||||
fetch(\"/api/v1/products/{{ product.id }}/on_sale\", {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
on_sale,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
async function update_price_from_form(e) {
|
||||
e.preventDefault();
|
||||
await trigger(\"atto::debounce\", [\"products::update\"]);
|
||||
|
||||
fetch(\"/api/v1/products/{{ product.id }}/price\", {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
price: e.target.price.valueAsNumber,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
async function update_stock_from_form(e) {
|
||||
e.preventDefault();
|
||||
await trigger(\"atto::debounce\", [\"products::update\"]);
|
||||
|
||||
fetch(\"/api/v1/products/{{ product.id }}/stock\", {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
stock: e.target.stock.valueAsNumber,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
async function update_method_from_form(e) {
|
||||
e.preventDefault();
|
||||
await trigger(\"atto::debounce\", [\"products::update\"]);
|
||||
|
||||
fetch(\"/api/v1/products/{{ product.id }}/method\", {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
method: e.target.use_automail.checked ? { AutoMail: e.target.automail_message.value } : \"ManualMail\",
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
globalThis.mirror_use_automail = () => {
|
||||
const use_automail = document.getElementById(\"use_automail\").checked;
|
||||
|
||||
if (use_automail) {
|
||||
document.getElementById(\"automail_message\").removeAttribute(\"disabled\");
|
||||
} else {
|
||||
document.getElementById(\"automail_message\").setAttribute(\"disabled\", \"true\");
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
mirror_use_automail();
|
||||
}, 150);"))
|
||||
(text "{% endblock %}")
|
67
crates/app/src/public/html/economy/product.lisp
Normal file
67
crates/app/src/public/html/economy/product.lisp
Normal file
|
@ -0,0 +1,67 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ product.title }} - {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"\") }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
(div
|
||||
("class" "card flex flex_col gap_2")
|
||||
(h3
|
||||
("style" "height: 32px")
|
||||
(text "{{ product.title }}"))
|
||||
(text "{{ components::full_username(user=owner) }}")
|
||||
|
||||
(text "{% if product.stock >= 0 -%}")
|
||||
(span ("class" "red") (text "{{ product.stock }} remaining"))
|
||||
(text "{%- endif %}")
|
||||
|
||||
(div
|
||||
("class" "card lowered w_full no_p_margin")
|
||||
(text "{{ product.description|markdown|safe }}"))
|
||||
|
||||
(div
|
||||
("class" "flex gap_2 items_center")
|
||||
(a
|
||||
("class" "button camo lowered")
|
||||
("href" "/wallet")
|
||||
("target" "_blank")
|
||||
(icon (text "badge-cent"))
|
||||
(text "{{ product.price }}"))
|
||||
(text "{% if user.id != product.owner -%}")
|
||||
(button
|
||||
("onclick" "purchase()")
|
||||
("disabled" "{{ product.stock == 0 }}")
|
||||
(icon (text "piggy-bank"))
|
||||
(str (text "economy:action.buy")))
|
||||
(text "{% else %}")
|
||||
(a
|
||||
("class" "button")
|
||||
("href" "/product/{{ product.id }}/edit")
|
||||
(icon (text "settings"))
|
||||
(str (text "general:label.edit")))
|
||||
(text "{%- endif %}"))))
|
||||
|
||||
(script
|
||||
(text "async function purchase() {
|
||||
await trigger(\"atto::debounce\", [\"products::buy\"]);
|
||||
|
||||
if (
|
||||
!(await trigger(\"atto::confirm\", [
|
||||
\"Are you sure you would like to do this? Your new balance will be {{ user.coins - product.price }} coins.\",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(\"/api/v1/products/{{ product.id }}/buy\", {
|
||||
method: \"POST\",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}"))
|
||||
(text "{% endblock %}")
|
91
crates/app/src/public/html/economy/products.lisp
Normal file
91
crates/app/src/public/html/economy/products.lisp
Normal file
|
@ -0,0 +1,91 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "My products - {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"products\") }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
; create new
|
||||
(text "{{ components::supporter_ad(body=\"Become a supporter to create unlimited products!\") }}")
|
||||
|
||||
(div
|
||||
("class" "card_nest")
|
||||
(div
|
||||
("class" "card small")
|
||||
(b
|
||||
(str (text "economy:label.create_new"))))
|
||||
(form
|
||||
("class" "card flex flex_col gap_2")
|
||||
("onsubmit" "create_product_from_form(event)")
|
||||
(div
|
||||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
(str (text "economy:label.title")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "title")
|
||||
("id" "title")
|
||||
("placeholder" "title")
|
||||
("required" "")
|
||||
("minlength" "2")
|
||||
("maxlength" "128")))
|
||||
(div
|
||||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "description")
|
||||
(str (text "economy:label.description")))
|
||||
(textarea
|
||||
("name" "description")
|
||||
("id" "description")
|
||||
("placeholder" "description")
|
||||
("required" "")
|
||||
("minlength" "2")
|
||||
("maxlength" "1024")))
|
||||
(button
|
||||
(text "{{ text \"communities:action.create\" }}"))))
|
||||
|
||||
; product listing
|
||||
(div
|
||||
("class" "card_nest")
|
||||
(div
|
||||
("class" "card small flex items_center gap_2")
|
||||
(icon (text "store"))
|
||||
(str (text "economy:label.my_products")))
|
||||
|
||||
(div
|
||||
("class" "card flex flex_col gap_2")
|
||||
(text "{% for item in list %} {{ components::product_listing_card(product=item, edit=true) }} {% endfor %}")
|
||||
(text "{{ components::pagination(page=page, items=list|length) }}"))))
|
||||
|
||||
(script
|
||||
(text "async function create_product_from_form(e) {
|
||||
e.preventDefault();
|
||||
await trigger(\"atto::debounce\", [\"products::create\"]);
|
||||
|
||||
fetch(\"/api/v1/products\", {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: e.target.title.value,
|
||||
description: e.target.description.value,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
|
||||
if (res.ok) {
|
||||
e.target.reset();
|
||||
setTimeout(() => {
|
||||
window.location.href = `/product/${res.payload}/edit`;
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}"))
|
||||
(text "{% endblock %}")
|
|
@ -51,9 +51,10 @@
|
|||
(td ("class" "w_content") (text "{{ components::full_username(user=transfer[3]) }}"))
|
||||
(td ("class" "w_content") (text "{{ components::full_username(user=transfer[4]) }}"))
|
||||
(td
|
||||
("class" "flex items_center gap_1")
|
||||
(text "{{ transfer[2] }}")
|
||||
(text "{% if transfer[6] -%}")
|
||||
(span ("title" "Pending") (icon (text "clock")))
|
||||
(span ("class" "flex items_center gap_1") ("title" "Pending") (icon (text "clock")))
|
||||
(text "{%- endif %}"))
|
||||
(td
|
||||
(text "{% if transfer[5] -%}")
|
||||
|
|
|
@ -73,14 +73,20 @@
|
|||
("class" "inner")
|
||||
(a
|
||||
("href" "/chats/0/0")
|
||||
("title" "Chats")
|
||||
(icon (text "message-circle"))
|
||||
(str (text "communities:label.chats")))
|
||||
(a
|
||||
("href" "/mail")
|
||||
("title" "Mail")
|
||||
(icon (text "mail"))
|
||||
(str (text "general:link.mail")))
|
||||
(a
|
||||
("href" "/wallet")
|
||||
(icon (text "piggy-bank"))
|
||||
(str (text "economy:label.my_wallet")))
|
||||
(a
|
||||
("href" "/products")
|
||||
(icon (text "store"))
|
||||
(str (text "economy:label.my_products")))
|
||||
(a
|
||||
("href" "/journals/0/0")
|
||||
(icon (text "notebook"))
|
||||
|
@ -318,6 +324,13 @@
|
|||
("class" "{% if selected == 'media' -%}active{%- endif %}")
|
||||
(str (text "auth:label.media")))
|
||||
|
||||
(text "{% if user and profile.settings.enable_shop -%}")
|
||||
(a
|
||||
("href" "/@{{ profile.username }}/shop")
|
||||
("class" "{% if selected == 'shop' -%}active{%- endif %}")
|
||||
(str (text "auth:label.shop")))
|
||||
(text "{%- endif %}")
|
||||
|
||||
(text "{% if is_self or is_helper -%}")
|
||||
(a
|
||||
("href" "/@{{ profile.username }}/outbox")
|
||||
|
|
|
@ -112,6 +112,7 @@
|
|||
subject: e.target.subject.value.trim(),
|
||||
receivers: RECEIVERS,
|
||||
replying_to: SEARCH_PARAMS.get(\"replying_to\") || \"0\",
|
||||
transfer_id: SEARCH_PARAMS.get(\"transfer_id\") || \"0\",
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
|
|
|
@ -92,6 +92,37 @@
|
|||
(text "{{ icon \"trash\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:action.delete\" }}"))))))
|
||||
(text "{% elif request.action_type == \"Transfer\" %}")
|
||||
(div
|
||||
("class" "card_nest")
|
||||
(div
|
||||
("class" "card small flex items_center gap_2")
|
||||
(icon (text "piggy-bank"))
|
||||
(span
|
||||
(str (text "requests:label.coin_transfer_request"))))
|
||||
(div
|
||||
("class" "card flex flex_col gap_2")
|
||||
(span (a ("href" "/api/v1/auth/user/find/{{ request.linked_asset }}") (text "Somebody")) (text " is asking for a transfer of ") (b (text "{{ request.data.Int32 }} coins")) (text "."))
|
||||
(div
|
||||
("class" "card flex flex_wrap w_full secondary gap_2")
|
||||
(a
|
||||
("href" "/api/v1/auth/user/find/{{ request.linked_asset }}")
|
||||
("class" "button")
|
||||
(text "{{ icon \"external-link\" }}")
|
||||
(span
|
||||
(text "{{ text \"requests:action.view_profile\" }}")))
|
||||
(button
|
||||
("class" "lowered green")
|
||||
("onclick" "accept_transfer_request(event, '{{ request.id }}', '{{ request.linked_asset }}', {{ request.data.Int32 }})")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:action.accept\" }}")))
|
||||
(button
|
||||
("class" "lowered red")
|
||||
("onclick" "remove_request('{{ request.id }}', '{{ request.linked_asset }}')")
|
||||
(text "{{ icon \"trash\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:action.delete\" }}"))))))
|
||||
(text "{%- endif %} {% endfor %} {% for question in questions %}")
|
||||
(div
|
||||
("class" "card_nest")
|
||||
|
@ -138,13 +169,15 @@
|
|||
(text "{{ components::pagination(page=page, items=requests|length, key=\"&id=\", value=profile.id) }}"))
|
||||
|
||||
(script
|
||||
(text "async function remove_request(id, linked_asset) {
|
||||
if (
|
||||
!(await trigger(\"atto::confirm\", [
|
||||
\"Are you sure you want to do this?\",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
(text "async function remove_request(id, linked_asset, confirm = true) {
|
||||
if (confirm) {
|
||||
if (
|
||||
!(await trigger(\"atto::confirm\", [
|
||||
\"Are you sure you want to do this?\",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fetch(`/api/v1/requests/${id}/${linked_asset}`, {
|
||||
|
@ -275,6 +308,41 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
globalThis.accept_transfer_request = async (e, id, receiver, amount) => {
|
||||
await trigger(\"atto::debounce\", [\"economy::transfer\"]);
|
||||
|
||||
if (
|
||||
!(await trigger(\"atto::confirm\", [
|
||||
\"Are you sure you would like to do this?\",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/v1/transfers`, {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
receiver,
|
||||
amount,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(async (res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
|
||||
if (res.ok) {
|
||||
e.target.parentElement.parentElement.parentElement.parentElement.remove();
|
||||
remove_request(id, receiver, false);
|
||||
}
|
||||
});
|
||||
};"))
|
||||
|
||||
(text "{% endblock %}")
|
||||
|
|
|
@ -256,6 +256,13 @@
|
|||
(icon (text "mail-plus"))
|
||||
(span
|
||||
(str (text "mail:action.send_mail"))))
|
||||
(text "{%- endif %} {% if not profile.settings.no_transfers -%}")
|
||||
(button
|
||||
("onclick" "request_transfer()")
|
||||
("class" "lowered")
|
||||
(icon (text "badge-cent"))
|
||||
(span
|
||||
(str (text "economy:action.request"))))
|
||||
(text "{%- endif %} {% if is_helper -%}")
|
||||
(a
|
||||
("href" "/mod_panel/profile/{{ profile.id }}")
|
||||
|
@ -289,6 +296,41 @@
|
|||
});
|
||||
};
|
||||
|
||||
globalThis.request_transfer = async () => {
|
||||
await trigger(\"atto::debounce\", [\"economy::transfer\"]);
|
||||
const amount = Number.parseInt((await trigger(\"atto::prompt\", [\"Request amount:\"])) || \"\");
|
||||
|
||||
if (amount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(await trigger(\"atto::confirm\", [
|
||||
`Are you sure you would like to request ${amount} coins from {{ profile.username }}?`,
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/v1/transfers/ask`, {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
receiver: \"{{ profile.id }}\",
|
||||
amount,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(async (res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
globalThis.toggle_follow_user = async (e) => {
|
||||
await trigger(\"atto::debounce\", [
|
||||
\"users::follow\",
|
||||
|
|
|
@ -1958,6 +1958,23 @@
|
|||
settings.forum_signature,
|
||||
\"textarea\",
|
||||
],
|
||||
[[], \"Economy\", \"title\"],
|
||||
[
|
||||
[
|
||||
\"enable_shop\",
|
||||
\"Show shop tab on my profile\",
|
||||
],
|
||||
\"{{ profile.settings.enable_shop }}\",
|
||||
\"checkbox\",
|
||||
],
|
||||
[
|
||||
[
|
||||
\"no_transfers\",
|
||||
\"Disable transfer requests\",
|
||||
],
|
||||
\"{{ profile.settings.no_transfers }}\",
|
||||
\"checkbox\",
|
||||
],
|
||||
[[], \"Misc\", \"title\"],
|
||||
[
|
||||
[\"hide_dislikes\", \"Hide post dislikes\"],
|
||||
|
|
17
crates/app/src/public/html/profile/shop.lisp
Normal file
17
crates/app/src/public/html/profile/shop.lisp
Normal file
|
@ -0,0 +1,17 @@
|
|||
(text "{% extends \"profile/base.html\" %} {% block content %} {% if profile.settings.enable_questions and (user or profile.settings.allow_anonymous_questions) %}")
|
||||
(div
|
||||
("style" "display: contents")
|
||||
(text "{{ components::create_question_form(receiver=profile.id, header=profile.settings.motivational_header, drawing_enabled=profile.settings.enable_drawings, allow_anonymous=profile.settings.allow_anonymous_questions) }}"))
|
||||
(text "{%- endif %}")
|
||||
(text "{{ macros::profile_nav(selected=\"shop\") }}")
|
||||
(div
|
||||
("class" "card_nest")
|
||||
(div
|
||||
("class" "card small flex gap_2 items_center")
|
||||
(icon (text "store"))
|
||||
(str (text "auth:label.shop")))
|
||||
(div
|
||||
("class" "card w_full flex flex_col gap_2")
|
||||
(text "{% for item in list %} {{ components::product_listing_card(product=item) }} {% endfor %}")
|
||||
(text "{{ components::pagination(page=page, items=list|length) }}")))
|
||||
(text "{% endblock %}")
|
Loading…
Add table
Add a link
Reference in a new issue