Admin, manage offers
This commit is contained in:
parent
d29326e76e
commit
c4766774dc
108
Cargo.lock
generated
108
Cargo.lock
generated
@ -433,6 +433,17 @@ dependencies = [
|
|||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atoi"
|
name = "atoi"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -747,6 +758,12 @@ version = "2.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
|
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "firestorm"
|
name = "firestorm"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -1314,6 +1331,7 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"gumdrop",
|
"gumdrop",
|
||||||
"password-hash",
|
"password-hash",
|
||||||
|
"postgres",
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -1397,6 +1415,24 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
||||||
|
dependencies = [
|
||||||
|
"phf_shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_shared"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||||
|
dependencies = [
|
||||||
|
"siphasher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
@ -1441,6 +1477,49 @@ dependencies = [
|
|||||||
"universal-hash",
|
"universal-hash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres"
|
||||||
|
version = "0.19.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8bbcd5f6deb39585a0d9f4ef34c4a41c25b7ad26d23c75d837d78c8e7adc85f"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator",
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
"tokio-postgres",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres-protocol"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "878c6cbf956e03af9aa8204b407b9cbf47c072164800aa918c516cd4b056c50c"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator",
|
||||||
|
"hmac",
|
||||||
|
"md-5",
|
||||||
|
"memchr",
|
||||||
|
"rand",
|
||||||
|
"sha2",
|
||||||
|
"stringprep",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres-types"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebd6e8b7189a73169290e89bd24c771071f1012d8fe6f738f5226531f0b03d89"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator",
|
||||||
|
"postgres-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@ -1723,6 +1802,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "siphasher"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@ -1977,6 +2062,29 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-postgres"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19c88a47a23c5d2dc9ecd28fb38fba5fc7e5ddc1fe64488ec145076b0c71c8ae"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator",
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"parking_lot 0.12.1",
|
||||||
|
"percent-encoding",
|
||||||
|
"phf",
|
||||||
|
"pin-project-lite",
|
||||||
|
"postgres-protocol",
|
||||||
|
"postgres-types",
|
||||||
|
"socket2",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.23.4"
|
version = "0.23.4"
|
||||||
|
@ -21,6 +21,7 @@ futures = { version = "0.3.21", features = ["async-await", "std"] }
|
|||||||
futures-util = { version = "0.3.21", features = [] }
|
futures-util = { version = "0.3.21", features = [] }
|
||||||
gumdrop = { version = "*" }
|
gumdrop = { version = "*" }
|
||||||
password-hash = { version = "0.4.2" }
|
password-hash = { version = "0.4.2" }
|
||||||
|
postgres = { version = "0.19.3" }
|
||||||
rand = { version = "0.8.5", features = [] }
|
rand = { version = "0.8.5", features = [] }
|
||||||
serde = { version = "*", features = ["derive"] }
|
serde = { version = "*", features = ["derive"] }
|
||||||
serde_json = { version = "*" }
|
serde_json = { version = "*" }
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
[general]
|
[general]
|
||||||
dirs = ["assets/templates"]
|
dirs = ["assets/templates"]
|
||||||
whitespace = "preserve"
|
whitespace = "suppress"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block content %}
|
{% block content -%}
|
||||||
<ow-admin>
|
<ow-admin>
|
||||||
<admin-businesses>
|
<admin-businesses>
|
||||||
{% for business in businesses %}
|
{% for business in businesses -%}
|
||||||
<admin-edit-business business-id="{{ business.id }}" state="{{ business.state }}">
|
<admin-edit-business business-id="{{ business.id }}" state="{{ business.state }}">
|
||||||
<admin-business
|
<admin-business
|
||||||
business-id="{{ business.id }}"
|
business-id="{{ business.id }}"
|
||||||
@ -11,7 +11,7 @@
|
|||||||
>
|
>
|
||||||
<p slot="description">{{business.description|safe}}</p>
|
<p slot="description">{{business.description|safe}}</p>
|
||||||
|
|
||||||
{% for item in business.items %}
|
{% for item in business.items -%}
|
||||||
<local-business-item
|
<local-business-item
|
||||||
slot="item"
|
slot="item"
|
||||||
name="{{item.name}}"
|
name="{{item.name}}"
|
||||||
@ -19,10 +19,10 @@
|
|||||||
picture-url="{{item.picture_url}}"
|
picture-url="{{item.picture_url}}"
|
||||||
>
|
>
|
||||||
</local-business-item>
|
</local-business-item>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</admin-business>
|
</admin-business>
|
||||||
</admin-edit-business>
|
</admin-edit-business>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</admin-businesses>
|
</admin-businesses>
|
||||||
</ow-admin>
|
</ow-admin>
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
published-at="{{article.published_at|opt_time}}"
|
published-at="{{article.published_at|opt_time}}"
|
||||||
created-at="{{article.created_at}}"
|
created-at="{{article.created_at}}"
|
||||||
>
|
>
|
||||||
{{ article.body|safe }}
|
{{- article.body|safe -}}
|
||||||
</article-form>
|
</article-form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<ow-admin>
|
<ow-admin>
|
||||||
<ow-articles>
|
<ow-articles>
|
||||||
{% for article in news %}
|
{% for article in news -%}
|
||||||
<edit-news-article article-id="{{ article.id }}">
|
<edit-news-article article-id="{{ article.id }}">
|
||||||
<news-article
|
<news-article
|
||||||
article-id="{{ article.id }}"
|
article-id="{{ article.id }}"
|
||||||
@ -11,10 +11,10 @@
|
|||||||
published-at="{{ article.published_at|opt_time }}"
|
published-at="{{ article.published_at|opt_time }}"
|
||||||
created-at="{{ article.created_at }}"
|
created-at="{{ article.created_at }}"
|
||||||
>
|
>
|
||||||
{{article.body|safe}}
|
{{- article.body|safe -}}
|
||||||
</news-article>
|
</news-article>
|
||||||
</edit-news-article>
|
</edit-news-article>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</ow-articles>
|
</ow-articles>
|
||||||
<article-form></article-form>
|
<article-form></article-form>
|
||||||
</ow-admin>
|
</ow-admin>
|
||||||
|
45
assets/templates/admin/offers/index.html
Normal file
45
assets/templates/admin/offers/index.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{% extends "../layout.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<ow-admin>
|
||||||
|
<ow-offers>
|
||||||
|
<h1>Admin - Sprzedaż niepotrzebnych rzeczy</h1>
|
||||||
|
|
||||||
|
{% for offer in offers %}
|
||||||
|
<admin-edit-offer
|
||||||
|
state="{{offer.state.as_str()}}"
|
||||||
|
offer-id="{{offer.id}}"
|
||||||
|
description="{{offer.description}}"
|
||||||
|
picture-url="{{offer.picture_url}}"
|
||||||
|
{% match offer.price_range %}
|
||||||
|
{% when PriceRange::Free %}
|
||||||
|
price-range-min="0"
|
||||||
|
price-range-max="0"
|
||||||
|
{% when PriceRange::Fixed with { value } %}
|
||||||
|
price-range-min="{{value}}"
|
||||||
|
price-range-max="0"
|
||||||
|
{% when PriceRange::Range with { min, max } %}
|
||||||
|
price-range-min="{{min}}"
|
||||||
|
price-range-max="{{max}}"
|
||||||
|
{% endmatch %}
|
||||||
|
>
|
||||||
|
<marketplace-offer
|
||||||
|
offer-id="{{offer.id}}"
|
||||||
|
description="{{offer.description}}"
|
||||||
|
picture-url="{{offer.picture_url}}"
|
||||||
|
{% match offer.price_range %}
|
||||||
|
{% when PriceRange::Free %}
|
||||||
|
price-range-min="0"
|
||||||
|
price-range-max="0"
|
||||||
|
{% when PriceRange::Fixed with { value } %}
|
||||||
|
price-range-min="{{value}}"
|
||||||
|
price-range-max="0"
|
||||||
|
{% when PriceRange::Range with { min, max } %}
|
||||||
|
price-range-min="{{min}}"
|
||||||
|
price-range-max="{{max}}"
|
||||||
|
{% endmatch %}
|
||||||
|
></marketplace-offer>
|
||||||
|
</admin-edit-offer>
|
||||||
|
{% endfor %}
|
||||||
|
</ow-offers>
|
||||||
|
</ow-admin>
|
||||||
|
{% endblock %}
|
@ -19,99 +19,7 @@
|
|||||||
{% when None %}
|
{% when None %}
|
||||||
{% endmatch %}
|
{% endmatch %}
|
||||||
<article>
|
<article>
|
||||||
<ow-nav>
|
{% include "nav.html" %}
|
||||||
<ow-path path="/" id="home" title="OS Wilno">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511 511" xml:space="preserve">
|
|
||||||
<path d="M127.5 143h121.675a31.386 31.386 0 0 0-1.175 8.5 7.5 7.5 0 0 0 7.5 7.5h128a7.5 7.5 0 0 0 7.5-7.5c0-17.369-14.131-31.5-31.5-31.5-1.387 0-2.789.108-4.222.327C346.299 110.022 333.268 104 319.5 104s-26.799 6.022-35.778 16.327A27.875 27.875 0 0 0 279.5 120a31.314 31.314 0 0 0-17.439 5.284c-2.147-11.751-7.917-22.248-16.119-30.284H327.5a7.5 7.5 0 0 0 7.5-7.5c0-26.191-21.309-47.5-47.5-47.5-11.859 0-22.976 4.337-31.73 12.298A31.495 31.495 0 0 0 251.5 52c-16.921 0-31.106 11.904-34.643 27.775a56.764 56.764 0 0 0-10.588-1.006c-14.429 0-27.94 5.38-38.486 15.237a37.778 37.778 0 0 0-5.822-.468C138.824 93.539 120 112.362 120 135.5a7.5 7.5 0 0 0 7.5 7.5zm124-76c1.475 0 3.04.218 4.926.687a7.505 7.505 0 0 0 7.31-2.184C270.008 58.73 278.447 55 287.5 55c15.34 0 28.232 10.683 31.626 25H232.42c3-7.605 10.422-13 19.08-13zm28 68c1.482 0 3.046.276 4.923.868a7.502 7.502 0 0 0 8.413-2.87C298.933 124.233 308.901 119 319.5 119s20.567 5.233 26.665 13.998a7.5 7.5 0 0 0 8.413 2.87c1.877-.592 3.441-.868 4.923-.868 6.399 0 11.959 3.662 14.695 9h-109.39c2.735-5.338 8.295-9 14.694-9zm-117.539-26.461c1.96 0 4.019.285 6.479.896a7.498 7.498 0 0 0 7.311-2.183c8.051-8.694 18.889-13.482 30.518-13.482 20.45 0 37.512 14.788 41.056 34.231H136.061c3.257-11.23 13.635-19.462 25.9-19.462z"/>
|
|
||||||
<path d="M510.905 262.363c.06-2.283.095-4.571.095-6.863 0-68.247-26.577-132.408-74.834-180.666C387.908 26.577 323.747 0 255.5 0S123.092 26.577 74.834 74.834C26.577 123.092 0 187.253 0 255.5s26.577 132.408 74.834 180.666C123.092 484.423 187.253 511 255.5 511s132.408-26.577 180.666-74.834c45.966-45.966 72.242-106.365 74.635-170.975a7.538 7.538 0 0 0 .198-1.691 7.486 7.486 0 0 0-.094-1.137zM85.441 85.441C130.865 40.016 191.26 15 255.5 15s124.635 25.016 170.059 70.441C470.984 130.865 496 191.26 496 255.5c0 .167-.006.333-.006.5H463v-8.5a7.5 7.5 0 0 0-15 0v8.5h-17v-25h.5a7.499 7.499 0 0 0 5.303-12.803l-32-32A7.497 7.497 0 0 0 399.5 184h-80c-1.989 0-3.897.79-5.303 2.197l-26.92 26.92-2.015-2.418A7.5 7.5 0 0 0 279.5 208H263v-32.5a7.5 7.5 0 0 0-15 0v.5h-17v-.5a7.5 7.5 0 0 0-15 0v.5h-65v-.5a7.5 7.5 0 0 0-15 0v.5h-17v-.5a7.5 7.5 0 0 0-15 0V224H17.046c6.8-52.302 30.482-100.646 68.395-138.559zM494.769 280H431v-9h64.494c-.19 3.01-.425 6.012-.725 9zm-15.521 64H431v-49h61.776a238.363 238.363 0 0 1-13.528 49zm-15.521 32.011c-.076-.002-.15-.011-.227-.011h-416c-.076 0-.151.009-.227.011A238.142 238.142 0 0 1 38.279 359h434.44a237.097 237.097 0 0 1-8.992 17.011zm-15.951 24.003c-.092-.003-.183-.014-.276-.014h-384c-.093 0-.184.01-.276.014A239.546 239.546 0 0 1 56.743 391h397.515a240.711 240.711 0 0 1-6.482 9.014zM15 255.5c0-5.532.199-11.032.567-16.5H104v17H15.5c-.167 0-.33.014-.494.025 0-.175-.006-.35-.006-.525zM143.5 199a7.5 7.5 0 0 0 7.5-7.5v-.5h65v.5a7.5 7.5 0 0 0 15 0v-.5h17v17H119v-17h17v.5a7.5 7.5 0 0 0 7.5 7.5zM416 231v113h-97v-73.027c.168.011.335.027.504.027a7.5 7.5 0 0 0 5.757-12.301L302.179 231H416zm-110.394-15 17-17h73.787l17 17H305.606zM207 344v-73h97v73h-97zm-88 0V223h96.487l-29.749 35.699a7.5 7.5 0 0 0 .96 10.563 7.464 7.464 0 0 0 5.301 1.715V344H119zm184.487-88h-95.975l27.5-33h40.975l27.5 33zM104 271v73H31.752c-9.135-23.111-14.646-47.678-16.247-73H104zm151.5 225c-64.24 0-124.635-25.016-170.059-70.441A244.667 244.667 0 0 1 75.51 415h359.98a245.917 245.917 0 0 1-9.931 10.559C380.135 470.984 319.74 496 255.5 496z"/>
|
|
||||||
<path d="M143.5 240a7.5 7.5 0 0 0-7.5 7.5v16a7.5 7.5 0 0 0 15 0v-16a7.5 7.5 0 0 0-7.5-7.5zM167.5 240a7.5 7.5 0 0 0-7.5 7.5v16a7.5 7.5 0 0 0 15 0v-16a7.5 7.5 0 0 0-7.5-7.5zM143.5 288a7.5 7.5 0 0 0-7.5 7.5v24a7.5 7.5 0 0 0 15 0v-24a7.5 7.5 0 0 0-7.5-7.5zM167.5 288a7.5 7.5 0 0 0-7.5 7.5v24a7.5 7.5 0 0 0 15 0v-24a7.5 7.5 0 0 0-7.5-7.5zM55.5 327a7.5 7.5 0 0 0 7.5-7.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 7.5 7.5zM39 319.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 15 0zM79.5 327a7.5 7.5 0 0 0 7.5-7.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 7.5 7.5zM231.5 288a7.5 7.5 0 0 0-7.5 7.5v24a7.5 7.5 0 0 0 15 0v-24a7.5 7.5 0 0 0-7.5-7.5zM255.5 288a7.5 7.5 0 0 0-7.5 7.5v24a7.5 7.5 0 0 0 15 0v-24a7.5 7.5 0 0 0-7.5-7.5zM279.5 288a7.5 7.5 0 0 0-7.5 7.5v24a7.5 7.5 0 0 0 15 0v-24a7.5 7.5 0 0 0-7.5-7.5zM343.5 327a7.5 7.5 0 0 0 7.5-7.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 7.5 7.5zM367.5 327a7.5 7.5 0 0 0 7.5-7.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 7.5 7.5zM391.5 327a7.5 7.5 0 0 0 7.5-7.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 7.5 7.5zM447.5 304a7.5 7.5 0 0 0-7.5 7.5v8a7.5 7.5 0 0 0 15 0v-8a7.5 7.5 0 0 0-7.5-7.5zM471.5 304a7.5 7.5 0 0 0-7.5 7.5v8a7.5 7.5 0 0 0 15 0v-8a7.5 7.5 0 0 0-7.5-7.5zM343.5 271a7.5 7.5 0 0 0 7.5-7.5v-8a7.5 7.5 0 0 0-15 0v8a7.5 7.5 0 0 0 7.5 7.5zM367.5 271a7.5 7.5 0 0 0 7.5-7.5v-8a7.5 7.5 0 0 0-15 0v8a7.5 7.5 0 0 0 7.5 7.5zM391.5 271a7.5 7.5 0 0 0 7.5-7.5v-8a7.5 7.5 0 0 0-15 0v8a7.5 7.5 0 0 0 7.5 7.5z"/>
|
|
||||||
</svg>
|
|
||||||
<span>OS Wilno</span>
|
|
||||||
</ow-path>
|
|
||||||
|
|
||||||
{% if page.is_public() %}
|
|
||||||
<ow-path path="/" selected="{{ page.select_index() }}" title="Lokalne usługi">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.999 511.999" xml:space="preserve">
|
|
||||||
<path d="M440.482 50.916c-8.099-4.226-18.091-1.088-22.321 7.013l-41.925 80.336-46.147 36.662h-54.683l-57.033-18.756-33.145-53.595 5.854-5.694c3.554-3.457 3.634-9.143.176-12.697l-13.735-14.12 10.294-10.014c4.024-3.914 4.113-10.35.198-14.374l-41.438-42.6c-3.914-4.024-10.35-4.113-14.374-.198l-66.5 64.687c-4.024 3.914-4.113 10.35-.198 14.374l41.438 42.599c3.914 4.024 10.35 4.113 14.374.198l10.294-10.013 13.737 14.121c3.449 3.547 9.136 3.64 12.696.176l2.896-2.817 32.466 52.499a16.544 16.544 0 0 0 8.901 7.014l58.436 19.218.008 287.213c0 10.964 8.888 19.853 19.852 19.853s19.852-8.888 19.852-19.853V333.466l8.565-2.239 24.462 64.136-17.398 78.459c-2.373 10.704 4.38 21.306 15.084 23.679 10.69 2.373 21.303-4.371 23.679-15.083l18.771-84.647a19.858 19.858 0 0 0-1.096-12.026l-24.153-57.154V202.66l51.197-40.674a16.547 16.547 0 0 0 4.375-5.299l43.551-83.45c4.23-8.1 1.089-18.094-7.01-22.321zM171.921 90.467a16.48 16.48 0 0 0-12.357 2.058c-6.031 3.73-8.81 10.704-7.514 17.272l-.092.088-7.475-7.683 20.167-19.617 7.475 7.684-.204.198z"/>
|
|
||||||
<circle cx="304.734" cy="129.707" r="34.286"/>
|
|
||||||
</svg>
|
|
||||||
<div>Lokalne Usługi</div>
|
|
||||||
</ow-path>
|
|
||||||
<ow-path path="/marketplace" selected="{{ page.select_marketplace() }}" title="Targ">
|
|
||||||
<svg viewBox="0 0 484.909 484.909" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M204.993 438.478c-6.347 6.349-6.347 16.639 0 22.978a16.196 16.196 0 0 0 11.488 4.761c4.158 0 8.316-1.587 11.489-4.761l49.747-49.754-22.979-22.978zm112.649-112.671-16.947 16.954 22.976 22.977 39.926-39.931zm-56.872 0h-45.954l135.627 135.648a16.193 16.193 0 0 0 11.487 4.761c4.158 0 8.315-1.587 11.488-4.761 6.349-6.339 6.349-16.629 0-22.978zM102.294 107.658c21.471 0 38.878-19.915 38.878-44.478 0-24.564-17.407-44.487-38.878-44.487-21.486 0-38.877 19.923-38.877 44.487 0 24.563 17.391 44.478 38.877 44.478zm-15.17 48.128c-58.083-103.857-29.041-51.929 0 0z"/>
|
|
||||||
<path d="M74.524 123.66c-7.062.128-11.934.302-12.44.539-5.554 1.365-19.132 13.9-21.512 19.605L1.42 250.377c-3.937 9.521.586 20.439 10.107 24.382a18.79 18.79 0 0 0 7.14 1.42c7.315 0 14.266-4.34 17.249-11.537l1.635-3.966c18.146-117.982 15.439 106.05 15.472 183.143 0 12.369 10.028 22.398 22.389 22.398 12.361 0 22.39-10.029 22.39-22.398V331.622h8.982v112.196c0 12.369 10.029 22.398 22.39 22.398s22.39-10.029 22.39-22.398c-.011-79.908-26.343-323.038 35.094-186.958 1.38 3.056 4.269 8.803 5.911 10.186.265.222 3.555 4.423 10.718 5.197.816.088 6.57-1.904 8.384-3.461 2.978-2.56 7.84-16.93 1.731-31.307-6.108-14.377-35.47-78.46-42.953-95.453-7.483-16.992-22.598-16.71-25.832-17.128-5.814-.751-11.658-.702-12.642-.736-13.92-.48-18.043.394-57.452-.498z"/>
|
|
||||||
<path d="M466.406 272.568h-14.13a28.036 28.036 0 0 0 6.552-18.05c0-15.549-12.604-28.154-28.153-28.154s-28.154 12.605-28.154 28.154c0 6.87 2.464 13.163 6.553 18.05h-22.131a28.031 28.031 0 0 0 6.553-18.05c0-15.549-12.604-28.154-28.154-28.154-15.549 0-28.153 12.605-28.153 28.154 0 6.87 2.464 13.163 6.553 18.05h-22.491a28.036 28.036 0 0 0 6.552-18.05c0-2.477-.322-4.877-.923-7.165a27.356 27.356 0 0 0-8.209-13.587 28.05 28.05 0 0 0-19.022-7.402c-15.549 0-28.154 12.605-28.154 28.154 0 6.87 2.464 13.163 6.553 18.05H112.007c-10.22 0-18.504 8.284-18.504 18.495s8.284 18.495 18.504 18.495h354.399c10.22 0 18.503-8.284 18.503-18.495s-8.283-18.495-18.503-18.495z"/>
|
|
||||||
<path d="M370.467 205.351c0 15.115 12.25 27.373 27.374 27.373 15.121 0 27.371-12.258 27.371-27.373 0-15.115-12.25-27.373-27.371-27.373-15.124 0-27.374 12.258-27.374 27.373zm-5.125-21.374c15.122 0 27.372-12.258 27.372-27.373 0-15.115-12.25-27.373-27.372-27.373-15.123 0-27.373 12.258-27.373 27.373 0 15.114 12.25 27.373 27.373 27.373zm-32.498 48.747c15.122 0 27.372-12.258 27.372-27.373 0-15.115-12.25-27.373-27.372-27.373-15.123 0-27.373 12.258-27.373 27.373 0 15.114 12.25 27.373 27.373 27.373z"/>
|
|
||||||
</svg>
|
|
||||||
<div>Targ</div>
|
|
||||||
</ow-path>
|
|
||||||
<ow-path path="/news" selected="{{ page.select_news() }}" title="Aktualności">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 508 508" xml:space="preserve">
|
|
||||||
<path d="M437.4 197.6H307.6c-7.8 0-14.2 6.3-14.2 14.1v141.1c0 7.8 6.3 14.1 14.1 14.1h129.9c7.8 0 14.1-6.3 14.1-14.1V211.7c0-7.8-6.3-14.1-14.1-14.1zm-14 141.1H321.7V225.8h101.7v112.9zM360.7 409.3H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.7 6.3 14.1 14.1 14.1h290.1c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 338.7H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 268.1H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 197.6H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1z"/>
|
|
||||||
<path d="M493.9 0H14.1C6.3 0 0 6.3 0 14.1v479.8c0 7.8 6.3 14.1 14.1 14.1h403c3.7 0 7.3-1.5 10-4.1l76.8-76.8c2.6-2.6 4.1-6.2 4.1-10v-403C508 6.3 501.7 0 493.9 0zm-62.7 459.8v-28.6h28.6l-28.6 28.6zm48.6-56.8h-62.7c-7.8 0-14.1 6.3-14.1 14.1v62.7H28.2V28.2h451.6V403z"/>
|
|
||||||
<path d="M383.4 108.6c-5.8 0-10.6-4.7-10.6-10.6 0-5.8 4.7-10.6 10.6-10.6 2.9 0 5.7 1.2 7.7 3.3 2.7 2.8 7.1 3 10 .3 2.8-2.7 3-7.1.3-10-4.6-4.9-11.2-7.8-18-7.8-13.6 0-24.7 11.1-24.7 24.7s11.1 24.7 24.7 24.7c5.8 0 10.6 4.7 10.6 10.6 0 5.8-4.7 10.6-10.6 10.6-2.9 0-5.7-1.2-7.7-3.3-2.7-2.8-7.1-3-10-.3-2.8 2.7-3 7.1-.3 10 4.6 4.9 11.2 7.8 18 7.8 13.6 0 24.7-11.1 24.7-24.7s-11.1-24.7-24.7-24.7zM221.5 123c3.9 0 7.1-3.2 7.1-7.1 0-3.9-3.2-7.1-7.1-7.1h-20.8V87.9h20.8c3.9 0 7.1-3.2 7.1-7.1 0-3.9-3.2-7.1-7.1-7.1h-27.8c-3.9 0-7.1 3.2-7.1 7.1v70.5c.1 3.9 3.2 7.1 7.1 7.1h27.8c3.9 0 7.1-3.2 7.1-7.1s-3.2-7.1-7.1-7.1h-20.8V123h20.8zM149.4 73.7c-3.9 0-7.1 3.2-7.1 7.1v45L113 77.2c-1.6-2.7-4.9-4-7.9-3.2-3.1.8-5.2 3.6-5.2 6.8v70.5c0 3.9 3.2 7.1 7.1 7.1s7.1-3.2 7.1-7.1v-45l29.2 48.7c1.3 2.2 3.6 3.4 6.1 3.4.6 0 1.3-.1 1.9-.3 3.1-.8 5.2-3.6 5.2-6.8V80.8c0-3.9-3.2-7.1-7.1-7.1zM335.1 74.1c-3.8-1-7.6 1.2-8.6 5l-12.1 45S302.1 78.7 302 78.5c-.8-2.1-2.4-3.8-4.7-4.4-3.8-1-7.6 1.2-8.6 5l-12.1 45-12-45c-1-3.8-4.9-6-8.6-5-3.8 1-6 4.9-5 8.6l18.9 70.5c.8 3.1 3.6 5.2 6.8 5.2s6-2.1 6.8-5.2l12-45 12.1 45c.8 3.1 3.6 5.2 6.8 5.2 3.2 0 6-2.1 6.8-5.2l18.9-70.5c1-3.8-1.2-7.6-5-8.6z"/>
|
|
||||||
</svg>
|
|
||||||
<div>Aktualności</div>
|
|
||||||
</ow-path>
|
|
||||||
<ow-path path="/account" selected="{{ page.select_account() }}" title="Konto">
|
|
||||||
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
|
||||||
<path d="M16 7.992C16 3.58 12.416 0 8 0S0 3.58 0 7.992c0 2.43 1.104 4.62 2.832 6.09.016.016.032.016.032.032.144.112.288.224.448.336.08.048.144.111.224.175A7.98 7.98 0 0 0 8.016 16a7.98 7.98 0 0 0 4.48-1.375c.08-.048.144-.111.224-.16.144-.111.304-.223.448-.335.016-.016.032-.016.032-.032 1.696-1.487 2.8-3.676 2.8-6.106zm-8 7.001c-1.504 0-2.88-.48-4.016-1.279.016-.128.048-.255.08-.383a4.17 4.17 0 0 1 .416-.991c.176-.304.384-.576.64-.816.24-.24.528-.463.816-.639.304-.176.624-.304.976-.4A4.15 4.15 0 0 1 8 10.342a4.185 4.185 0 0 1 2.928 1.166c.368.368.656.8.864 1.295.112.288.192.592.24.911A7.03 7.03 0 0 1 8 14.993zm-2.448-7.4a2.49 2.49 0 0 1-.208-1.024c0-.351.064-.703.208-1.023.144-.32.336-.607.576-.847.24-.24.528-.431.848-.575.32-.144.672-.208 1.024-.208.368 0 .704.064 1.024.208.32.144.608.336.848.575.24.24.432.528.576.847.144.32.208.672.208 1.023 0 .368-.064.704-.208 1.023a2.84 2.84 0 0 1-.576.848 2.84 2.84 0 0 1-.848.575 2.715 2.715 0 0 1-2.064 0 2.84 2.84 0 0 1-.848-.575 2.526 2.526 0 0 1-.56-.848zm7.424 5.306c0-.032-.016-.048-.016-.08a5.22 5.22 0 0 0-.688-1.406 4.883 4.883 0 0 0-1.088-1.135 5.207 5.207 0 0 0-1.04-.608 2.82 2.82 0 0 0 .464-.383 4.2 4.2 0 0 0 .624-.784 3.624 3.624 0 0 0 .528-1.934 3.71 3.71 0 0 0-.288-1.47 3.799 3.799 0 0 0-.816-1.199 3.845 3.845 0 0 0-1.2-.8 3.72 3.72 0 0 0-1.472-.287 3.72 3.72 0 0 0-1.472.288 3.631 3.631 0 0 0-1.2.815 3.84 3.84 0 0 0-.8 1.199 3.71 3.71 0 0 0-.288 1.47c0 .352.048.688.144 1.007.096.336.224.64.4.927.16.288.384.544.624.784.144.144.304.271.48.383a5.12 5.12 0 0 0-1.04.624c-.416.32-.784.703-1.088 1.119a4.999 4.999 0 0 0-.688 1.406c-.016.032-.016.064-.016.08C1.776 11.636.992 9.91.992 7.992.992 4.14 4.144.991 8 .991s7.008 3.149 7.008 7.001a6.96 6.96 0 0 1-2.032 4.907z"/>
|
|
||||||
</svg>
|
|
||||||
<div>Konto</div>
|
|
||||||
</ow-path>
|
|
||||||
|
|
||||||
{% if h.is_above_user(account) %}
|
|
||||||
<ow-path path="/account/business-items" selected="{{ page.select_account_business() }}" title="Moje usługi">
|
|
||||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M25 26a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V5h10V3H5v23a3 3 0 0 0 3 3h16a3 3 0 0 0 3-3V13h-2Z"/>
|
|
||||||
<path d="M27.12 2.88a3.08 3.08 0 0 0-4.24 0L17 8.75l-1 5.3L21.25 13l5.87-5.87a3 3 0 0 0 0-4.25Zm-6.86 8.27-1.76.35.35-1.76 3.32-3.33 1.42 1.42Zm5.45-5.44-.71.7L23.59 5l.7-.71a1 1 0 0 1 1.42 0 1 1 0 0 1 0 1.42Z"/>
|
|
||||||
</svg>
|
|
||||||
<div>Moje usługi</div>
|
|
||||||
</ow-path>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<ow-path path="/account/offers" selected="{{ page.select_account_offers() }}" title="Moje sprzedaże">
|
|
||||||
<svg viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M30.412 19.045a.34.34 0 0 0-.34.341V30.83c0 .12-.102.222-.222.222h-2.627v-3.14h.87a.402.402 0 0 0 .372-.22.402.402 0 0 0-.054-.43l-1.818-2.346a.436.436 0 0 0-.346-.173h-.003a.437.437 0 0 0-.347.179l-1.758 2.343a.403.403 0 0 0-.049.429.403.403 0 0 0 .372.218h.806v3.14h-8.941a.594.594 0 0 1-.594-.594V19.716c0-.327.266-.594.594-.594H27.7a.34.34 0 1 0 0-.681H16.327c-.703 0-1.275.572-1.275 1.275v10.742c0 .703.572 1.275 1.275 1.275h9.282a.34.34 0 0 0 .34-.34V27.57a.34.34 0 0 0-.34-.34h-.592l1.233-1.645 1.274 1.645h-.642a.34.34 0 0 0-.34.34v3.821a.34.34 0 0 0 .34.34h2.968a.905.905 0 0 0 .903-.903V19.386a.34.34 0 0 0-.34-.34z"
|
|
||||||
/>
|
|
||||||
<path d="M17.52 15.715c-.638-.713-5.53-6.169-7.115-7.557 1.257-.794 2.022-2.14 2.022-3.61C12.427 2.168 10.42.23 7.952.23c-2.468 0-4.475 1.938-4.475 4.32 0 1.455.75 2.789 1.986 3.586-1.026.75-1.648 1.93-1.648 3.186v18.466c0 .971.819 1.761 1.825 1.761s1.824-.79 1.824-1.76V19.643h.938v10.143c0 .971.819 1.761 1.825 1.761s1.824-.79 1.824-1.76l.036-15.054 3.216 3.104c.304.294.704.44 1.104.44.4 0 .8-.146 1.104-.44a1.472 1.472 0 0 0 .008-2.123z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</ow-path>
|
|
||||||
|
|
||||||
{% if h.is_admin(account) %}
|
|
||||||
<ow-path path="/admin" selected="{{ page.select_admin_news() }}" title="Admin">
|
|
||||||
<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M14.68 14.81a6.76 6.76 0 1 1 6.76-6.75 6.77 6.77 0 0 1-6.76 6.75Zm0-11.51a4.76 4.76 0 1 0 4.76 4.76 4.76 4.76 0 0 0-4.76-4.76Z"/>
|
|
||||||
<path d="M16.42 31.68A2.14 2.14 0 0 1 15.8 30H4v-5.78a14.81 14.81 0 0 1 11.09-4.68h.72a2.2 2.2 0 0 1 .62-1.85l.12-.11c-.47 0-1-.06-1.46-.06A16.47 16.47 0 0 0 2.2 23.26a1 1 0 0 0-.2.6V30a2 2 0 0 0 2 2h12.7Z"/>
|
|
||||||
<path d="M26.87 16.29a.37.37 0 0 1 .15 0 .42.42 0 0 0-.15 0Z"/>
|
|
||||||
<path d="m33.68 23.32-2-.61a7.21 7.21 0 0 0-.58-1.41l1-1.86A.38.38 0 0 0 32 19l-1.45-1.45a.36.36 0 0 0-.44-.07l-1.84 1a7.15 7.15 0 0 0-1.43-.61l-.61-2a.36.36 0 0 0-.36-.24h-2.05a.36.36 0 0 0-.35.26l-.61 2a7 7 0 0 0-1.44.6l-1.82-1a.35.35 0 0 0-.43.07L17.69 19a.38.38 0 0 0-.06.44l1 1.82a6.77 6.77 0 0 0-.63 1.43l-2 .6a.36.36 0 0 0-.26.35v2.05A.35.35 0 0 0 16 26l2 .61a7 7 0 0 0 .6 1.41l-1 1.91a.36.36 0 0 0 .06.43l1.45 1.45a.38.38 0 0 0 .44.07l1.87-1a7.09 7.09 0 0 0 1.4.57l.6 2a.38.38 0 0 0 .35.26h2.05a.37.37 0 0 0 .35-.26l.61-2.05a6.92 6.92 0 0 0 1.38-.57l1.89 1a.36.36 0 0 0 .43-.07L32 30.4a.35.35 0 0 0 0-.4l-1-1.88a7 7 0 0 0 .58-1.39l2-.61a.36.36 0 0 0 .26-.35v-2.1a.36.36 0 0 0-.16-.35ZM24.85 28a3.34 3.34 0 1 1 3.33-3.33A3.34 3.34 0 0 1 24.85 28Z"/>
|
|
||||||
<path fill="none" d="M0 0h36v36H0z"/>
|
|
||||||
</svg>
|
|
||||||
<div>Admin</div>
|
|
||||||
</ow-path>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% else if page.is_admin() %}
|
|
||||||
<ow-path path="/admin/news" selected="{{ page.select_admin_news() }}" title="Zarządzaj aktualnościami">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 508 508" xml:space="preserve">
|
|
||||||
<path d="M437.4 197.6H307.6c-7.8 0-14.2 6.3-14.2 14.1v141.1c0 7.8 6.3 14.1 14.1 14.1h129.9c7.8 0 14.1-6.3 14.1-14.1V211.7c0-7.8-6.3-14.1-14.1-14.1zm-14 141.1H321.7V225.8h101.7v112.9zM360.7 409.3H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.7 6.3 14.1 14.1 14.1h290.1c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 338.7H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 268.1H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 197.6H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1z"/>
|
|
||||||
<path d="M493.9 0H14.1C6.3 0 0 6.3 0 14.1v479.8c0 7.8 6.3 14.1 14.1 14.1h403c3.7 0 7.3-1.5 10-4.1l76.8-76.8c2.6-2.6 4.1-6.2 4.1-10v-403C508 6.3 501.7 0 493.9 0zm-62.7 459.8v-28.6h28.6l-28.6 28.6zm48.6-56.8h-62.7c-7.8 0-14.1 6.3-14.1 14.1v62.7H28.2V28.2h451.6V403z"/>
|
|
||||||
<path d="M383.4 108.6c-5.8 0-10.6-4.7-10.6-10.6 0-5.8 4.7-10.6 10.6-10.6 2.9 0 5.7 1.2 7.7 3.3 2.7 2.8 7.1 3 10 .3 2.8-2.7 3-7.1.3-10-4.6-4.9-11.2-7.8-18-7.8-13.6 0-24.7 11.1-24.7 24.7s11.1 24.7 24.7 24.7c5.8 0 10.6 4.7 10.6 10.6 0 5.8-4.7 10.6-10.6 10.6-2.9 0-5.7-1.2-7.7-3.3-2.7-2.8-7.1-3-10-.3-2.8 2.7-3 7.1-.3 10 4.6 4.9 11.2 7.8 18 7.8 13.6 0 24.7-11.1 24.7-24.7s-11.1-24.7-24.7-24.7zM221.5 123c3.9 0 7.1-3.2 7.1-7.1 0-3.9-3.2-7.1-7.1-7.1h-20.8V87.9h20.8c3.9 0 7.1-3.2 7.1-7.1 0-3.9-3.2-7.1-7.1-7.1h-27.8c-3.9 0-7.1 3.2-7.1 7.1v70.5c.1 3.9 3.2 7.1 7.1 7.1h27.8c3.9 0 7.1-3.2 7.1-7.1s-3.2-7.1-7.1-7.1h-20.8V123h20.8zM149.4 73.7c-3.9 0-7.1 3.2-7.1 7.1v45L113 77.2c-1.6-2.7-4.9-4-7.9-3.2-3.1.8-5.2 3.6-5.2 6.8v70.5c0 3.9 3.2 7.1 7.1 7.1s7.1-3.2 7.1-7.1v-45l29.2 48.7c1.3 2.2 3.6 3.4 6.1 3.4.6 0 1.3-.1 1.9-.3 3.1-.8 5.2-3.6 5.2-6.8V80.8c0-3.9-3.2-7.1-7.1-7.1zM335.1 74.1c-3.8-1-7.6 1.2-8.6 5l-12.1 45S302.1 78.7 302 78.5c-.8-2.1-2.4-3.8-4.7-4.4-3.8-1-7.6 1.2-8.6 5l-12.1 45-12-45c-1-3.8-4.9-6-8.6-5-3.8 1-6 4.9-5 8.6l18.9 70.5c.8 3.1 3.6 5.2 6.8 5.2s6-2.1 6.8-5.2l12-45 12.1 45c.8 3.1 3.6 5.2 6.8 5.2 3.2 0 6-2.1 6.8-5.2l18.9-70.5c1-3.8-1.2-7.6-5-8.6z"/>
|
|
||||||
</svg>
|
|
||||||
<div>Aktualności</div>
|
|
||||||
</ow-path>
|
|
||||||
<ow-path path="/admin/businesses" selected="{{ page.select_admin_businesses() }}"
|
|
||||||
title="Zarządzaj usługami">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.999 511.999" xml:space="preserve">
|
|
||||||
<path d="M440.482 50.916c-8.099-4.226-18.091-1.088-22.321 7.013l-41.925 80.336-46.147 36.662h-54.683l-57.033-18.756-33.145-53.595 5.854-5.694c3.554-3.457 3.634-9.143.176-12.697l-13.735-14.12 10.294-10.014c4.024-3.914 4.113-10.35.198-14.374l-41.438-42.6c-3.914-4.024-10.35-4.113-14.374-.198l-66.5 64.687c-4.024 3.914-4.113 10.35-.198 14.374l41.438 42.599c3.914 4.024 10.35 4.113 14.374.198l10.294-10.013 13.737 14.121c3.449 3.547 9.136 3.64 12.696.176l2.896-2.817 32.466 52.499a16.544 16.544 0 0 0 8.901 7.014l58.436 19.218.008 287.213c0 10.964 8.888 19.853 19.852 19.853s19.852-8.888 19.852-19.853V333.466l8.565-2.239 24.462 64.136-17.398 78.459c-2.373 10.704 4.38 21.306 15.084 23.679 10.69 2.373 21.303-4.371 23.679-15.083l18.771-84.647a19.858 19.858 0 0 0-1.096-12.026l-24.153-57.154V202.66l51.197-40.674a16.547 16.547 0 0 0 4.375-5.299l43.551-83.45c4.23-8.1 1.089-18.094-7.01-22.321zM171.921 90.467a16.48 16.48 0 0 0-12.357 2.058c-6.031 3.73-8.81 10.704-7.514 17.272l-.092.088-7.475-7.683 20.167-19.617 7.475 7.684-.204.198z"/>
|
|
||||||
<circle cx="304.734" cy="129.707" r="34.286"/>
|
|
||||||
</svg>
|
|
||||||
<div>Lokalne Usługi</div>
|
|
||||||
</ow-path>
|
|
||||||
{% endif %}
|
|
||||||
</ow-nav>
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
|
109
assets/templates/nav.html
Normal file
109
assets/templates/nav.html
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<ow-nav>
|
||||||
|
<ow-path path="/" id="home" title="OS Wilno">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511 511" xml:space="preserve">
|
||||||
|
<path d="M127.5 143h121.675a31.386 31.386 0 0 0-1.175 8.5 7.5 7.5 0 0 0 7.5 7.5h128a7.5 7.5 0 0 0 7.5-7.5c0-17.369-14.131-31.5-31.5-31.5-1.387 0-2.789.108-4.222.327C346.299 110.022 333.268 104 319.5 104s-26.799 6.022-35.778 16.327A27.875 27.875 0 0 0 279.5 120a31.314 31.314 0 0 0-17.439 5.284c-2.147-11.751-7.917-22.248-16.119-30.284H327.5a7.5 7.5 0 0 0 7.5-7.5c0-26.191-21.309-47.5-47.5-47.5-11.859 0-22.976 4.337-31.73 12.298A31.495 31.495 0 0 0 251.5 52c-16.921 0-31.106 11.904-34.643 27.775a56.764 56.764 0 0 0-10.588-1.006c-14.429 0-27.94 5.38-38.486 15.237a37.778 37.778 0 0 0-5.822-.468C138.824 93.539 120 112.362 120 135.5a7.5 7.5 0 0 0 7.5 7.5zm124-76c1.475 0 3.04.218 4.926.687a7.505 7.505 0 0 0 7.31-2.184C270.008 58.73 278.447 55 287.5 55c15.34 0 28.232 10.683 31.626 25H232.42c3-7.605 10.422-13 19.08-13zm28 68c1.482 0 3.046.276 4.923.868a7.502 7.502 0 0 0 8.413-2.87C298.933 124.233 308.901 119 319.5 119s20.567 5.233 26.665 13.998a7.5 7.5 0 0 0 8.413 2.87c1.877-.592 3.441-.868 4.923-.868 6.399 0 11.959 3.662 14.695 9h-109.39c2.735-5.338 8.295-9 14.694-9zm-117.539-26.461c1.96 0 4.019.285 6.479.896a7.498 7.498 0 0 0 7.311-2.183c8.051-8.694 18.889-13.482 30.518-13.482 20.45 0 37.512 14.788 41.056 34.231H136.061c3.257-11.23 13.635-19.462 25.9-19.462z"/>
|
||||||
|
<path d="M510.905 262.363c.06-2.283.095-4.571.095-6.863 0-68.247-26.577-132.408-74.834-180.666C387.908 26.577 323.747 0 255.5 0S123.092 26.577 74.834 74.834C26.577 123.092 0 187.253 0 255.5s26.577 132.408 74.834 180.666C123.092 484.423 187.253 511 255.5 511s132.408-26.577 180.666-74.834c45.966-45.966 72.242-106.365 74.635-170.975a7.538 7.538 0 0 0 .198-1.691 7.486 7.486 0 0 0-.094-1.137zM85.441 85.441C130.865 40.016 191.26 15 255.5 15s124.635 25.016 170.059 70.441C470.984 130.865 496 191.26 496 255.5c0 .167-.006.333-.006.5H463v-8.5a7.5 7.5 0 0 0-15 0v8.5h-17v-25h.5a7.499 7.499 0 0 0 5.303-12.803l-32-32A7.497 7.497 0 0 0 399.5 184h-80c-1.989 0-3.897.79-5.303 2.197l-26.92 26.92-2.015-2.418A7.5 7.5 0 0 0 279.5 208H263v-32.5a7.5 7.5 0 0 0-15 0v.5h-17v-.5a7.5 7.5 0 0 0-15 0v.5h-65v-.5a7.5 7.5 0 0 0-15 0v.5h-17v-.5a7.5 7.5 0 0 0-15 0V224H17.046c6.8-52.302 30.482-100.646 68.395-138.559zM494.769 280H431v-9h64.494c-.19 3.01-.425 6.012-.725 9zm-15.521 64H431v-49h61.776a238.363 238.363 0 0 1-13.528 49zm-15.521 32.011c-.076-.002-.15-.011-.227-.011h-416c-.076 0-.151.009-.227.011A238.142 238.142 0 0 1 38.279 359h434.44a237.097 237.097 0 0 1-8.992 17.011zm-15.951 24.003c-.092-.003-.183-.014-.276-.014h-384c-.093 0-.184.01-.276.014A239.546 239.546 0 0 1 56.743 391h397.515a240.711 240.711 0 0 1-6.482 9.014zM15 255.5c0-5.532.199-11.032.567-16.5H104v17H15.5c-.167 0-.33.014-.494.025 0-.175-.006-.35-.006-.525zM143.5 199a7.5 7.5 0 0 0 7.5-7.5v-.5h65v.5a7.5 7.5 0 0 0 15 0v-.5h17v17H119v-17h17v.5a7.5 7.5 0 0 0 7.5 7.5zM416 231v113h-97v-73.027c.168.011.335.027.504.027a7.5 7.5 0 0 0 5.757-12.301L302.179 231H416zm-110.394-15 17-17h73.787l17 17H305.606zM207 344v-73h97v73h-97zm-88 0V223h96.487l-29.749 35.699a7.5 7.5 0 0 0 .96 10.563 7.464 7.464 0 0 0 5.301 1.715V344H119zm184.487-88h-95.975l27.5-33h40.975l27.5 33zM104 271v73H31.752c-9.135-23.111-14.646-47.678-16.247-73H104zm151.5 225c-64.24 0-124.635-25.016-170.059-70.441A244.667 244.667 0 0 1 75.51 415h359.98a245.917 245.917 0 0 1-9.931 10.559C380.135 470.984 319.74 496 255.5 496z"/>
|
||||||
|
<path d="M143.5 240a7.5 7.5 0 0 0-7.5 7.5v16a7.5 7.5 0 0 0 15 0v-16a7.5 7.5 0 0 0-7.5-7.5zM167.5 240a7.5 7.5 0 0 0-7.5 7.5v16a7.5 7.5 0 0 0 15 0v-16a7.5 7.5 0 0 0-7.5-7.5zM143.5 288a7.5 7.5 0 0 0-7.5 7.5v24a7.5 7.5 0 0 0 15 0v-24a7.5 7.5 0 0 0-7.5-7.5zM167.5 288a7.5 7.5 0 0 0-7.5 7.5v24a7.5 7.5 0 0 0 15 0v-24a7.5 7.5 0 0 0-7.5-7.5zM55.5 327a7.5 7.5 0 0 0 7.5-7.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 7.5 7.5zM39 319.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 15 0zM79.5 327a7.5 7.5 0 0 0 7.5-7.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 7.5 7.5zM231.5 288a7.5 7.5 0 0 0-7.5 7.5v24a7.5 7.5 0 0 0 15 0v-24a7.5 7.5 0 0 0-7.5-7.5zM255.5 288a7.5 7.5 0 0 0-7.5 7.5v24a7.5 7.5 0 0 0 15 0v-24a7.5 7.5 0 0 0-7.5-7.5zM279.5 288a7.5 7.5 0 0 0-7.5 7.5v24a7.5 7.5 0 0 0 15 0v-24a7.5 7.5 0 0 0-7.5-7.5zM343.5 327a7.5 7.5 0 0 0 7.5-7.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 7.5 7.5zM367.5 327a7.5 7.5 0 0 0 7.5-7.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 7.5 7.5zM391.5 327a7.5 7.5 0 0 0 7.5-7.5v-24a7.5 7.5 0 0 0-15 0v24a7.5 7.5 0 0 0 7.5 7.5zM447.5 304a7.5 7.5 0 0 0-7.5 7.5v8a7.5 7.5 0 0 0 15 0v-8a7.5 7.5 0 0 0-7.5-7.5zM471.5 304a7.5 7.5 0 0 0-7.5 7.5v8a7.5 7.5 0 0 0 15 0v-8a7.5 7.5 0 0 0-7.5-7.5zM343.5 271a7.5 7.5 0 0 0 7.5-7.5v-8a7.5 7.5 0 0 0-15 0v8a7.5 7.5 0 0 0 7.5 7.5zM367.5 271a7.5 7.5 0 0 0 7.5-7.5v-8a7.5 7.5 0 0 0-15 0v8a7.5 7.5 0 0 0 7.5 7.5zM391.5 271a7.5 7.5 0 0 0 7.5-7.5v-8a7.5 7.5 0 0 0-15 0v8a7.5 7.5 0 0 0 7.5 7.5z"/>
|
||||||
|
</svg>
|
||||||
|
<span>OS Wilno</span>
|
||||||
|
</ow-path>
|
||||||
|
|
||||||
|
{% if page.is_public() -%}
|
||||||
|
<ow-path path="/" selected="{{ page.select_index() }}" title="Lokalne usługi">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.999 511.999" xml:space="preserve">
|
||||||
|
<path d="M440.482 50.916c-8.099-4.226-18.091-1.088-22.321 7.013l-41.925 80.336-46.147 36.662h-54.683l-57.033-18.756-33.145-53.595 5.854-5.694c3.554-3.457 3.634-9.143.176-12.697l-13.735-14.12 10.294-10.014c4.024-3.914 4.113-10.35.198-14.374l-41.438-42.6c-3.914-4.024-10.35-4.113-14.374-.198l-66.5 64.687c-4.024 3.914-4.113 10.35-.198 14.374l41.438 42.599c3.914 4.024 10.35 4.113 14.374.198l10.294-10.013 13.737 14.121c3.449 3.547 9.136 3.64 12.696.176l2.896-2.817 32.466 52.499a16.544 16.544 0 0 0 8.901 7.014l58.436 19.218.008 287.213c0 10.964 8.888 19.853 19.852 19.853s19.852-8.888 19.852-19.853V333.466l8.565-2.239 24.462 64.136-17.398 78.459c-2.373 10.704 4.38 21.306 15.084 23.679 10.69 2.373 21.303-4.371 23.679-15.083l18.771-84.647a19.858 19.858 0 0 0-1.096-12.026l-24.153-57.154V202.66l51.197-40.674a16.547 16.547 0 0 0 4.375-5.299l43.551-83.45c4.23-8.1 1.089-18.094-7.01-22.321zM171.921 90.467a16.48 16.48 0 0 0-12.357 2.058c-6.031 3.73-8.81 10.704-7.514 17.272l-.092.088-7.475-7.683 20.167-19.617 7.475 7.684-.204.198z"/>
|
||||||
|
<circle cx="304.734" cy="129.707" r="34.286"/>
|
||||||
|
</svg>
|
||||||
|
<div>Lokalne Usługi</div>
|
||||||
|
</ow-path>
|
||||||
|
<ow-path path="/marketplace" selected="{{ page.select_marketplace() }}" title="Targ">
|
||||||
|
<svg viewBox="0 0 484.909 484.909" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M204.993 438.478c-6.347 6.349-6.347 16.639 0 22.978a16.196 16.196 0 0 0 11.488 4.761c4.158 0 8.316-1.587 11.489-4.761l49.747-49.754-22.979-22.978zm112.649-112.671-16.947 16.954 22.976 22.977 39.926-39.931zm-56.872 0h-45.954l135.627 135.648a16.193 16.193 0 0 0 11.487 4.761c4.158 0 8.315-1.587 11.488-4.761 6.349-6.339 6.349-16.629 0-22.978zM102.294 107.658c21.471 0 38.878-19.915 38.878-44.478 0-24.564-17.407-44.487-38.878-44.487-21.486 0-38.877 19.923-38.877 44.487 0 24.563 17.391 44.478 38.877 44.478zm-15.17 48.128c-58.083-103.857-29.041-51.929 0 0z"/>
|
||||||
|
<path d="M74.524 123.66c-7.062.128-11.934.302-12.44.539-5.554 1.365-19.132 13.9-21.512 19.605L1.42 250.377c-3.937 9.521.586 20.439 10.107 24.382a18.79 18.79 0 0 0 7.14 1.42c7.315 0 14.266-4.34 17.249-11.537l1.635-3.966c18.146-117.982 15.439 106.05 15.472 183.143 0 12.369 10.028 22.398 22.389 22.398 12.361 0 22.39-10.029 22.39-22.398V331.622h8.982v112.196c0 12.369 10.029 22.398 22.39 22.398s22.39-10.029 22.39-22.398c-.011-79.908-26.343-323.038 35.094-186.958 1.38 3.056 4.269 8.803 5.911 10.186.265.222 3.555 4.423 10.718 5.197.816.088 6.57-1.904 8.384-3.461 2.978-2.56 7.84-16.93 1.731-31.307-6.108-14.377-35.47-78.46-42.953-95.453-7.483-16.992-22.598-16.71-25.832-17.128-5.814-.751-11.658-.702-12.642-.736-13.92-.48-18.043.394-57.452-.498z"/>
|
||||||
|
<path d="M466.406 272.568h-14.13a28.036 28.036 0 0 0 6.552-18.05c0-15.549-12.604-28.154-28.153-28.154s-28.154 12.605-28.154 28.154c0 6.87 2.464 13.163 6.553 18.05h-22.131a28.031 28.031 0 0 0 6.553-18.05c0-15.549-12.604-28.154-28.154-28.154-15.549 0-28.153 12.605-28.153 28.154 0 6.87 2.464 13.163 6.553 18.05h-22.491a28.036 28.036 0 0 0 6.552-18.05c0-2.477-.322-4.877-.923-7.165a27.356 27.356 0 0 0-8.209-13.587 28.05 28.05 0 0 0-19.022-7.402c-15.549 0-28.154 12.605-28.154 28.154 0 6.87 2.464 13.163 6.553 18.05H112.007c-10.22 0-18.504 8.284-18.504 18.495s8.284 18.495 18.504 18.495h354.399c10.22 0 18.503-8.284 18.503-18.495s-8.283-18.495-18.503-18.495z"/>
|
||||||
|
<path d="M370.467 205.351c0 15.115 12.25 27.373 27.374 27.373 15.121 0 27.371-12.258 27.371-27.373 0-15.115-12.25-27.373-27.371-27.373-15.124 0-27.374 12.258-27.374 27.373zm-5.125-21.374c15.122 0 27.372-12.258 27.372-27.373 0-15.115-12.25-27.373-27.372-27.373-15.123 0-27.373 12.258-27.373 27.373 0 15.114 12.25 27.373 27.373 27.373zm-32.498 48.747c15.122 0 27.372-12.258 27.372-27.373 0-15.115-12.25-27.373-27.372-27.373-15.123 0-27.373 12.258-27.373 27.373 0 15.114 12.25 27.373 27.373 27.373z"/>
|
||||||
|
</svg>
|
||||||
|
<div>Targ</div>
|
||||||
|
</ow-path>
|
||||||
|
<ow-path path="/news" selected="{{ page.select_news() }}" title="Aktualności">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 508 508" xml:space="preserve">
|
||||||
|
<path d="M437.4 197.6H307.6c-7.8 0-14.2 6.3-14.2 14.1v141.1c0 7.8 6.3 14.1 14.1 14.1h129.9c7.8 0 14.1-6.3 14.1-14.1V211.7c0-7.8-6.3-14.1-14.1-14.1zm-14 141.1H321.7V225.8h101.7v112.9zM360.7 409.3H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.7 6.3 14.1 14.1 14.1h290.1c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 338.7H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 268.1H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 197.6H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1z"/>
|
||||||
|
<path d="M493.9 0H14.1C6.3 0 0 6.3 0 14.1v479.8c0 7.8 6.3 14.1 14.1 14.1h403c3.7 0 7.3-1.5 10-4.1l76.8-76.8c2.6-2.6 4.1-6.2 4.1-10v-403C508 6.3 501.7 0 493.9 0zm-62.7 459.8v-28.6h28.6l-28.6 28.6zm48.6-56.8h-62.7c-7.8 0-14.1 6.3-14.1 14.1v62.7H28.2V28.2h451.6V403z"/>
|
||||||
|
<path d="M383.4 108.6c-5.8 0-10.6-4.7-10.6-10.6 0-5.8 4.7-10.6 10.6-10.6 2.9 0 5.7 1.2 7.7 3.3 2.7 2.8 7.1 3 10 .3 2.8-2.7 3-7.1.3-10-4.6-4.9-11.2-7.8-18-7.8-13.6 0-24.7 11.1-24.7 24.7s11.1 24.7 24.7 24.7c5.8 0 10.6 4.7 10.6 10.6 0 5.8-4.7 10.6-10.6 10.6-2.9 0-5.7-1.2-7.7-3.3-2.7-2.8-7.1-3-10-.3-2.8 2.7-3 7.1-.3 10 4.6 4.9 11.2 7.8 18 7.8 13.6 0 24.7-11.1 24.7-24.7s-11.1-24.7-24.7-24.7zM221.5 123c3.9 0 7.1-3.2 7.1-7.1 0-3.9-3.2-7.1-7.1-7.1h-20.8V87.9h20.8c3.9 0 7.1-3.2 7.1-7.1 0-3.9-3.2-7.1-7.1-7.1h-27.8c-3.9 0-7.1 3.2-7.1 7.1v70.5c.1 3.9 3.2 7.1 7.1 7.1h27.8c3.9 0 7.1-3.2 7.1-7.1s-3.2-7.1-7.1-7.1h-20.8V123h20.8zM149.4 73.7c-3.9 0-7.1 3.2-7.1 7.1v45L113 77.2c-1.6-2.7-4.9-4-7.9-3.2-3.1.8-5.2 3.6-5.2 6.8v70.5c0 3.9 3.2 7.1 7.1 7.1s7.1-3.2 7.1-7.1v-45l29.2 48.7c1.3 2.2 3.6 3.4 6.1 3.4.6 0 1.3-.1 1.9-.3 3.1-.8 5.2-3.6 5.2-6.8V80.8c0-3.9-3.2-7.1-7.1-7.1zM335.1 74.1c-3.8-1-7.6 1.2-8.6 5l-12.1 45S302.1 78.7 302 78.5c-.8-2.1-2.4-3.8-4.7-4.4-3.8-1-7.6 1.2-8.6 5l-12.1 45-12-45c-1-3.8-4.9-6-8.6-5-3.8 1-6 4.9-5 8.6l18.9 70.5c.8 3.1 3.6 5.2 6.8 5.2s6-2.1 6.8-5.2l12-45 12.1 45c.8 3.1 3.6 5.2 6.8 5.2 3.2 0 6-2.1 6.8-5.2l18.9-70.5c1-3.8-1.2-7.6-5-8.6z"/>
|
||||||
|
</svg>
|
||||||
|
<div>Aktualności</div>
|
||||||
|
</ow-path>
|
||||||
|
<ow-path path="/account" selected="{{ page.select_account() }}" title="Konto">
|
||||||
|
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
||||||
|
<path d="M16 7.992C16 3.58 12.416 0 8 0S0 3.58 0 7.992c0 2.43 1.104 4.62 2.832 6.09.016.016.032.016.032.032.144.112.288.224.448.336.08.048.144.111.224.175A7.98 7.98 0 0 0 8.016 16a7.98 7.98 0 0 0 4.48-1.375c.08-.048.144-.111.224-.16.144-.111.304-.223.448-.335.016-.016.032-.016.032-.032 1.696-1.487 2.8-3.676 2.8-6.106zm-8 7.001c-1.504 0-2.88-.48-4.016-1.279.016-.128.048-.255.08-.383a4.17 4.17 0 0 1 .416-.991c.176-.304.384-.576.64-.816.24-.24.528-.463.816-.639.304-.176.624-.304.976-.4A4.15 4.15 0 0 1 8 10.342a4.185 4.185 0 0 1 2.928 1.166c.368.368.656.8.864 1.295.112.288.192.592.24.911A7.03 7.03 0 0 1 8 14.993zm-2.448-7.4a2.49 2.49 0 0 1-.208-1.024c0-.351.064-.703.208-1.023.144-.32.336-.607.576-.847.24-.24.528-.431.848-.575.32-.144.672-.208 1.024-.208.368 0 .704.064 1.024.208.32.144.608.336.848.575.24.24.432.528.576.847.144.32.208.672.208 1.023 0 .368-.064.704-.208 1.023a2.84 2.84 0 0 1-.576.848 2.84 2.84 0 0 1-.848.575 2.715 2.715 0 0 1-2.064 0 2.84 2.84 0 0 1-.848-.575 2.526 2.526 0 0 1-.56-.848zm7.424 5.306c0-.032-.016-.048-.016-.08a5.22 5.22 0 0 0-.688-1.406 4.883 4.883 0 0 0-1.088-1.135 5.207 5.207 0 0 0-1.04-.608 2.82 2.82 0 0 0 .464-.383 4.2 4.2 0 0 0 .624-.784 3.624 3.624 0 0 0 .528-1.934 3.71 3.71 0 0 0-.288-1.47 3.799 3.799 0 0 0-.816-1.199 3.845 3.845 0 0 0-1.2-.8 3.72 3.72 0 0 0-1.472-.287 3.72 3.72 0 0 0-1.472.288 3.631 3.631 0 0 0-1.2.815 3.84 3.84 0 0 0-.8 1.199 3.71 3.71 0 0 0-.288 1.47c0 .352.048.688.144 1.007.096.336.224.64.4.927.16.288.384.544.624.784.144.144.304.271.48.383a5.12 5.12 0 0 0-1.04.624c-.416.32-.784.703-1.088 1.119a4.999 4.999 0 0 0-.688 1.406c-.016.032-.016.064-.016.08C1.776 11.636.992 9.91.992 7.992.992 4.14 4.144.991 8 .991s7.008 3.149 7.008 7.001a6.96 6.96 0 0 1-2.032 4.907z"/>
|
||||||
|
</svg>
|
||||||
|
<div>Konto</div>
|
||||||
|
</ow-path>
|
||||||
|
|
||||||
|
{% if h.is_above_user(account) -%}
|
||||||
|
<ow-path path="/account/business-items" selected="{{ page.select_account_business() }}" title="Moje usługi">
|
||||||
|
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M25 26a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V5h10V3H5v23a3 3 0 0 0 3 3h16a3 3 0 0 0 3-3V13h-2Z"/>
|
||||||
|
<path d="M27.12 2.88a3.08 3.08 0 0 0-4.24 0L17 8.75l-1 5.3L21.25 13l5.87-5.87a3 3 0 0 0 0-4.25Zm-6.86 8.27-1.76.35.35-1.76 3.32-3.33 1.42 1.42Zm5.45-5.44-.71.7L23.59 5l.7-.71a1 1 0 0 1 1.42 0 1 1 0 0 1 0 1.42Z"/>
|
||||||
|
</svg>
|
||||||
|
<div>Moje usługi</div>
|
||||||
|
</ow-path>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
<ow-path path="/account/offers" selected="{{ page.select_account_offers() }}" title="Moje sprzedaże">
|
||||||
|
<svg viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M30.412 19.045a.34.34 0 0 0-.34.341V30.83c0 .12-.102.222-.222.222h-2.627v-3.14h.87a.402.402 0 0 0 .372-.22.402.402 0 0 0-.054-.43l-1.818-2.346a.436.436 0 0 0-.346-.173h-.003a.437.437 0 0 0-.347.179l-1.758 2.343a.403.403 0 0 0-.049.429.403.403 0 0 0 .372.218h.806v3.14h-8.941a.594.594 0 0 1-.594-.594V19.716c0-.327.266-.594.594-.594H27.7a.34.34 0 1 0 0-.681H16.327c-.703 0-1.275.572-1.275 1.275v10.742c0 .703.572 1.275 1.275 1.275h9.282a.34.34 0 0 0 .34-.34V27.57a.34.34 0 0 0-.34-.34h-.592l1.233-1.645 1.274 1.645h-.642a.34.34 0 0 0-.34.34v3.821a.34.34 0 0 0 .34.34h2.968a.905.905 0 0 0 .903-.903V19.386a.34.34 0 0 0-.34-.34z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M17.52 15.715c-.638-.713-5.53-6.169-7.115-7.557 1.257-.794 2.022-2.14 2.022-3.61C12.427 2.168 10.42.23 7.952.23c-2.468 0-4.475 1.938-4.475 4.32 0 1.455.75 2.789 1.986 3.586-1.026.75-1.648 1.93-1.648 3.186v18.466c0 .971.819 1.761 1.825 1.761s1.824-.79 1.824-1.76V19.643h.938v10.143c0 .971.819 1.761 1.825 1.761s1.824-.79 1.824-1.76l.036-15.054 3.216 3.104c.304.294.704.44 1.104.44.4 0 .8-.146 1.104-.44a1.472 1.472 0 0 0 .008-2.123z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</ow-path>
|
||||||
|
|
||||||
|
{% if h.is_admin(account) -%}
|
||||||
|
<ow-path path="/admin" selected="{{ page.select_admin_news() }}" title="Admin">
|
||||||
|
<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.68 14.81a6.76 6.76 0 1 1 6.76-6.75 6.77 6.77 0 0 1-6.76 6.75Zm0-11.51a4.76 4.76 0 1 0 4.76 4.76 4.76 4.76 0 0 0-4.76-4.76Z"/>
|
||||||
|
<path d="M16.42 31.68A2.14 2.14 0 0 1 15.8 30H4v-5.78a14.81 14.81 0 0 1 11.09-4.68h.72a2.2 2.2 0 0 1 .62-1.85l.12-.11c-.47 0-1-.06-1.46-.06A16.47 16.47 0 0 0 2.2 23.26a1 1 0 0 0-.2.6V30a2 2 0 0 0 2 2h12.7Z"/>
|
||||||
|
<path d="M26.87 16.29a.37.37 0 0 1 .15 0 .42.42 0 0 0-.15 0Z"/>
|
||||||
|
<path d="m33.68 23.32-2-.61a7.21 7.21 0 0 0-.58-1.41l1-1.86A.38.38 0 0 0 32 19l-1.45-1.45a.36.36 0 0 0-.44-.07l-1.84 1a7.15 7.15 0 0 0-1.43-.61l-.61-2a.36.36 0 0 0-.36-.24h-2.05a.36.36 0 0 0-.35.26l-.61 2a7 7 0 0 0-1.44.6l-1.82-1a.35.35 0 0 0-.43.07L17.69 19a.38.38 0 0 0-.06.44l1 1.82a6.77 6.77 0 0 0-.63 1.43l-2 .6a.36.36 0 0 0-.26.35v2.05A.35.35 0 0 0 16 26l2 .61a7 7 0 0 0 .6 1.41l-1 1.91a.36.36 0 0 0 .06.43l1.45 1.45a.38.38 0 0 0 .44.07l1.87-1a7.09 7.09 0 0 0 1.4.57l.6 2a.38.38 0 0 0 .35.26h2.05a.37.37 0 0 0 .35-.26l.61-2.05a6.92 6.92 0 0 0 1.38-.57l1.89 1a.36.36 0 0 0 .43-.07L32 30.4a.35.35 0 0 0 0-.4l-1-1.88a7 7 0 0 0 .58-1.39l2-.61a.36.36 0 0 0 .26-.35v-2.1a.36.36 0 0 0-.16-.35ZM24.85 28a3.34 3.34 0 1 1 3.33-3.33A3.34 3.34 0 0 1 24.85 28Z"/>
|
||||||
|
<path fill="none" d="M0 0h36v36H0z"/>
|
||||||
|
</svg>
|
||||||
|
<div>Admin</div>
|
||||||
|
</ow-path>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{% else if page.is_admin() -%}
|
||||||
|
<ow-path path="/admin/news" selected="{{ page.select_admin_news() }}" title="Zarządzaj aktualnościami">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 508 508" xml:space="preserve">
|
||||||
|
<path d="M437.4 197.6H307.6c-7.8 0-14.2 6.3-14.2 14.1v141.1c0 7.8 6.3 14.1 14.1 14.1h129.9c7.8 0 14.1-6.3 14.1-14.1V211.7c0-7.8-6.3-14.1-14.1-14.1zm-14 141.1H321.7V225.8h101.7v112.9zM360.7 409.3H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.7 6.3 14.1 14.1 14.1h290.1c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 338.7H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 268.1H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1zM251.2 197.6H70.6c-7.8 0-14.1 6.3-14.1 14.1-.1 7.8 6.3 14.1 14.1 14.1h180.6c7.8 0 14.1-6.3 14.1-14.1s-6.3-14.1-14.1-14.1z"/>
|
||||||
|
<path d="M493.9 0H14.1C6.3 0 0 6.3 0 14.1v479.8c0 7.8 6.3 14.1 14.1 14.1h403c3.7 0 7.3-1.5 10-4.1l76.8-76.8c2.6-2.6 4.1-6.2 4.1-10v-403C508 6.3 501.7 0 493.9 0zm-62.7 459.8v-28.6h28.6l-28.6 28.6zm48.6-56.8h-62.7c-7.8 0-14.1 6.3-14.1 14.1v62.7H28.2V28.2h451.6V403z"/>
|
||||||
|
<path d="M383.4 108.6c-5.8 0-10.6-4.7-10.6-10.6 0-5.8 4.7-10.6 10.6-10.6 2.9 0 5.7 1.2 7.7 3.3 2.7 2.8 7.1 3 10 .3 2.8-2.7 3-7.1.3-10-4.6-4.9-11.2-7.8-18-7.8-13.6 0-24.7 11.1-24.7 24.7s11.1 24.7 24.7 24.7c5.8 0 10.6 4.7 10.6 10.6 0 5.8-4.7 10.6-10.6 10.6-2.9 0-5.7-1.2-7.7-3.3-2.7-2.8-7.1-3-10-.3-2.8 2.7-3 7.1-.3 10 4.6 4.9 11.2 7.8 18 7.8 13.6 0 24.7-11.1 24.7-24.7s-11.1-24.7-24.7-24.7zM221.5 123c3.9 0 7.1-3.2 7.1-7.1 0-3.9-3.2-7.1-7.1-7.1h-20.8V87.9h20.8c3.9 0 7.1-3.2 7.1-7.1 0-3.9-3.2-7.1-7.1-7.1h-27.8c-3.9 0-7.1 3.2-7.1 7.1v70.5c.1 3.9 3.2 7.1 7.1 7.1h27.8c3.9 0 7.1-3.2 7.1-7.1s-3.2-7.1-7.1-7.1h-20.8V123h20.8zM149.4 73.7c-3.9 0-7.1 3.2-7.1 7.1v45L113 77.2c-1.6-2.7-4.9-4-7.9-3.2-3.1.8-5.2 3.6-5.2 6.8v70.5c0 3.9 3.2 7.1 7.1 7.1s7.1-3.2 7.1-7.1v-45l29.2 48.7c1.3 2.2 3.6 3.4 6.1 3.4.6 0 1.3-.1 1.9-.3 3.1-.8 5.2-3.6 5.2-6.8V80.8c0-3.9-3.2-7.1-7.1-7.1zM335.1 74.1c-3.8-1-7.6 1.2-8.6 5l-12.1 45S302.1 78.7 302 78.5c-.8-2.1-2.4-3.8-4.7-4.4-3.8-1-7.6 1.2-8.6 5l-12.1 45-12-45c-1-3.8-4.9-6-8.6-5-3.8 1-6 4.9-5 8.6l18.9 70.5c.8 3.1 3.6 5.2 6.8 5.2s6-2.1 6.8-5.2l12-45 12.1 45c.8 3.1 3.6 5.2 6.8 5.2 3.2 0 6-2.1 6.8-5.2l18.9-70.5c1-3.8-1.2-7.6-5-8.6z"/>
|
||||||
|
</svg>
|
||||||
|
<div>Aktualności</div>
|
||||||
|
</ow-path>
|
||||||
|
|
||||||
|
<ow-path path="/admin/businesses" selected="{{ page.select_admin_businesses() }}"
|
||||||
|
title="Zarządzaj usługami">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.999 511.999" xml:space="preserve">
|
||||||
|
<path d="M440.482 50.916c-8.099-4.226-18.091-1.088-22.321 7.013l-41.925 80.336-46.147 36.662h-54.683l-57.033-18.756-33.145-53.595 5.854-5.694c3.554-3.457 3.634-9.143.176-12.697l-13.735-14.12 10.294-10.014c4.024-3.914 4.113-10.35.198-14.374l-41.438-42.6c-3.914-4.024-10.35-4.113-14.374-.198l-66.5 64.687c-4.024 3.914-4.113 10.35-.198 14.374l41.438 42.599c3.914 4.024 10.35 4.113 14.374.198l10.294-10.013 13.737 14.121c3.449 3.547 9.136 3.64 12.696.176l2.896-2.817 32.466 52.499a16.544 16.544 0 0 0 8.901 7.014l58.436 19.218.008 287.213c0 10.964 8.888 19.853 19.852 19.853s19.852-8.888 19.852-19.853V333.466l8.565-2.239 24.462 64.136-17.398 78.459c-2.373 10.704 4.38 21.306 15.084 23.679 10.69 2.373 21.303-4.371 23.679-15.083l18.771-84.647a19.858 19.858 0 0 0-1.096-12.026l-24.153-57.154V202.66l51.197-40.674a16.547 16.547 0 0 0 4.375-5.299l43.551-83.45c4.23-8.1 1.089-18.094-7.01-22.321zM171.921 90.467a16.48 16.48 0 0 0-12.357 2.058c-6.031 3.73-8.81 10.704-7.514 17.272l-.092.088-7.475-7.683 20.167-19.617 7.475 7.684-.204.198z"/>
|
||||||
|
<circle cx="304.734" cy="129.707" r="34.286"/>
|
||||||
|
</svg>
|
||||||
|
<div>Lokalne Usługi</div>
|
||||||
|
</ow-path>
|
||||||
|
|
||||||
|
<ow-path path="/admin/offers" selected="{{ page.select_admin_offers() }}"
|
||||||
|
title="Zarządzaj ofertami">
|
||||||
|
<svg viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M30.412 19.045a.34.34 0 0 0-.34.341V30.83c0 .12-.102.222-.222.222h-2.627v-3.14h.87a.402.402 0 0 0 .372-.22.402.402 0 0 0-.054-.43l-1.818-2.346a.436.436 0 0 0-.346-.173h-.003a.437.437 0 0 0-.347.179l-1.758 2.343a.403.403 0 0 0-.049.429.403.403 0 0 0 .372.218h.806v3.14h-8.941a.594.594 0 0 1-.594-.594V19.716c0-.327.266-.594.594-.594H27.7a.34.34 0 1 0 0-.681H16.327c-.703 0-1.275.572-1.275 1.275v10.742c0 .703.572 1.275 1.275 1.275h9.282a.34.34 0 0 0 .34-.34V27.57a.34.34 0 0 0-.34-.34h-.592l1.233-1.645 1.274 1.645h-.642a.34.34 0 0 0-.34.34v3.821a.34.34 0 0 0 .34.34h2.968a.905.905 0 0 0 .903-.903V19.386a.34.34 0 0 0-.34-.34z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M17.52 15.715c-.638-.713-5.53-6.169-7.115-7.557 1.257-.794 2.022-2.14 2.022-3.61C12.427 2.168 10.42.23 7.952.23c-2.468 0-4.475 1.938-4.475 4.32 0 1.455.75 2.789 1.986 3.586-1.026.75-1.648 1.93-1.648 3.186v18.466c0 .971.819 1.761 1.825 1.761s1.824-.79 1.824-1.76V19.643h.938v10.143c0 .971.819 1.761 1.825 1.761s1.824-.79 1.824-1.76l.036-15.054 3.216 3.104c.304.294.704.44 1.104.44.4 0 .8-.146 1.104-.44a1.472 1.472 0 0 0 .008-2.123z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div>Zarządzań ofertami</div>
|
||||||
|
</ow-path>
|
||||||
|
{%- endif %}
|
||||||
|
</ow-nav>
|
@ -5,20 +5,40 @@
|
|||||||
|
|
||||||
<offer-form></offer-form>
|
<offer-form></offer-form>
|
||||||
{% for offer in offers %}
|
{% for offer in offers %}
|
||||||
|
<user-edit-offer
|
||||||
|
state="{{offer.state.as_str()}}"
|
||||||
|
offer-id="{{offer.id}}"
|
||||||
|
description="{{offer.description}}"
|
||||||
|
picture-url="{{offer.picture_url}}"
|
||||||
|
{% match offer.price_range %}
|
||||||
|
{% when PriceRange::Free %}
|
||||||
|
price-range-min="0"
|
||||||
|
price-range-max="0"
|
||||||
|
{% when PriceRange::Fixed with { value } %}
|
||||||
|
price-range-min="{{value}}"
|
||||||
|
price-range-max="0"
|
||||||
|
{% when PriceRange::Range with { min, max } %}
|
||||||
|
price-range-min="{{min}}"
|
||||||
|
price-range-max="{{max}}"
|
||||||
|
{% endmatch %}
|
||||||
|
>
|
||||||
<marketplace-offer
|
<marketplace-offer
|
||||||
offer-id="{{offer.id}}"
|
offer-id="{{offer.id}}"
|
||||||
description="{{offer.description}}"
|
description="{{offer.description}}"
|
||||||
picture-url="{{offer.picture_url}}"
|
picture-url="{{offer.picture_url}}"
|
||||||
{% match offer.price_range %}
|
{% match offer.price_range %}
|
||||||
{% when PriceRange::Free %}
|
{% when PriceRange::Free %}
|
||||||
price-range="free"
|
price-range-min="0"
|
||||||
|
price-range-max="0"
|
||||||
{% when PriceRange::Fixed with { value } %}
|
{% when PriceRange::Fixed with { value } %}
|
||||||
price-range="{{value}}"
|
price-range-min="{{value}}"
|
||||||
|
price-range-max="0"
|
||||||
{% when PriceRange::Range with { min, max } %}
|
{% when PriceRange::Range with { min, max } %}
|
||||||
price-range-min="{{min}}"
|
price-range-min="{{min}}"
|
||||||
price-range-max="{{max}}"
|
price-range-max="{{max}}"
|
||||||
{% endmatch %}
|
{% endmatch %}
|
||||||
></marketplace-offer>
|
></marketplace-offer>
|
||||||
|
</user-edit-offer>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</marketplace-offers>
|
</marketplace-offers>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import "./admin/ow-admin";
|
import "./admin/ow-admin";
|
||||||
|
|
||||||
import "./admin/article-form";
|
import "./admin/news/article-form";
|
||||||
import "./admin/edit-news-article";
|
import "./admin/news/edit-news-article";
|
||||||
|
|
||||||
import "./admin/admin-business";
|
import "./admin/businesses/admin-business";
|
||||||
import "./admin/admin-businesses";
|
import "./admin/businesses/admin-businesses";
|
||||||
import "./admin/admin-edit-business";
|
import "./admin/businesses/admin-edit-business";
|
||||||
|
|
||||||
|
import "./admin/offers/admin-edit-offer";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component } from "../shared";
|
import { Component } from "../../shared";
|
||||||
|
|
||||||
customElements.define('admin-business', class extends Component {
|
customElements.define('admin-business', class extends Component {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
@ -1,4 +1,4 @@
|
|||||||
import { Component } from "../shared";
|
import { Component } from "../../shared";
|
||||||
|
|
||||||
customElements.define('admin-businesses', class extends Component {
|
customElements.define('admin-businesses', class extends Component {
|
||||||
constructor() {
|
constructor() {
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, BUTTON_STYLE, INPUT_STYLE } from "../shared";
|
import { Component, BUTTON_STYLE, INPUT_STYLE } from "../../shared";
|
||||||
|
|
||||||
customElements.define('admin-edit-business', class extends Component {
|
customElements.define('admin-edit-business', class extends Component {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, FORM_STYLE } from "../shared";
|
import { Component, FORM_STYLE } from "../../shared";
|
||||||
|
|
||||||
customElements.define('article-form', class extends Component {
|
customElements.define('article-form', class extends Component {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, BUTTON_STYLE } from "../shared";
|
import { Component, BUTTON_STYLE } from "../../shared";
|
||||||
|
|
||||||
customElements.define('edit-news-article', class extends Component {
|
customElements.define('edit-news-article', class extends Component {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
109
client/src/admin/offers/admin-edit-offer.js
Normal file
109
client/src/admin/offers/admin-edit-offer.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { Component, BUTTON_STYLE } from "../../shared";
|
||||||
|
|
||||||
|
customElements.define('admin-edit-offer', class extends Component {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['offer-id', 'state', 'description', 'picture-url', 'price-range-min', 'price-range-max'];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#view { display: block; }
|
||||||
|
|
||||||
|
#actions > input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#state {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
:host([state="approved"]) #state { color: green; }
|
||||||
|
:host([state="banned"]) #state { color: darkred; }
|
||||||
|
:host([state="pending"]) #state { color: orange; }
|
||||||
|
|
||||||
|
:host([state="approved"]) #approve { display: none; }
|
||||||
|
:host([state="banned"]) #ban { display: none; }
|
||||||
|
|
||||||
|
@media only screen and (min-device-width: 1200px) {
|
||||||
|
#view { display: flex; }
|
||||||
|
#actions {
|
||||||
|
margin-left: 16px;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${BUTTON_STYLE}
|
||||||
|
</style>
|
||||||
|
<section id="view">
|
||||||
|
<slot></slot>
|
||||||
|
<div id="actions">
|
||||||
|
<div id="state"></div>
|
||||||
|
<form action="/admin/offers/approve" method="post">
|
||||||
|
<input type="hidden" name="id" class="offer_id" />
|
||||||
|
<input type="submit" id="approve" value="Zaakceptuj" />
|
||||||
|
</form>
|
||||||
|
<form action="/admin/offers/ban" method="post">
|
||||||
|
<input type="hidden" name="id" class="offer_id" />
|
||||||
|
<input type="submit" id="ban" value="Zbanuj" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
get offer_id() {
|
||||||
|
const v = parseInt(this.getAttribute('offer-id'));
|
||||||
|
return isNaN(v) ? null : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
set offer_id(v) {
|
||||||
|
v = parseInt(v);
|
||||||
|
this.setAttribute('offer-id', v);
|
||||||
|
for (const el of this.shadowRoot.querySelectorAll('.offer_id')) {
|
||||||
|
el.value = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return this.getAttribute('description');
|
||||||
|
}
|
||||||
|
|
||||||
|
set description(v) {
|
||||||
|
this.setAttribute('description', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
get picture_url() {
|
||||||
|
return this.getAttribute('picture-url');
|
||||||
|
}
|
||||||
|
|
||||||
|
set picture_url(v) {
|
||||||
|
this.setAttribute('picture-url', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
get price_range_min() {
|
||||||
|
this.getAttribute('price-range-min');
|
||||||
|
}
|
||||||
|
|
||||||
|
set price_range_min(v) {
|
||||||
|
this.setAttribute('price-range-min', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
get price_range_max() {
|
||||||
|
this.getAttribute('price-range-max');
|
||||||
|
}
|
||||||
|
|
||||||
|
set price_range_max(v) {
|
||||||
|
this.setAttribute('price-range-max', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
get state() {
|
||||||
|
return this.getAttribute('state');
|
||||||
|
}
|
||||||
|
|
||||||
|
set state(v) {
|
||||||
|
this.setAttribute('state', v);
|
||||||
|
this.shadowRoot.querySelector('#state').textContent = v;
|
||||||
|
}
|
||||||
|
});
|
@ -1,15 +1,17 @@
|
|||||||
|
import "./shared/rich-text-editor";
|
||||||
|
import "./shared/form-navigation.js";
|
||||||
|
import "./shared/image-popup.js";
|
||||||
|
import "./shared/nav/ow-nav.js";
|
||||||
|
import "./shared/nav/ow-path.js";
|
||||||
|
import "./shared/price/price-input";
|
||||||
|
import "./shared/price/price-view";
|
||||||
|
|
||||||
import "./ow-account/ow-account.js";
|
import "./ow-account/ow-account.js";
|
||||||
|
|
||||||
import "./local-businesses/local-businesses.js";
|
import "./local-businesses/local-businesses.js";
|
||||||
import "./local-businesses/local-business-item";
|
import "./local-businesses/local-business-item";
|
||||||
import "./local-businesses/local-business";
|
import "./local-businesses/local-business";
|
||||||
|
|
||||||
import "./shared/nav/ow-nav.js";
|
|
||||||
import "./shared/nav/ow-path.js";
|
|
||||||
|
|
||||||
import "./shared/price/price-view";
|
|
||||||
import "./shared/price/price-input";
|
|
||||||
|
|
||||||
import "./login-form.js";
|
import "./login-form.js";
|
||||||
import "./register-form.js";
|
import "./register-form.js";
|
||||||
|
|
||||||
@ -19,9 +21,6 @@ import "./business-items/business-item-editor";
|
|||||||
import "./news/ow-articles";
|
import "./news/ow-articles";
|
||||||
import "./news/news-article";
|
import "./news/news-article";
|
||||||
|
|
||||||
import "./shared/rich-text-editor";
|
|
||||||
import "./shared/form-navigation.js";
|
|
||||||
|
|
||||||
import "./contacts/contact-info-list";
|
import "./contacts/contact-info-list";
|
||||||
import "./contacts/contact-info";
|
import "./contacts/contact-info";
|
||||||
import "./contacts/contact-info-editor";
|
import "./contacts/contact-info-editor";
|
||||||
@ -31,6 +30,7 @@ import "./contacts/edit-contact-info";
|
|||||||
import "./offers/marketplace-offer";
|
import "./offers/marketplace-offer";
|
||||||
import "./offers/marketplace-offers";
|
import "./offers/marketplace-offers";
|
||||||
import "./offers/offer-form";
|
import "./offers/offer-form";
|
||||||
|
import "./offers/user-edit-offer";
|
||||||
|
|
||||||
import "./terms_and_conditions/terms-and-conditions";
|
import "./terms_and_conditions/terms-and-conditions";
|
||||||
|
|
||||||
|
@ -12,51 +12,77 @@ customElements.define('marketplace-offer', class extends Component {
|
|||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 16px;
|
||||||
}
|
border-bottom: 1px solid var(--border-light-gray-color);
|
||||||
img[src=""] { display: none; }
|
|
||||||
img {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
#preview {
|
#preview {
|
||||||
max-width: 50%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
image-popup {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
#sep {
|
||||||
|
display: block;
|
||||||
|
grid-area: sep;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
:host([price-range-max="0"]) #sep {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
@media only screen and (min-device-width: 1200px) {
|
@media only screen and (min-device-width: 1200px) {
|
||||||
section {
|
section {
|
||||||
display: grid;
|
display: grid;
|
||||||
column-gap: 8px;
|
column-gap: 16px;
|
||||||
grid-template-areas: "img desc desc desc desc"
|
grid-template-areas: "img desc desc desc desc desc"
|
||||||
"img desc desc desc desc"
|
"img _ _ min sep max";
|
||||||
"img _ _ min max";
|
grid-template-columns: 400px auto auto 100px 10px 100px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
image-popup {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
}
|
}
|
||||||
#preview {
|
#preview {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
grid-area: img;
|
grid-area: img;
|
||||||
|
align-self: center;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
#description {
|
#description {
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
grid-area: desc;
|
grid-area: desc;
|
||||||
justify-self: stretch;
|
justify-self: stretch;
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
#price-min {
|
#price-min {
|
||||||
grid-area: min;
|
grid-area: min;
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
#price-max {
|
#price-max {
|
||||||
grid-area: max;
|
grid-area: max;
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
|
width: 100px;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${ INPUT_STYLE }
|
${ INPUT_STYLE }
|
||||||
</style>
|
</style>
|
||||||
<section>
|
<section>
|
||||||
<div id="preview">
|
<div id="preview">
|
||||||
<img alt="" src="" id="picture" />
|
<image-popup src="" id="picture">
|
||||||
|
</image-popup>
|
||||||
</div>
|
</div>
|
||||||
<p id="description"></p>
|
<p id="description"></p>
|
||||||
<p id="price-min"></p>
|
<span id="price-min"></span>
|
||||||
<p id="price-max"></p>
|
<span id="sep">-</span>
|
||||||
|
<span id="price-max"></span>
|
||||||
</section>
|
</section>
|
||||||
`);
|
`);
|
||||||
this.#price_range = new PriceRange(0, 0);
|
this.#price_range = new PriceRange(0, 0);
|
||||||
@ -103,7 +129,7 @@ customElements.define('marketplace-offer', class extends Component {
|
|||||||
if (v === 'free')
|
if (v === 'free')
|
||||||
this.#price_range = new PriceRange(v, 0);
|
this.#price_range = new PriceRange(v, 0);
|
||||||
else if (v.includes(',')) {
|
else if (v.includes(',')) {
|
||||||
const [min, max, ...r] = v.split(',');
|
const [min, max, ..._] = v.split(',');
|
||||||
this.#price_range = new PriceRange(parseInt(min), parseInt(max));
|
this.#price_range = new PriceRange(parseInt(min), parseInt(max));
|
||||||
} else {
|
} else {
|
||||||
this.#price_range.min = parseInt(v);
|
this.#price_range.min = parseInt(v);
|
||||||
@ -125,7 +151,6 @@ customElements.define('marketplace-offer', class extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set price_range_max(v) {
|
set price_range_max(v) {
|
||||||
console.info('max', v)
|
|
||||||
this.#price_range.max = v;
|
this.#price_range.max = v;
|
||||||
this.#displayPrice();
|
this.#displayPrice();
|
||||||
}
|
}
|
||||||
@ -133,6 +158,7 @@ customElements.define('marketplace-offer', class extends Component {
|
|||||||
#displayPrice() {
|
#displayPrice() {
|
||||||
const min = this.shadowRoot.querySelector('#price-min');
|
const min = this.shadowRoot.querySelector('#price-min');
|
||||||
const max = this.shadowRoot.querySelector('#price-max');
|
const max = this.shadowRoot.querySelector('#price-max');
|
||||||
|
|
||||||
if (this.#price_range.isFree) {
|
if (this.#price_range.isFree) {
|
||||||
min.innerHTML = ``;
|
min.innerHTML = ``;
|
||||||
max.innerHTML = `Za darmo`;
|
max.innerHTML = `Za darmo`;
|
||||||
|
@ -2,25 +2,26 @@ import { Component, FORM_STYLE } from "../shared";
|
|||||||
|
|
||||||
customElements.define('offer-form', class extends Component {
|
customElements.define('offer-form', class extends Component {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ['offer-id', 'description', 'picture-url']
|
return ['offer-id', 'description', 'picture-url', 'price-range-min', 'price-range-max'];
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(`
|
super(`
|
||||||
<style>
|
<style>
|
||||||
:host { display: block; }
|
:host { display: block; }
|
||||||
|
.tip {
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--border-slim-color)
|
||||||
|
}
|
||||||
|
@media only screen and (min-device-width: 1200px) {
|
||||||
section > form {
|
section > form {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
#imageSection {
|
#imageSection {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
#descriptionSection {
|
|
||||||
width: calc(100% - 230px);
|
|
||||||
}
|
|
||||||
@media only screen and (min-device-width: 1200px) {
|
|
||||||
#priceSection {
|
#priceSection {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
@ -35,27 +36,34 @@ customElements.define('offer-form', class extends Component {
|
|||||||
<section>
|
<section>
|
||||||
<form action="/offers/create" method="post">
|
<form action="/offers/create" method="post">
|
||||||
<div id="imageSection">
|
<div id="imageSection">
|
||||||
<image-input send-original="true"></image-input>
|
<image-input send-original="true" width="800" height="800"
|
||||||
|
></image-input>
|
||||||
<input name="picture_url" id="picture_url" type="hidden" />
|
<input name="picture_url" id="picture_url" type="hidden" />
|
||||||
</div>
|
</div>
|
||||||
<div id="descriptionSection">
|
<div id="descriptionSection">
|
||||||
<label for="description">Opis</label>
|
<label for="description">Opis</label>
|
||||||
<input name="description" id="description" type="text" />
|
<textarea name="description" id="description">
|
||||||
|
</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div id="priceSection">
|
<div id="priceSection">
|
||||||
<div>
|
<div>
|
||||||
<label>Cena minimalna</label>
|
<label>Cena minimalna</label>
|
||||||
<price-input id="priceMinUI" value="0"></price-input>
|
<price-input id="priceMinUI" value="0"></price-input>
|
||||||
<input name="price_min" id="priceMin" type="hidden" />
|
<input name="price_min" id="priceMin" type="hidden" value="0" />
|
||||||
|
<span class="tip">Jeżeli cena minimalna i maksymalna wynoszą 0 produkt będzie dostępny za darmo</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>Cena maksymalna</label>
|
<label>Cena maksymalna</label>
|
||||||
<price-input id="priceMaxUI" value="0"></price-input>
|
<price-input id="priceMaxUI" value="0"></price-input>
|
||||||
<input name="price_max" id="priceMax" type="hidden" />
|
<input name="price_max" id="priceMax" type="hidden" value="0" />
|
||||||
|
<span class="tip">Pozostaw 0, żeby cena minimalna była jedyną dostępną ceną</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input id="submit" type="submit" value="Utwórz" />
|
<input id="submit" type="submit" value="Utwórz" />
|
||||||
|
<div>
|
||||||
|
<slot name="action"></slot>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
@ -90,6 +98,26 @@ customElements.define('offer-form', class extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get price_range_min() {
|
||||||
|
this.getAttribute('price-range-min');
|
||||||
|
}
|
||||||
|
|
||||||
|
set price_range_min(v) {
|
||||||
|
this.setAttribute('price-range-min', v);
|
||||||
|
this.shadowRoot.querySelector('#priceMinUI').value = v;
|
||||||
|
this.shadowRoot.querySelector('#priceMin').value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
get price_range_max() {
|
||||||
|
this.getAttribute('price-range-max');
|
||||||
|
}
|
||||||
|
|
||||||
|
set price_range_max(v) {
|
||||||
|
this.setAttribute('price-range-max', v);
|
||||||
|
this.shadowRoot.querySelector('#priceMaxUI').value = v;
|
||||||
|
this.shadowRoot.querySelector('#priceMax').value = v;
|
||||||
|
}
|
||||||
|
|
||||||
get description() {
|
get description() {
|
||||||
return this.getAttribute('description');
|
return this.getAttribute('description');
|
||||||
}
|
}
|
||||||
|
114
client/src/offers/user-edit-offer.js
Normal file
114
client/src/offers/user-edit-offer.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { Component, BUTTON_STYLE } from "../shared";
|
||||||
|
|
||||||
|
const MODES = { 'view': 'view', 'form': 'form' };
|
||||||
|
|
||||||
|
customElements.define('user-edit-offer', class extends Component {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['mode', 'offer-id', 'description', 'picture-url', 'price-range-min', 'price-range-max'];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#view, #form {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:host([mode='view']) #view { display: block; }
|
||||||
|
:host([mode='form']) #form { display: block; }
|
||||||
|
|
||||||
|
#actions > input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@media only screen and (min-device-width: 1200px) {
|
||||||
|
:host([mode='view']) #view { display: flex; }
|
||||||
|
#actions {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${BUTTON_STYLE}
|
||||||
|
</style>
|
||||||
|
<section id="view">
|
||||||
|
<slot></slot>
|
||||||
|
<div id="actions">
|
||||||
|
<input type="button" value="Edytuj" id="edit" />
|
||||||
|
<input type="button" slot="action" id="finish" value="Zakończ" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="form">
|
||||||
|
<offer-form>
|
||||||
|
<input type="button" slot="action" id="cancel" value="Anuluj" />
|
||||||
|
</offer-form>
|
||||||
|
</section>
|
||||||
|
`);
|
||||||
|
|
||||||
|
this.shadowRoot.querySelector('#edit').addEventListener('click', ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
this.mode = 'form';
|
||||||
|
});
|
||||||
|
this.shadowRoot.querySelector('#cancel').addEventListener('click', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.mode = 'view';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get mode() {
|
||||||
|
const mode = this.getAttribute('mode');
|
||||||
|
return MODES[mode] || 'view';
|
||||||
|
}
|
||||||
|
|
||||||
|
set mode(mode) {
|
||||||
|
this.setAttribute('mode', MODES[mode] || 'view');
|
||||||
|
}
|
||||||
|
|
||||||
|
get offer_id() {
|
||||||
|
const v = parseInt(this.getAttribute('offer-id'));
|
||||||
|
return isNaN(v) ? null : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
set offer_id(v) {
|
||||||
|
v = parseInt(v);
|
||||||
|
this.setAttribute('offer-id', v);
|
||||||
|
this.shadowRoot.querySelector('offer-form').offer_id = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return this.getAttribute('description');
|
||||||
|
}
|
||||||
|
|
||||||
|
set description(v) {
|
||||||
|
this.setAttribute('description', v);
|
||||||
|
this.shadowRoot.querySelector('offer-form').description = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
get picture_url() {
|
||||||
|
return this.getAttribute('picture-url');
|
||||||
|
}
|
||||||
|
|
||||||
|
set picture_url(v) {
|
||||||
|
this.setAttribute('picture-url', v);
|
||||||
|
this.shadowRoot.querySelector('offer-form').picture_url = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
get price_range_min() {
|
||||||
|
this.getAttribute('price-range-min');
|
||||||
|
}
|
||||||
|
|
||||||
|
set price_range_min(v) {
|
||||||
|
this.setAttribute('price-range-min', v);
|
||||||
|
this.shadowRoot.querySelector('offer-form').price_range_min = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
get price_range_max() {
|
||||||
|
this.getAttribute('price-range-max');
|
||||||
|
}
|
||||||
|
|
||||||
|
set price_range_max(v) {
|
||||||
|
this.setAttribute('price-range-max', v);
|
||||||
|
this.shadowRoot.querySelector('offer-form').price_range_max = v;
|
||||||
|
}
|
||||||
|
});
|
@ -141,6 +141,9 @@ export class Component extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
}
|
||||||
|
|
||||||
attributeChangedCallback(name, oldV, newV) {
|
attributeChangedCallback(name, oldV, newV) {
|
||||||
if (oldV === newV)
|
if (oldV === newV)
|
||||||
return;
|
return;
|
||||||
@ -190,8 +193,8 @@ export class PriceRange {
|
|||||||
#max;
|
#max;
|
||||||
|
|
||||||
constructor(min, max) {
|
constructor(min, max) {
|
||||||
this.#min = min || 0;
|
this.min = min || 0;
|
||||||
this.#max = max || 0;
|
this.max = max || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFree() {
|
get isFree() {
|
||||||
@ -211,6 +214,8 @@ export class PriceRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set min(v) {
|
set min(v) {
|
||||||
|
v = parseInt(v);
|
||||||
|
if (isNaN(v)) v = 0;
|
||||||
this.#min = v;
|
this.#min = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +224,8 @@ export class PriceRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set max(v) {
|
set max(v) {
|
||||||
|
v = parseInt(v);
|
||||||
|
if (isNaN(v)) v = 0;
|
||||||
this.#max = v;
|
this.#max = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,27 +66,35 @@ customElements.define('image-input', class extends Component {
|
|||||||
|
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
if (this.send_original) {
|
let maxWidth = this.width;
|
||||||
image.onload = () => {
|
let maxHeight = this.height;
|
||||||
canvas.width = image.naturalWidth;
|
|
||||||
canvas.height = image.naturalHeight;
|
|
||||||
canvas.getContext('2d').drawImage(image, 0, 0);
|
|
||||||
toFile(canvas);
|
|
||||||
};
|
|
||||||
|
|
||||||
image.src = URL.createObjectURL(input.files[0]);
|
|
||||||
} else {
|
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
const width = image.width > image.height ? 200 : (image.width * 200) / image.height;
|
if (this.send_original) {
|
||||||
const height = image.width > image.height ? (image.width * 200) / image.height : 200;
|
maxWidth = maxWidth < image.naturalWidth ? maxWidth : image.naturalWidth;
|
||||||
|
maxHeight = maxHeight < image.naturalHeight ? maxHeight : image.naturalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = image.width > image.height
|
||||||
|
? maxWidth
|
||||||
|
: (image.width * maxHeight) / image.height;
|
||||||
|
const height = image.width > image.height
|
||||||
|
? (image.height * maxWidth) / image.width
|
||||||
|
: maxHeight;
|
||||||
|
|
||||||
canvas.width = image.width = width;
|
canvas.width = image.width = width;
|
||||||
canvas.height = image.height = height;
|
canvas.height = image.height = height;
|
||||||
|
if (this.send_original) {
|
||||||
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
|
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
|
||||||
|
} else {
|
||||||
|
canvas.getContext('2d').drawImage(image, 0, 0);
|
||||||
|
}
|
||||||
toFile(canvas);
|
toFile(canvas);
|
||||||
|
image.width = width > image ? 200 : (width * 200) / height;
|
||||||
|
image.height = width > image ? (width * 200) / height : 200;
|
||||||
};
|
};
|
||||||
|
|
||||||
image.src = URL.createObjectURL(input.files[0]);
|
image.src = URL.createObjectURL(input.files[0]);
|
||||||
}
|
|
||||||
view.innerHTML = '';
|
view.innerHTML = '';
|
||||||
view.appendChild(image);
|
view.appendChild(image);
|
||||||
});
|
});
|
||||||
|
120
client/src/shared/image-popup.js
Normal file
120
client/src/shared/image-popup.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { Component } from "../shared";
|
||||||
|
|
||||||
|
customElements.define('image-popup', class extends Component {
|
||||||
|
#listener;
|
||||||
|
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['src', 'popup'];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#small {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#large {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#small > img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
img[src=''] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:host([popup="true"]) #large {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
#background {
|
||||||
|
background: rgba(120, 120, 120, .6);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
#large > #image {
|
||||||
|
margin: 16px;
|
||||||
|
height: calc(100% - 32px);
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#large > #image > img {
|
||||||
|
margin: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="small">
|
||||||
|
<img alt="" src="" />
|
||||||
|
</div>
|
||||||
|
<div id="large">
|
||||||
|
<div id="background"></div>
|
||||||
|
<div id="image">
|
||||||
|
<img src="" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
this.shadowRoot.querySelector('#small').addEventListener('click', ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
this.popup = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.shadowRoot.querySelector('#large').addEventListener('click', ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
this.popup = false;
|
||||||
|
});
|
||||||
|
this.#listener = ({ key }) => {
|
||||||
|
if (key !== 'Escape') return;
|
||||||
|
if (!this.popup) return;
|
||||||
|
this.popup = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
window.addEventListener('keydown', this.#listener, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
window.removeEventListener('keydown', this.#listener, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
get popup() {
|
||||||
|
return this.getAttribute('popup') === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
set popup(v) {
|
||||||
|
if (v === true || v === 'true')
|
||||||
|
this.setAttribute('popup', 'true');
|
||||||
|
else
|
||||||
|
this.removeAttribute('popup');
|
||||||
|
}
|
||||||
|
|
||||||
|
get src() {
|
||||||
|
return this.getAttribute('src');
|
||||||
|
}
|
||||||
|
|
||||||
|
set src(v) {
|
||||||
|
this.setAttribute('src', v);
|
||||||
|
this.shadowRoot.querySelector('#small img').src = v;
|
||||||
|
this.shadowRoot.querySelector('#large img').src = v;
|
||||||
|
}
|
||||||
|
});
|
@ -37,6 +37,16 @@ pub enum OfferState {
|
|||||||
Banned,
|
Banned,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OfferState {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
OfferState::Pending => "pending",
|
||||||
|
OfferState::Approved => "approved",
|
||||||
|
OfferState::Banned => "banned",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
@ -209,6 +219,7 @@ impl<'l> sqlx::Decode<'l, Postgres> for PriceRange {
|
|||||||
}
|
}
|
||||||
PgValueFormat::Binary => {
|
PgValueFormat::Binary => {
|
||||||
let mut bytes = value.as_bytes()?;
|
let mut bytes = value.as_bytes()?;
|
||||||
|
// println!("{bytes:?}");
|
||||||
|
|
||||||
let _len = take_i32(&mut bytes);
|
let _len = take_i32(&mut bytes);
|
||||||
|
|
||||||
@ -228,23 +239,27 @@ impl<'l> sqlx::Decode<'l, Postgres> for PriceRange {
|
|||||||
|
|
||||||
impl<'l> sqlx::Encode<'l, Postgres> for PriceRange {
|
impl<'l> sqlx::Encode<'l, Postgres> for PriceRange {
|
||||||
fn encode_by_ref(&self, buf: &mut <Postgres as HasArguments<'l>>::ArgumentBuffer) -> IsNull {
|
fn encode_by_ref(&self, buf: &mut <Postgres as HasArguments<'l>>::ArgumentBuffer) -> IsNull {
|
||||||
|
let _ = 2i32.encode(buf);
|
||||||
|
fn write_value(n: &i32, buf: &mut <Postgres as HasArguments<'_>>::ArgumentBuffer) {
|
||||||
|
let _ = 23.encode(buf);
|
||||||
|
let _ = 4.encode(buf);
|
||||||
|
let _ = n.encode(buf);
|
||||||
|
}
|
||||||
match self {
|
match self {
|
||||||
PriceRange::Free => {
|
PriceRange::Free => {
|
||||||
let _ = 0.encode(buf);
|
write_value(&0, buf);
|
||||||
let _ = 0.encode(buf);
|
write_value(&0, buf);
|
||||||
true.encode(buf)
|
|
||||||
}
|
}
|
||||||
PriceRange::Fixed { value } => {
|
PriceRange::Fixed { value } => {
|
||||||
let _ = value.encode(buf);
|
write_value(value, buf);
|
||||||
let _ = 0.encode(buf);
|
write_value(&0, buf);
|
||||||
false.encode(buf)
|
|
||||||
}
|
}
|
||||||
PriceRange::Range { min, max } => {
|
PriceRange::Range { min, max } => {
|
||||||
let _ = min.encode(buf);
|
write_value(min, buf);
|
||||||
let _ = max.encode(buf);
|
write_value(max, buf);
|
||||||
false.encode(buf)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
IsNull::No
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ pub enum Page {
|
|||||||
AdminNews,
|
AdminNews,
|
||||||
AdminCreateNews,
|
AdminCreateNews,
|
||||||
AdminBusinesses,
|
AdminBusinesses,
|
||||||
|
AdminOffers,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
@ -26,7 +27,7 @@ impl Page {
|
|||||||
pub fn is_admin(&self) -> bool {
|
pub fn is_admin(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
Page::AdminNews | Page::AdminCreateNews | Page::AdminBusinesses
|
Page::AdminNews | Page::AdminCreateNews | Page::AdminBusinesses | Page::AdminOffers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +87,14 @@ impl Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_admin_offers(&self) -> &str {
|
||||||
|
if matches!(self, Page::AdminOffers) {
|
||||||
|
"selected"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn select_account_offers(&self) -> &str {
|
pub fn select_account_offers(&self) -> &str {
|
||||||
if matches!(self, Page::AccountOffers) {
|
if matches!(self, Page::AccountOffers) {
|
||||||
"selected"
|
"selected"
|
||||||
|
79
src/queries/accounts.rs
Normal file
79
src/queries/accounts.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::model::db;
|
||||||
|
use crate::queries::{Error, Result, T};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn account_by_id(t: &mut T<'_>, id: String) -> Option<db::Account> {
|
||||||
|
match sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT id, login, email, pass, facebook_id, account_type
|
||||||
|
FROM accounts
|
||||||
|
WHERE id = $1 :: INT
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_optional(t)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(e) => {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn account_by_email(t: &mut T<'_>, email: String) -> Result<db::Account> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT id, login, email, pass, facebook_id, account_type
|
||||||
|
FROM accounts
|
||||||
|
WHERE email = $1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(&email)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::AccountByEmail { email }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn create_account(t: &mut T<'_>, input: db::CreateAccountInput) -> Result<db::Account> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
INSERT INTO accounts (login, email, pass, facebook_id, account_type)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
RETURNING id, login, email, pass, facebook_id, account_type
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(&input.login)
|
||||||
|
.bind(&input.email)
|
||||||
|
.bind(&input.pass)
|
||||||
|
.bind(&input.facebook_id)
|
||||||
|
.bind(input.account_type)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
match e {
|
||||||
|
sqlx::Error::Database(e) => {
|
||||||
|
if e.message()
|
||||||
|
== "duplicate key value violates unique constraint \"accounts_email_key\""
|
||||||
|
{
|
||||||
|
Error::AccountTaken { input }
|
||||||
|
} else {
|
||||||
|
Error::CreateAccount { input }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Error::CreateAccount { input },
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
156
src/queries/businesses.rs
Normal file
156
src/queries/businesses.rs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::model::db;
|
||||||
|
use crate::queries::{Error, Result, T};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn set_business_state(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
id: i32,
|
||||||
|
state: db::LocalBusinessState,
|
||||||
|
) -> Result<db::LocalBusiness> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
UPDATE local_businesses
|
||||||
|
SET state = $2
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
state
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.bind(state)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
Error::BusinessItemState { id, state }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn create_local_business(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
name: String,
|
||||||
|
owner_id: i32,
|
||||||
|
description: String,
|
||||||
|
) -> Result<db::LocalBusiness> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
INSERT INTO local_businesses (name, owner_id, description)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING id, owner_id, name, description, state
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(&name)
|
||||||
|
.bind(owner_id)
|
||||||
|
.bind(&description)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::CreateLocalBusiness {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
owner_id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn all_businesses(t: &mut T<'_>) -> Result<Vec<db::LocalBusiness>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT id, owner_id, name, description, state
|
||||||
|
FROM local_businesses
|
||||||
|
GROUP BY id, state
|
||||||
|
ORDER BY id DESC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
Error::VisibleBusinesses
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn visible_businesses(t: &mut T<'_>) -> Result<Vec<db::LocalBusiness>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT id, owner_id, name, description, state
|
||||||
|
FROM local_businesses
|
||||||
|
WHERE state != 'Banned'
|
||||||
|
GROUP BY id, state
|
||||||
|
ORDER BY id DESC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
Error::VisibleBusinesses
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn account_business_by_owner_id(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
account_id: i32,
|
||||||
|
) -> Result<db::LocalBusiness> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT id, owner_id, name, description, state
|
||||||
|
FROM local_businesses
|
||||||
|
WHERE state != 'Banned' AND owner_id = $1
|
||||||
|
GROUP BY id, state
|
||||||
|
ORDER BY id DESC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(account_id)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::OwnedBusiness { account_id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn update_business(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
input: db::UpdateLocalBusinessInput,
|
||||||
|
) -> Result<db::LocalBusiness> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
UPDATE local_businesses
|
||||||
|
SET
|
||||||
|
name = $2,
|
||||||
|
description = $3
|
||||||
|
WHERE
|
||||||
|
id = $1
|
||||||
|
RETURNING
|
||||||
|
id, owner_id, name, description, state
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(input.id)
|
||||||
|
.bind(&input.name)
|
||||||
|
.bind(&input.description)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::UpdateBusiness { input }
|
||||||
|
})
|
||||||
|
}
|
139
src/queries/contacts.rs
Normal file
139
src/queries/contacts.rs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::model::db;
|
||||||
|
use crate::queries::{Error, Result, T};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn all_contacts(t: &mut T<'_>) -> Result<Vec<db::ContactInfo>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
contact_type,
|
||||||
|
content
|
||||||
|
FROM
|
||||||
|
contacts
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
Error::AllContacts
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn account_contacts(t: &mut T<'_>, account_id: i32) -> Result<Vec<db::ContactInfo>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
contact_type,
|
||||||
|
content
|
||||||
|
FROM
|
||||||
|
contacts
|
||||||
|
WHERE
|
||||||
|
owner_id = $1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(account_id)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
Error::AccountContacts { account_id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn create_contact(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
input: db::CreateContactInput,
|
||||||
|
) -> Result<Vec<db::ContactInfo>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
INSERT INTO contacts ( owner_id, contact_type, content )
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
contact_type,
|
||||||
|
content
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(input.owner_id)
|
||||||
|
.bind(&input.contact_type)
|
||||||
|
.bind(&input.content)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
Error::CreateContact { input }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn update_contact(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
input: db::UpdateContactInput,
|
||||||
|
) -> Result<Vec<db::ContactInfo>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
UPDATE contacts
|
||||||
|
SET
|
||||||
|
contact_type = $3,
|
||||||
|
content = $4
|
||||||
|
WHERE id = $1 AND owner_id = $2
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
contact_type,
|
||||||
|
content
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(input.id)
|
||||||
|
.bind(input.owner_id)
|
||||||
|
.bind(&input.contact_type)
|
||||||
|
.bind(&input.content)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
Error::UpdateContact { input }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn delete_contact(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
id: i32,
|
||||||
|
account_id: i32,
|
||||||
|
) -> Result<Vec<db::ContactInfo>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
DELETE FROM contacts
|
||||||
|
WHERE id = $1 AND owner_id = $2
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
contact_type,
|
||||||
|
content
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.bind(account_id)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
Error::DeleteContact { id }
|
||||||
|
})
|
||||||
|
}
|
374
src/queries/local_business_items.rs
Normal file
374
src/queries/local_business_items.rs
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::model::db;
|
||||||
|
use crate::queries::{Error, Result, T};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn visible_business_items(t: &mut T<'_>) -> Result<Vec<db::LocalBusinessItem>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
local_business_id,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
item_order,
|
||||||
|
picture_url
|
||||||
|
FROM local_business_items
|
||||||
|
ORDER BY item_order ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
Error::VisibleBusinessItems
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn all_business_items(t: &mut T<'_>) -> Result<Vec<db::LocalBusinessItem>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
local_business_id,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
item_order,
|
||||||
|
picture_url
|
||||||
|
FROM local_business_items
|
||||||
|
ORDER BY item_order ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
Error::VisibleBusinessItems
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn account_items(t: &mut T<'_>, account_id: i32) -> Vec<db::LocalBusinessItem> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
local_business_items.id,
|
||||||
|
local_business_items.local_business_id,
|
||||||
|
local_business_items.name,
|
||||||
|
local_business_items.price,
|
||||||
|
local_business_items.item_order,
|
||||||
|
local_business_items.picture_url
|
||||||
|
FROM local_business_items
|
||||||
|
INNER JOIN local_businesses
|
||||||
|
ON local_businesses.id = local_business_items.local_business_id
|
||||||
|
WHERE local_businesses.owner_id = $1
|
||||||
|
ORDER BY item_order ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(account_id)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::AllItems
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn item_by_id(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
account_id: i32,
|
||||||
|
item_id: i32,
|
||||||
|
) -> Result<db::LocalBusinessItem> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
local_business_items.id,
|
||||||
|
local_business_items.local_business_id,
|
||||||
|
local_business_items.name,
|
||||||
|
local_business_items.price,
|
||||||
|
local_business_items.item_order,
|
||||||
|
local_business_items.picture_url
|
||||||
|
FROM local_business_items
|
||||||
|
INNER JOIN local_businesses
|
||||||
|
ON local_businesses.id = local_business_items.local_business_id
|
||||||
|
WHERE local_business_items.id = $1 AND owner_id = $2
|
||||||
|
ORDER BY item_order ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(item_id)
|
||||||
|
.bind(account_id)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::Item { item_id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn move_item(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
account_id: i32,
|
||||||
|
item_id: i32,
|
||||||
|
item_order: i32,
|
||||||
|
) -> Result<db::LocalBusinessItem> {
|
||||||
|
let mut current = item_by_id(t, account_id, item_id).await?;
|
||||||
|
|
||||||
|
let all: Vec<db::LocalBusinessItem> = sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
local_business_items.id,
|
||||||
|
local_business_items.local_business_id,
|
||||||
|
local_business_items.name,
|
||||||
|
local_business_items.price,
|
||||||
|
local_business_items.item_order,
|
||||||
|
local_business_items.picture_url
|
||||||
|
FROM local_business_items
|
||||||
|
INNER JOIN local_businesses
|
||||||
|
ON local_businesses.id = local_business_items.local_business_id
|
||||||
|
WHERE local_businesses.owner_id = $1
|
||||||
|
ORDER BY item_order ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(account_id)
|
||||||
|
.fetch_all(&mut *t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::Item { item_id }
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let idx = all
|
||||||
|
.iter()
|
||||||
|
.position(|p| p.id == item_id)
|
||||||
|
.ok_or(Error::Item { item_id })?;
|
||||||
|
|
||||||
|
dbg!(idx);
|
||||||
|
|
||||||
|
match item_order.cmp(¤t.item_order) {
|
||||||
|
Ordering::Less => {
|
||||||
|
if let Some(prev) = idx.checked_sub(1).and_then(|prev_idx| {
|
||||||
|
dbg!(prev_idx);
|
||||||
|
all.get(prev_idx)
|
||||||
|
}) {
|
||||||
|
dbg!(
|
||||||
|
"Less and found",
|
||||||
|
current.id,
|
||||||
|
current.item_order,
|
||||||
|
prev.id,
|
||||||
|
prev.item_order,
|
||||||
|
);
|
||||||
|
dbg!(update_item_order(&mut *t, current.id, prev.item_order).await?);
|
||||||
|
dbg!(update_item_order(&mut *t, prev.id, current.item_order).await?);
|
||||||
|
} else {
|
||||||
|
dbg!("Less and not found, skipping...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ordering::Equal => {
|
||||||
|
dbg!("Equal, skipping...");
|
||||||
|
}
|
||||||
|
Ordering::Greater => {
|
||||||
|
if let Some(next) = idx.checked_add(1).and_then(|next_idx| {
|
||||||
|
dbg!(next_idx);
|
||||||
|
all.get(next_idx)
|
||||||
|
}) {
|
||||||
|
dbg!(
|
||||||
|
"Greater and found",
|
||||||
|
current.id,
|
||||||
|
current.item_order,
|
||||||
|
next.id,
|
||||||
|
next.item_order,
|
||||||
|
);
|
||||||
|
dbg!(update_item_order(&mut *t, current.id, next.item_order).await?);
|
||||||
|
dbg!(update_item_order(&mut *t, next.id, current.item_order).await?);
|
||||||
|
} else {
|
||||||
|
dbg!("Greater and not found, skipping...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
current.item_order = item_order;
|
||||||
|
Ok(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn update_item_order(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
id: i32,
|
||||||
|
item_order: i32,
|
||||||
|
) -> Result<db::LocalBusinessItem> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
UPDATE local_business_items
|
||||||
|
SET
|
||||||
|
item_order = $2
|
||||||
|
WHERE
|
||||||
|
id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
local_business_id,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
item_order,
|
||||||
|
picture_url
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.bind(item_order)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::UpdateItemOrder { id, item_order }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn update_item(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
input: db::UpdateLocalBusinessItemInput,
|
||||||
|
) -> Result<db::LocalBusinessItem> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
UPDATE local_business_items
|
||||||
|
SET
|
||||||
|
name = $3,
|
||||||
|
price = $4,
|
||||||
|
picture_url = $5,
|
||||||
|
item_order = $6
|
||||||
|
WHERE
|
||||||
|
local_business_id = $1 AND
|
||||||
|
id = $2
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
local_business_id,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
item_order,
|
||||||
|
picture_url
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(input.local_business_id)
|
||||||
|
.bind(input.id)
|
||||||
|
.bind(&input.name)
|
||||||
|
.bind(input.price)
|
||||||
|
.bind(if input.picture_url.is_empty() {
|
||||||
|
format!("--{}", uuid::Uuid::new_v4())
|
||||||
|
} else {
|
||||||
|
input.picture_url.clone()
|
||||||
|
})
|
||||||
|
.bind(input.item_order)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::UpdateItem { input }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn create_item(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
input: db::CreateLocalBusinessItemInput,
|
||||||
|
) -> Result<db::LocalBusinessItem> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
INSERT INTO local_business_items (local_business_id, name, price, picture_url, item_order)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
local_business_id,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
item_order,
|
||||||
|
picture_url
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(input.local_business_id)
|
||||||
|
.bind(&input.name)
|
||||||
|
.bind(input.price)
|
||||||
|
.bind(if input.picture_url.is_empty() {
|
||||||
|
format!("--{}", uuid::Uuid::new_v4())
|
||||||
|
} else {
|
||||||
|
input.picture_url.clone()
|
||||||
|
})
|
||||||
|
.bind(input.item_order)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::CreateItem { input }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn set_item_order(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
item_id: i32,
|
||||||
|
idx: i32,
|
||||||
|
) -> Result<db::LocalBusinessItem> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
UPDATE local_business_items
|
||||||
|
SET
|
||||||
|
item_order = $2
|
||||||
|
WHERE
|
||||||
|
id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
local_business_id,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
item_order,
|
||||||
|
picture_url
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(item_id)
|
||||||
|
.bind(idx as i32 + 1)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::SetOrder { item_id, idx }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn delete_item(t: &mut T<'_>, item_id: i32) -> Result<Option<db::LocalBusinessItem>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
DELETE FROM local_business_items
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
local_business_id,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
item_order,
|
||||||
|
picture_url
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(item_id)
|
||||||
|
.fetch_optional(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::DeleteItem { item_id }
|
||||||
|
})
|
||||||
|
}
|
1055
src/queries/mod.rs
1055
src/queries/mod.rs
File diff suppressed because it is too large
Load Diff
168
src/queries/news.rs
Normal file
168
src/queries/news.rs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::model::db;
|
||||||
|
use crate::model::db::NewsArticle;
|
||||||
|
use crate::queries::{Error, Result, T};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn all_news(t: &mut T<'_>) -> Result<Vec<NewsArticle>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
status,
|
||||||
|
published_at,
|
||||||
|
created_at
|
||||||
|
FROM
|
||||||
|
news
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::AllNews
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn news_article_by_id(t: &mut T<'_>, id: i32) -> Result<NewsArticle> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
status,
|
||||||
|
published_at,
|
||||||
|
created_at
|
||||||
|
FROM
|
||||||
|
news
|
||||||
|
WHERE id = $1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::NewsArticleById { id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn published_news(t: &mut T<'_>) -> Result<Vec<NewsArticle>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
status,
|
||||||
|
published_at,
|
||||||
|
created_at
|
||||||
|
FROM
|
||||||
|
news
|
||||||
|
WHERE
|
||||||
|
status = 'Published'
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::PublishedNews
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn create_news_article(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
input: db::CreateNewsArticleInput,
|
||||||
|
) -> Result<NewsArticle> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
INSERT INTO news (title, body, status)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
status,
|
||||||
|
published_at,
|
||||||
|
created_at
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(&input.title)
|
||||||
|
.bind(&input.body)
|
||||||
|
.bind(input.status)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::CreateNewsArticle { input }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn update_news_article(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
input: db::UpdateNewsArticleInput,
|
||||||
|
) -> Result<NewsArticle> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
UPDATE news
|
||||||
|
SET title = $2, body = $3, status = $4
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
status,
|
||||||
|
published_at,
|
||||||
|
created_at
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(input.id)
|
||||||
|
.bind(&input.title)
|
||||||
|
.bind(&input.body)
|
||||||
|
.bind(input.status)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::UpdateNewsArticle { input }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn delete_news_article(t: &mut T<'_>, id: i32) -> Result<Option<NewsArticle>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
DELETE FROM news
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
status,
|
||||||
|
published_at,
|
||||||
|
created_at
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_optional(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::DeleteNewsArticle { id }
|
||||||
|
})
|
||||||
|
}
|
178
src/queries/offers.rs
Normal file
178
src/queries/offers.rs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::model::db;
|
||||||
|
use crate::model::db::OfferState;
|
||||||
|
use crate::queries::{Error, Result, T};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn all_offers(t: &mut T<'_>) -> Result<Vec<db::Offer>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
price_range,
|
||||||
|
description,
|
||||||
|
picture_url,
|
||||||
|
state,
|
||||||
|
created_at
|
||||||
|
FROM offers
|
||||||
|
ORDER BY id ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::AllOffers
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn visible_offers(t: &mut T<'_>) -> Result<Vec<db::Offer>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
price_range,
|
||||||
|
description,
|
||||||
|
picture_url,
|
||||||
|
state,
|
||||||
|
created_at
|
||||||
|
FROM offers
|
||||||
|
WHERE created_at + '2 weeks' > now() AND state = 'Approved'
|
||||||
|
ORDER BY id ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::VisibleOffers
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn account_offers(t: &mut T<'_>, account_id: i32) -> Result<Vec<db::Offer>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
price_range,
|
||||||
|
description,
|
||||||
|
picture_url,
|
||||||
|
state,
|
||||||
|
created_at
|
||||||
|
FROM offers
|
||||||
|
WHERE owner_id = $1
|
||||||
|
ORDER BY id ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(account_id)
|
||||||
|
.fetch_all(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::AccountOffers
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn create_offer(t: &mut T<'_>, input: db::CreateOfferInput) -> Result<db::Offer> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
INSERT INTO offers (description, picture_url, state, search, owner_id, price_range)
|
||||||
|
VALUES ($1, $2, $3, to_tsvector('polish', $4), $5, $6)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
price_range,
|
||||||
|
description,
|
||||||
|
picture_url,
|
||||||
|
state,
|
||||||
|
created_at
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(&input.description)
|
||||||
|
.bind(&input.picture_url)
|
||||||
|
.bind(input.state)
|
||||||
|
.bind(&input.description)
|
||||||
|
.bind(input.owner_id)
|
||||||
|
.bind(input.price_range)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::CreateOffer { input }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn update_offer(t: &mut T<'_>, input: db::UpdateOfferInput) -> Result<db::Offer> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
UPDATE offers
|
||||||
|
SET description = $2,
|
||||||
|
picture_url = $3,
|
||||||
|
state = $4,
|
||||||
|
search = to_tsvector('polish', $5),
|
||||||
|
price_range = $6
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
price_range,
|
||||||
|
description,
|
||||||
|
picture_url,
|
||||||
|
state,
|
||||||
|
created_at
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(input.id)
|
||||||
|
.bind(&input.description)
|
||||||
|
.bind(&input.picture_url)
|
||||||
|
.bind(input.state)
|
||||||
|
.bind(&input.description)
|
||||||
|
.bind(&input.price_range)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::UpdateOffer { input }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn set_offer_state(t: &mut T<'_>, offer_id: i32, state: OfferState) -> Result<db::Offer> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
UPDATE offers
|
||||||
|
SET state = $2
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
price_range,
|
||||||
|
description,
|
||||||
|
picture_url,
|
||||||
|
state,
|
||||||
|
created_at
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(offer_id)
|
||||||
|
.bind(state)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::SetOfferState { offer_id, state }
|
||||||
|
})
|
||||||
|
}
|
@ -1,13 +1,9 @@
|
|||||||
use actix_web::web::{Data, Form, Path, ServiceConfig};
|
mod businesses;
|
||||||
use actix_web::{get, post, web, HttpResponse};
|
mod news;
|
||||||
use askama::*;
|
mod offers;
|
||||||
use sqlx::PgPool;
|
|
||||||
|
|
||||||
use crate::model::view::{Page, SetStateBusinessInput};
|
use actix_web::web;
|
||||||
use crate::model::{db, view};
|
use actix_web::web::ServiceConfig;
|
||||||
use crate::routes::{Identity, JsonResult, Result};
|
|
||||||
use crate::view::{filters, Helper};
|
|
||||||
use crate::{authorize, queries};
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! require_admin {
|
macro_rules! require_admin {
|
||||||
@ -27,308 +23,11 @@ macro_rules! require_admin {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Template)]
|
|
||||||
#[template(path = "admin/news.html")]
|
|
||||||
struct AdminNewsTemplate {
|
|
||||||
page: view::Page,
|
|
||||||
error: Option<String>,
|
|
||||||
account: Option<db::Account>,
|
|
||||||
news: Vec<db::NewsArticle>,
|
|
||||||
h: Helper,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("")]
|
|
||||||
async fn admin(db: Data<PgPool>, id: Identity) -> Result<HttpResponse> {
|
|
||||||
let pool = db.into_inner();
|
|
||||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
|
||||||
let _account = require_admin!(&mut t, id);
|
|
||||||
|
|
||||||
Ok(HttpResponse::SeeOther()
|
|
||||||
.append_header(("Location", "/admin/news"))
|
|
||||||
.finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("")]
|
|
||||||
async fn admin_news(db: Data<PgPool>, id: Identity) -> Result<HttpResponse> {
|
|
||||||
let pool = db.into_inner();
|
|
||||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
|
||||||
let _account = require_admin!(&mut t, id);
|
|
||||||
|
|
||||||
let news = queries::all_news(&mut t).await.unwrap_or_default();
|
|
||||||
|
|
||||||
t.commit().await.ok();
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(
|
|
||||||
AdminNewsTemplate {
|
|
||||||
page: Page::AdminNews,
|
|
||||||
news,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Template)]
|
|
||||||
#[template(path = "admin/edit.html")]
|
|
||||||
struct EditTemplate {
|
|
||||||
page: Page,
|
|
||||||
error: Option<String>,
|
|
||||||
account: Option<db::Account>,
|
|
||||||
article: db::NewsArticle,
|
|
||||||
h: Helper,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/{id}")]
|
|
||||||
async fn edit_news_article(
|
|
||||||
path: Path<(i32,)>,
|
|
||||||
db: Data<PgPool>,
|
|
||||||
id: Identity,
|
|
||||||
) -> Result<HttpResponse> {
|
|
||||||
let article_id = path.into_inner().0;
|
|
||||||
let pool = db.into_inner();
|
|
||||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
|
||||||
let _account = require_admin!(&mut t, id);
|
|
||||||
|
|
||||||
let article = match queries::news_article_by_id(&mut t, article_id).await {
|
|
||||||
Ok(article) => article,
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
|
|
||||||
return Ok(HttpResponse::BadRequest().finish());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
t.commit().await.ok();
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(
|
|
||||||
EditTemplate {
|
|
||||||
page: Page::AdminNews,
|
|
||||||
article,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/create")]
|
|
||||||
async fn create_news_article(
|
|
||||||
db: Data<PgPool>,
|
|
||||||
id: Identity,
|
|
||||||
form: Form<view::CreateNewsArticleInput>,
|
|
||||||
) -> Result<HttpResponse> {
|
|
||||||
let form = form.into_inner();
|
|
||||||
let pool = db.into_inner();
|
|
||||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
|
||||||
let _account = require_admin!(&mut t, id);
|
|
||||||
|
|
||||||
if let Err(e) = queries::create_news_article(
|
|
||||||
&mut t,
|
|
||||||
db::CreateNewsArticleInput {
|
|
||||||
title: form.title,
|
|
||||||
body: form.body,
|
|
||||||
status: form.status,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
dbg!(e);
|
|
||||||
t.rollback().await.ok();
|
|
||||||
|
|
||||||
return Ok(HttpResponse::BadRequest().content_type("text/html").body(
|
|
||||||
AdminNewsTemplate {
|
|
||||||
page: Page::AdminCreateNews,
|
|
||||||
error: Some("Failed".into()),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.unwrap(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
t.commit().await.ok();
|
|
||||||
|
|
||||||
Ok(HttpResponse::SeeOther()
|
|
||||||
.append_header(("Location", "/admin/news"))
|
|
||||||
.finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/update")]
|
|
||||||
async fn update_news_article(
|
|
||||||
db: Data<PgPool>,
|
|
||||||
id: Identity,
|
|
||||||
form: Form<view::UpdateNewsArticleInput>,
|
|
||||||
) -> Result<HttpResponse> {
|
|
||||||
let form = form.into_inner();
|
|
||||||
let pool = db.into_inner();
|
|
||||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
|
||||||
let _account = require_admin!(&mut t, id);
|
|
||||||
|
|
||||||
match queries::update_news_article(
|
|
||||||
&mut t,
|
|
||||||
db::UpdateNewsArticleInput {
|
|
||||||
id: form.id,
|
|
||||||
title: form.title,
|
|
||||||
body: form.body,
|
|
||||||
status: form.status,
|
|
||||||
published_at: if matches!(form.status, db::NewsStatus::Published) {
|
|
||||||
Some(chrono::Utc::now().naive_utc())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
t.rollback().await.ok();
|
|
||||||
|
|
||||||
Ok(HttpResponse::SeeOther()
|
|
||||||
.append_header(("Location", "/admin/news"))
|
|
||||||
.finish())
|
|
||||||
}
|
|
||||||
Ok(..) => {
|
|
||||||
t.commit().await.ok();
|
|
||||||
|
|
||||||
Ok(HttpResponse::SeeOther()
|
|
||||||
.append_header(("Location", "/admin/news"))
|
|
||||||
.finish())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/delete")]
|
|
||||||
async fn delete_news_article(
|
|
||||||
db: Data<PgPool>,
|
|
||||||
id: Identity,
|
|
||||||
form: Form<view::DeleteNewsArticleInput>,
|
|
||||||
) -> Result<HttpResponse> {
|
|
||||||
let form = form.into_inner();
|
|
||||||
let pool = db.into_inner();
|
|
||||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
|
||||||
let _account = require_admin!(&mut t, id);
|
|
||||||
|
|
||||||
match queries::delete_news_article(&mut t, form.id).await {
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
t.rollback().await.ok();
|
|
||||||
|
|
||||||
Ok(HttpResponse::SeeOther()
|
|
||||||
.append_header(("Location", "/admin"))
|
|
||||||
.finish())
|
|
||||||
}
|
|
||||||
Ok(..) => {
|
|
||||||
t.commit().await.ok();
|
|
||||||
|
|
||||||
Ok(HttpResponse::SeeOther()
|
|
||||||
.append_header(("Location", "/admin"))
|
|
||||||
.finish())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/upload")]
|
|
||||||
async fn news_article_upload(
|
|
||||||
payload: actix_multipart::Multipart,
|
|
||||||
db: Data<PgPool>,
|
|
||||||
id: Identity,
|
|
||||||
) -> JsonResult<HttpResponse> {
|
|
||||||
let pool = db.into_inner();
|
|
||||||
let mut t = crate::ok_or_internal!(json pool.begin().await);
|
|
||||||
let account = require_admin!(json; t, id);
|
|
||||||
t.commit().await.ok();
|
|
||||||
crate::routes::uploads::hande_upload(payload, Some(account.id), "news").await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Template)]
|
|
||||||
#[template(path = "admin/businesses.html")]
|
|
||||||
struct AdminBusinessesTemplate {
|
|
||||||
page: Page,
|
|
||||||
error: Option<String>,
|
|
||||||
account: Option<db::Account>,
|
|
||||||
businesses: Vec<view::LocalBusiness>,
|
|
||||||
h: Helper,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("")]
|
|
||||||
async fn admin_businesses(db: Data<PgPool>, id: Identity) -> Result<HttpResponse> {
|
|
||||||
let pool = db.into_inner();
|
|
||||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
|
||||||
let _account = require_admin!(&mut t, id);
|
|
||||||
|
|
||||||
let (services, mut items, mut contacts) = {
|
|
||||||
use crate::model::db::{LocalBusiness, LocalBusinessItem};
|
|
||||||
let services: Vec<LocalBusiness> =
|
|
||||||
queries::all_businesses(&mut t).await.unwrap_or_default();
|
|
||||||
|
|
||||||
let items: Vec<LocalBusinessItem> = queries::all_business_items(&mut t)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default();
|
|
||||||
let contacts = queries::all_contacts(&mut t).await.unwrap_or_default();
|
|
||||||
(services, items, contacts)
|
|
||||||
};
|
|
||||||
|
|
||||||
let businesses: Vec<_> = services
|
|
||||||
.into_iter()
|
|
||||||
.map(|service| view::LocalBusiness::from((service, &mut items, &mut contacts)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
t.commit().await.ok();
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(
|
|
||||||
AdminBusinessesTemplate {
|
|
||||||
page: Page::AdminBusinesses,
|
|
||||||
businesses,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/set-state")]
|
|
||||||
async fn admin_business_set_state(
|
|
||||||
db: Data<PgPool>,
|
|
||||||
id: Identity,
|
|
||||||
form: Form<SetStateBusinessInput>,
|
|
||||||
) -> Result<HttpResponse> {
|
|
||||||
let pool = db.into_inner();
|
|
||||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
|
||||||
let _account = require_admin!(&mut t, id);
|
|
||||||
let form = form.into_inner();
|
|
||||||
dbg!(&form);
|
|
||||||
|
|
||||||
if let Err(e) = queries::set_business_state(&mut t, form.id, form.state).await {
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = t.commit().await {
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::SeeOther()
|
|
||||||
.append_header(("Location", "/admin/businesses"))
|
|
||||||
.finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure(config: &mut ServiceConfig) {
|
pub fn configure(config: &mut ServiceConfig) {
|
||||||
config.service(
|
config.service(
|
||||||
web::scope("/admin")
|
web::scope("/admin")
|
||||||
.service(
|
.configure(news::configure)
|
||||||
web::scope("/news")
|
.configure(businesses::configure)
|
||||||
.service(admin_news)
|
.configure(offers::configure),
|
||||||
.service(create_news_article)
|
|
||||||
.service(news_article_upload)
|
|
||||||
.service(edit_news_article)
|
|
||||||
.service(update_news_article)
|
|
||||||
.service(delete_news_article),
|
|
||||||
)
|
|
||||||
.service(
|
|
||||||
web::scope("/businesses")
|
|
||||||
.service(admin_businesses)
|
|
||||||
.service(admin_business_set_state),
|
|
||||||
)
|
|
||||||
.service(admin),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
89
src/routes/restricted/admin/businesses.rs
Normal file
89
src/routes/restricted/admin/businesses.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
use actix_web::web::{Data, Form, ServiceConfig};
|
||||||
|
use actix_web::{get, post, web, HttpResponse};
|
||||||
|
use askama::*;
|
||||||
|
use sqlx_core::postgres::PgPool;
|
||||||
|
|
||||||
|
use crate::model::view::{Page, SetStateBusinessInput};
|
||||||
|
use crate::model::{db, view};
|
||||||
|
use crate::routes::{Identity, Result};
|
||||||
|
use crate::view::Helper;
|
||||||
|
use crate::{authorize, ok_or_internal, queries, require_admin};
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Template)]
|
||||||
|
#[template(path = "admin/businesses.html")]
|
||||||
|
struct AdminBusinessesTemplate {
|
||||||
|
page: Page,
|
||||||
|
error: Option<String>,
|
||||||
|
account: Option<db::Account>,
|
||||||
|
businesses: Vec<view::LocalBusiness>,
|
||||||
|
h: Helper,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("")]
|
||||||
|
async fn admin_businesses(db: Data<PgPool>, id: Identity) -> Result<HttpResponse> {
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = ok_or_internal!(pool.begin().await);
|
||||||
|
let _account = require_admin!(&mut t, id);
|
||||||
|
|
||||||
|
let (services, mut items, mut contacts) = {
|
||||||
|
use crate::model::db::{LocalBusiness, LocalBusinessItem};
|
||||||
|
let services: Vec<LocalBusiness> =
|
||||||
|
queries::all_businesses(&mut t).await.unwrap_or_default();
|
||||||
|
|
||||||
|
let items: Vec<LocalBusinessItem> = queries::all_business_items(&mut t)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
let contacts = queries::all_contacts(&mut t).await.unwrap_or_default();
|
||||||
|
(services, items, contacts)
|
||||||
|
};
|
||||||
|
|
||||||
|
let businesses: Vec<_> = services
|
||||||
|
.into_iter()
|
||||||
|
.map(|service| view::LocalBusiness::from((service, &mut items, &mut contacts)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
t.commit().await.ok();
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||||
|
AdminBusinessesTemplate {
|
||||||
|
page: Page::AdminBusinesses,
|
||||||
|
businesses,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/set-state")]
|
||||||
|
async fn admin_business_set_state(
|
||||||
|
db: Data<PgPool>,
|
||||||
|
id: Identity,
|
||||||
|
form: Form<SetStateBusinessInput>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||||
|
let _account = require_admin!(&mut t, id);
|
||||||
|
let form = form.into_inner();
|
||||||
|
dbg!(&form);
|
||||||
|
|
||||||
|
if let Err(e) = queries::set_business_state(&mut t, form.id, form.state).await {
|
||||||
|
dbg!(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = t.commit().await {
|
||||||
|
dbg!(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", "/admin/businesses"))
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure(config: &mut ServiceConfig) {
|
||||||
|
config.service(
|
||||||
|
web::scope("/businesses")
|
||||||
|
.service(admin_businesses)
|
||||||
|
.service(admin_business_set_state),
|
||||||
|
);
|
||||||
|
}
|
238
src/routes/restricted/admin/news.rs
Normal file
238
src/routes/restricted/admin/news.rs
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
use actix_web::web::{Data, Form, Path, ServiceConfig};
|
||||||
|
use actix_web::{get, post, web, HttpResponse};
|
||||||
|
use askama::*;
|
||||||
|
use sqlx_core::postgres::PgPool;
|
||||||
|
|
||||||
|
use crate::model::view::Page;
|
||||||
|
use crate::model::{db, view};
|
||||||
|
use crate::routes::{Identity, JsonResult, Result};
|
||||||
|
use crate::view::{filters, Helper};
|
||||||
|
use crate::{authorize, ok_or_internal, queries, require_admin};
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Template)]
|
||||||
|
#[template(path = "admin/news.html")]
|
||||||
|
struct AdminNewsTemplate {
|
||||||
|
page: view::Page,
|
||||||
|
error: Option<String>,
|
||||||
|
account: Option<db::Account>,
|
||||||
|
news: Vec<db::NewsArticle>,
|
||||||
|
h: Helper,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("")]
|
||||||
|
async fn admin(db: Data<PgPool>, id: Identity) -> Result<HttpResponse> {
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = ok_or_internal!(pool.begin().await);
|
||||||
|
let _account = require_admin!(&mut t, id);
|
||||||
|
|
||||||
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", "/admin/news"))
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("")]
|
||||||
|
async fn admin_news(db: Data<PgPool>, id: Identity) -> Result<HttpResponse> {
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = ok_or_internal!(pool.begin().await);
|
||||||
|
let _account = require_admin!(&mut t, id);
|
||||||
|
|
||||||
|
let news = queries::all_news(&mut t).await.unwrap_or_default();
|
||||||
|
|
||||||
|
t.commit().await.ok();
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||||
|
AdminNewsTemplate {
|
||||||
|
page: Page::AdminNews,
|
||||||
|
news,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Template)]
|
||||||
|
#[template(path = "admin/edit.html")]
|
||||||
|
struct EditTemplate {
|
||||||
|
page: Page,
|
||||||
|
error: Option<String>,
|
||||||
|
account: Option<db::Account>,
|
||||||
|
article: db::NewsArticle,
|
||||||
|
h: Helper,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/{id}")]
|
||||||
|
async fn edit_news_article(
|
||||||
|
path: Path<(i32,)>,
|
||||||
|
db: Data<PgPool>,
|
||||||
|
id: Identity,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let article_id = path.into_inner().0;
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||||
|
let _account = require_admin!(&mut t, id);
|
||||||
|
|
||||||
|
let article = match queries::news_article_by_id(&mut t, article_id).await {
|
||||||
|
Ok(article) => article,
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
|
||||||
|
return Ok(HttpResponse::BadRequest().finish());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
t.commit().await.ok();
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||||
|
EditTemplate {
|
||||||
|
page: Page::AdminNews,
|
||||||
|
article,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/create")]
|
||||||
|
async fn create_news_article(
|
||||||
|
db: Data<PgPool>,
|
||||||
|
id: Identity,
|
||||||
|
form: Form<view::CreateNewsArticleInput>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let form = form.into_inner();
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||||
|
let _account = require_admin!(&mut t, id);
|
||||||
|
|
||||||
|
if let Err(e) = queries::create_news_article(
|
||||||
|
&mut t,
|
||||||
|
db::CreateNewsArticleInput {
|
||||||
|
title: form.title,
|
||||||
|
body: form.body,
|
||||||
|
status: form.status,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
dbg!(e);
|
||||||
|
t.rollback().await.ok();
|
||||||
|
|
||||||
|
return Ok(HttpResponse::BadRequest().content_type("text/html").body(
|
||||||
|
AdminNewsTemplate {
|
||||||
|
page: Page::AdminCreateNews,
|
||||||
|
error: Some("Failed".into()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
t.commit().await.ok();
|
||||||
|
|
||||||
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", "/admin/news"))
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/update")]
|
||||||
|
async fn update_news_article(
|
||||||
|
db: Data<PgPool>,
|
||||||
|
id: Identity,
|
||||||
|
form: Form<view::UpdateNewsArticleInput>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let form = form.into_inner();
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = ok_or_internal!(pool.begin().await);
|
||||||
|
let _account = require_admin!(&mut t, id);
|
||||||
|
|
||||||
|
match queries::update_news_article(
|
||||||
|
&mut t,
|
||||||
|
db::UpdateNewsArticleInput {
|
||||||
|
id: form.id,
|
||||||
|
title: form.title,
|
||||||
|
body: form.body,
|
||||||
|
status: form.status,
|
||||||
|
published_at: if matches!(form.status, db::NewsStatus::Published) {
|
||||||
|
Some(chrono::Utc::now().naive_utc())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
t.rollback().await.ok();
|
||||||
|
|
||||||
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", "/admin/news"))
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
Ok(..) => {
|
||||||
|
t.commit().await.ok();
|
||||||
|
|
||||||
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", "/admin/news"))
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/delete")]
|
||||||
|
async fn delete_news_article(
|
||||||
|
db: Data<PgPool>,
|
||||||
|
id: Identity,
|
||||||
|
form: Form<view::DeleteNewsArticleInput>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let form = form.into_inner();
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||||
|
let _account = require_admin!(&mut t, id);
|
||||||
|
|
||||||
|
match queries::delete_news_article(&mut t, form.id).await {
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
t.rollback().await.ok();
|
||||||
|
|
||||||
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", "/admin"))
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
Ok(..) => {
|
||||||
|
t.commit().await.ok();
|
||||||
|
|
||||||
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", "/admin"))
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/upload")]
|
||||||
|
async fn news_article_upload(
|
||||||
|
payload: actix_multipart::Multipart,
|
||||||
|
db: Data<PgPool>,
|
||||||
|
id: Identity,
|
||||||
|
) -> JsonResult<HttpResponse> {
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = crate::ok_or_internal!(json pool.begin().await);
|
||||||
|
let account = require_admin!(json; t, id);
|
||||||
|
t.commit().await.ok();
|
||||||
|
crate::routes::uploads::hande_upload(payload, Some(account.id), "news").await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure(config: &mut ServiceConfig) {
|
||||||
|
config
|
||||||
|
.service(
|
||||||
|
web::scope("/news")
|
||||||
|
.service(admin_news)
|
||||||
|
.service(create_news_article)
|
||||||
|
.service(news_article_upload)
|
||||||
|
.service(edit_news_article)
|
||||||
|
.service(update_news_article)
|
||||||
|
.service(delete_news_article),
|
||||||
|
)
|
||||||
|
.service(admin);
|
||||||
|
}
|
131
src/routes/restricted/admin/offers.rs
Normal file
131
src/routes/restricted/admin/offers.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use actix_web::web::{Data, Form, ServiceConfig};
|
||||||
|
use actix_web::{get, post, web, HttpResponse};
|
||||||
|
use askama::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx_core::postgres::PgPool;
|
||||||
|
|
||||||
|
use crate::model::db;
|
||||||
|
use crate::model::db::PriceRange;
|
||||||
|
use crate::model::view::Page;
|
||||||
|
use crate::routes::{Identity, Result};
|
||||||
|
use crate::view::Helper;
|
||||||
|
use crate::{authorize, ok_or_internal, queries, require_admin};
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Template)]
|
||||||
|
#[template(path = "admin/offers/index.html")]
|
||||||
|
struct AdminOffersTemplate {
|
||||||
|
page: Page,
|
||||||
|
error: Option<String>,
|
||||||
|
account: Option<db::Account>,
|
||||||
|
h: Helper,
|
||||||
|
offers: Vec<db::Offer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("")]
|
||||||
|
async fn admin_offers(db: Data<PgPool>, id: Identity) -> Result<HttpResponse> {
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = ok_or_internal!(pool.begin().await);
|
||||||
|
let account = require_admin!(&mut t, id);
|
||||||
|
|
||||||
|
let offers = queries::all_offers(&mut t).await.unwrap_or_default();
|
||||||
|
|
||||||
|
t.commit().await.ok();
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||||
|
AdminOffersTemplate {
|
||||||
|
account: Some(account),
|
||||||
|
page: Page::AdminOffers,
|
||||||
|
offers,
|
||||||
|
error: None,
|
||||||
|
h: Default::default(),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AffectedOffer {
|
||||||
|
id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/ban")]
|
||||||
|
async fn admin_ban_offer(
|
||||||
|
db: Data<PgPool>,
|
||||||
|
id: Identity,
|
||||||
|
form: Form<AffectedOffer>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = ok_or_internal!(pool.begin().await);
|
||||||
|
let account = require_admin!(&mut t, id);
|
||||||
|
|
||||||
|
match queries::set_offer_state(&mut t, form.id, db::OfferState::Banned).await {
|
||||||
|
Ok(_) => {
|
||||||
|
t.commit().await.ok();
|
||||||
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", "/admin/offers"))
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
let offers = queries::all_offers(&mut t).await.unwrap_or_default();
|
||||||
|
t.rollback().await.ok();
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||||
|
AdminOffersTemplate {
|
||||||
|
account: Some(account),
|
||||||
|
page: Page::AdminOffers,
|
||||||
|
offers,
|
||||||
|
error: Some("Nie można zbanować oferty".into()),
|
||||||
|
h: Default::default(),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/approve")]
|
||||||
|
async fn admin_approve_offer(
|
||||||
|
db: Data<PgPool>,
|
||||||
|
id: Identity,
|
||||||
|
form: Form<AffectedOffer>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = ok_or_internal!(pool.begin().await);
|
||||||
|
let account = require_admin!(&mut t, id);
|
||||||
|
|
||||||
|
match queries::set_offer_state(&mut t, form.id, db::OfferState::Approved).await {
|
||||||
|
Ok(_) => {
|
||||||
|
t.commit().await.ok();
|
||||||
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", "/admin/offers"))
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
let offers = queries::all_offers(&mut t).await.unwrap_or_default();
|
||||||
|
t.rollback().await.ok();
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||||
|
AdminOffersTemplate {
|
||||||
|
account: Some(account),
|
||||||
|
page: Page::AdminOffers,
|
||||||
|
offers,
|
||||||
|
error: Some("Nie można zbanować oferty".into()),
|
||||||
|
h: Default::default(),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure(config: &mut ServiceConfig) {
|
||||||
|
config.service(
|
||||||
|
web::scope("/offers")
|
||||||
|
.service(admin_offers)
|
||||||
|
.service(admin_ban_offer)
|
||||||
|
.service(admin_approve_offer),
|
||||||
|
);
|
||||||
|
}
|
@ -106,7 +106,7 @@ async fn update_offer(
|
|||||||
) -> Result<HttpResponse> {
|
) -> Result<HttpResponse> {
|
||||||
let pool = db.into_inner();
|
let pool = db.into_inner();
|
||||||
let mut t = ok_or_internal!(pool.begin().await);
|
let mut t = ok_or_internal!(pool.begin().await);
|
||||||
let _account = authorize!(&mut t, id);
|
let account = authorize!(&mut t, id);
|
||||||
let form = form.into_inner();
|
let form = form.into_inner();
|
||||||
dbg!(&form);
|
dbg!(&form);
|
||||||
|
|
||||||
@ -125,12 +125,31 @@ async fn update_offer(
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {}
|
Ok(_) => {
|
||||||
Err(_) => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.commit().await.ok();
|
t.commit().await.ok();
|
||||||
Ok(HttpResponse::NotImplemented().finish())
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", "/account/offers"))
|
||||||
|
.finish())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
let offers = queries::account_offers(&mut t, account.id)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
t.rollback().await.ok();
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||||
|
AccountOffersTemplate {
|
||||||
|
account: Some(account),
|
||||||
|
offers,
|
||||||
|
page: Page::AccountOffers,
|
||||||
|
error: Some("Problem z utworzeniem wpisu".into()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configure(config: &mut ServiceConfig) {
|
pub fn configure(config: &mut ServiceConfig) {
|
||||||
|
Loading…
Reference in New Issue
Block a user