Compare commits
10 Commits
4e1755895d
...
d6899a03e6
Author | SHA1 | Date | |
---|---|---|---|
d6899a03e6 | |||
50fffa9158 | |||
b9569229fb | |||
bbf774d8d9 | |||
6cff8e7a37 | |||
9cf85bece3 | |||
ac49905e0b | |||
a9d223d976 | |||
670c1f7cf2 | |||
ee61a10282 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -9,3 +9,6 @@ client/dist/admin.js
|
|||||||
web/tmp
|
web/tmp
|
||||||
web/build
|
web/build
|
||||||
**/*.wasm
|
**/*.wasm
|
||||||
|
tmp
|
||||||
|
web/node_modules
|
||||||
|
web/dist
|
||||||
|
100
Cargo.lock
generated
100
Cargo.lock
generated
@ -1026,6 +1026,15 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "html-escape"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
|
||||||
|
dependencies = [
|
||||||
|
"utf8-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
@ -1815,6 +1824,15 @@ version = "0.4.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slotmap"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@ -1959,6 +1977,75 @@ version = "2.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sycamore"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67817393b3c9828db84614f64db9a1ebb94729ce3a3751c41e7ff23d3f8e7f00"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"indexmap",
|
||||||
|
"js-sys",
|
||||||
|
"paste",
|
||||||
|
"sycamore-core",
|
||||||
|
"sycamore-macro",
|
||||||
|
"sycamore-reactive",
|
||||||
|
"sycamore-web",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sycamore-core"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dce7f0440c5ea2b74a544deb5423708c023fade36e63515423d3f3ab5e1a998"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"sycamore-reactive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sycamore-macro"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f3abd3f402c1a943cf70860b91a40c79c713e269156445998dbfd647deac8a5"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sycamore-reactive"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6376b578ad32f5f3ab6943bccec906fb0e1f0258a8bedf811afdec8c3330ef80"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"bumpalo",
|
||||||
|
"indexmap",
|
||||||
|
"slotmap",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sycamore-web"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9520735f765e60718df8125eb27db19b990bed1116c9b3e8aae374dde1fe8"
|
||||||
|
dependencies = [
|
||||||
|
"html-escape",
|
||||||
|
"indexmap",
|
||||||
|
"js-sys",
|
||||||
|
"once_cell",
|
||||||
|
"sycamore-core",
|
||||||
|
"sycamore-reactive",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.98"
|
version = "1.0.98"
|
||||||
@ -2305,6 +2392,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8-width"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@ -2436,6 +2529,13 @@ version = "0.2.81"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
|
checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"sycamore",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.58"
|
version = "0.3.58"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
'server',
|
'server',
|
||||||
|
'web',
|
||||||
]
|
]
|
||||||
|
@ -8,3 +8,4 @@ import "./admin/businesses/admin-businesses";
|
|||||||
import "./admin/businesses/admin-edit-business";
|
import "./admin/businesses/admin-edit-business";
|
||||||
|
|
||||||
import "./admin/offers/admin-edit-offer";
|
import "./admin/offers/admin-edit-offer";
|
||||||
|
import "./admin/offers/ow-admin-offers";
|
||||||
|
@ -1,12 +1,66 @@
|
|||||||
import { Component } from "../../shared";
|
import { Component, FORM_STYLE } from "../../shared";
|
||||||
|
|
||||||
customElements.define('admin-businesses', class extends Component {
|
customElements.define('admin-businesses', class extends Component {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['state-filter'];
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(`
|
super(`
|
||||||
<style>
|
<style>
|
||||||
:host { display: block; }
|
:host { display: block; }
|
||||||
|
::slotted([state]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:host([state-filter="Pending"]) ::slotted([state="Pending"]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
:host([state-filter="Approved"]) ::slotted([state="Approved"]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
:host([state-filter="Banned"]) ::slotted([state="Banned"]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
:host([state-filter="Pinned"]) ::slotted([state="Pinned"]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
:host([state-filter="Internal"]) ::slotted([state="Internal"]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
${ FORM_STYLE }
|
||||||
</style>
|
</style>
|
||||||
<slot></slot>
|
<article>
|
||||||
|
<section>
|
||||||
|
<select id="state">
|
||||||
|
<option value="Pending">Pending</option>
|
||||||
|
<option value="Approved">Approved</option>
|
||||||
|
<option value="Banned">Banned</option>
|
||||||
|
<option value="Pinned">Pinned</option>
|
||||||
|
<option value="Internal">Internal</option>
|
||||||
|
</select>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<slot></slot>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
`);
|
`);
|
||||||
|
this.shadowRoot.querySelector('#state').addEventListener('change', ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.state_filter = ev.target.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.state_filter = 'Pending';
|
||||||
|
}
|
||||||
|
|
||||||
|
get state_filter() {
|
||||||
|
return this.getAttribute('state-filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
set state_filter(v) {
|
||||||
|
this.setAttribute('state-filter', v);
|
||||||
|
this.shadowRoot.querySelector('#state').value = v;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -8,26 +8,47 @@ customElements.define('admin-edit-business', class extends Component {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super(`
|
super(`
|
||||||
<style>
|
<style>
|
||||||
:host { display: block; }
|
:host {
|
||||||
section {
|
display: block;
|
||||||
|
border-bottom: 2px solid var(--border-slim-color);
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
:host(:first-child) {
|
||||||
|
border-top: 2px solid var(--border-slim-color);
|
||||||
|
}
|
||||||
|
article {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
#state {
|
#state {
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
#view {
|
||||||
|
width: calc(100% - 220px);
|
||||||
|
}
|
||||||
|
#actions {
|
||||||
|
width: 200px
|
||||||
|
}
|
||||||
#actions > input:not(:last-child) {
|
#actions > input:not(:last-child) {
|
||||||
margin-right: .5rem;
|
margin-right: .5rem;
|
||||||
}
|
}
|
||||||
|
#actions > input {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#actions select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
::slotted(admin-business) {
|
::slotted(admin-business) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
${ BUTTON_STYLE }
|
${ BUTTON_STYLE }${ INPUT_STYLE }
|
||||||
${ INPUT_STYLE }
|
|
||||||
</style>
|
</style>
|
||||||
<section>
|
<article>
|
||||||
<slot></slot>
|
<section id="view">
|
||||||
<div id="actions">
|
<slot></slot>
|
||||||
|
</section>
|
||||||
|
<section id="actions">
|
||||||
<input value="Usuń" type="button" />
|
<input value="Usuń" type="button" />
|
||||||
<form id="change-state" action="/admin/businesses/set-state" method="post">
|
<form id="change-state" action="/admin/businesses/set-state" method="post">
|
||||||
<input name="id" id="id" type="hidden" />
|
<input name="id" id="id" type="hidden" />
|
||||||
@ -39,8 +60,8 @@ customElements.define('admin-edit-business', class extends Component {
|
|||||||
<option value="Internal">Internal</option>
|
<option value="Internal">Internal</option>
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
</article>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const form = this.shadowRoot.querySelector('#change-state');
|
const form = this.shadowRoot.querySelector('#change-state');
|
||||||
|
@ -10,6 +10,8 @@ customElements.define('admin-edit-offer', class extends Component {
|
|||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
border-bottom: 2px solid var(--border-slim-color);
|
||||||
|
padding: 8px 0;
|
||||||
}
|
}
|
||||||
#view { display: block; }
|
#view { display: block; }
|
||||||
#actions form {
|
#actions form {
|
||||||
@ -17,6 +19,7 @@ customElements.define('admin-edit-offer', class extends Component {
|
|||||||
}
|
}
|
||||||
#actions form input {
|
#actions form input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
#state {
|
#state {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
64
client/src/admin/offers/ow-admin-offers.js
Normal file
64
client/src/admin/offers/ow-admin-offers.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { Component, FORM_STYLE } from "../../shared.js";
|
||||||
|
|
||||||
|
customElements.define('ow-admin-offers', class extends Component {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['state-filter'];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
::slotted([state]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:host([state-filter='Pending']) ::slotted([state="Pending"]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
:host([state-filter='Approved']) ::slotted([state="Approved"]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
:host([state-filter='Banned']) ::slotted([state="Banned"]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
:host([state-filter='Finished']) ::slotted([state="Finished"]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
${ FORM_STYLE }
|
||||||
|
</style>
|
||||||
|
<article>
|
||||||
|
<section>
|
||||||
|
<select id="state">
|
||||||
|
<option value="Pending">Pending</option>
|
||||||
|
<option value="Approved">Approved</option>
|
||||||
|
<option value="Banned">Banned</option>
|
||||||
|
<option value="Finished">Finished</option>
|
||||||
|
</select>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<slot></slot>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
`);
|
||||||
|
this.shadowRoot.querySelector('#state').addEventListener('change', ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.state_filter = ev.target.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.state_filter = 'Pending';
|
||||||
|
}
|
||||||
|
|
||||||
|
get state_filter() {
|
||||||
|
return this.getAttribute('state-filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
set state_filter(v) {
|
||||||
|
this.setAttribute('state-filter', v);
|
||||||
|
this.shadowRoot.querySelector('#state').value = v;
|
||||||
|
}
|
||||||
|
});
|
@ -18,8 +18,10 @@ import "./ow-account/account-view.js";
|
|||||||
import "./local-businesses/local-businesses.js";
|
import "./local-businesses/local-businesses.js";
|
||||||
import "./local-businesses/local-business-item.js";
|
import "./local-businesses/local-business-item.js";
|
||||||
import "./local-businesses/local-business.js";
|
import "./local-businesses/local-business.js";
|
||||||
|
import "./local-businesses/single-local-business.js";
|
||||||
|
|
||||||
import "./login-form.js";
|
import "./login-form.js";
|
||||||
|
|
||||||
import "./register-form.js";
|
import "./register-form.js";
|
||||||
import "./register-form/register-business-account-form";
|
import "./register-form/register-business-account-form";
|
||||||
import "./register-form/register-business-item-form.js";
|
import "./register-form/register-business-item-form.js";
|
||||||
|
@ -81,19 +81,23 @@ customElements.define('business-item-editor', class extends Component {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
if (this.shadowRoot.querySelector('register-item-form-row').reportValidity()) {
|
// console.info(this);
|
||||||
|
// if (this.shadowRoot.querySelector('register-item-form-row').reportValidity()) {
|
||||||
|
if (form.reportValidity()) {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
price,
|
price,
|
||||||
picture_url,
|
picture_url,
|
||||||
item_order,
|
item_order,
|
||||||
} = ev.detail;
|
} = ev.detail;
|
||||||
|
|
||||||
form.querySelector('#name').value = name;
|
form.querySelector('#name').value = name;
|
||||||
form.querySelector('#price').value = price;
|
form.querySelector('#price').value = price;
|
||||||
form.querySelector('#picture_url').value = picture_url;
|
form.querySelector('#picture_url').value = picture_url;
|
||||||
form.querySelector('#item_order').value = item_order;
|
form.querySelector('#item_order').value = item_order;
|
||||||
form.submit();
|
form.submit();
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
const moveForm = this.shadowRoot.querySelector('#moveForm');
|
const moveForm = this.shadowRoot.querySelector('#moveForm');
|
||||||
this.addEventListener('item:up', ev => {
|
this.addEventListener('item:up', ev => {
|
||||||
|
@ -147,10 +147,6 @@ customElements.define('business-item', class extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
attributeChangedCallback(name, oldV, newV) {
|
attributeChangedCallback(name, oldV, newV) {
|
||||||
super.attributeChangedCallback(name, oldV, newV);
|
super.attributeChangedCallback(name, oldV, newV);
|
||||||
|
|
||||||
|
@ -160,6 +160,9 @@ customElements.define('contact-info-editor', class extends Component {
|
|||||||
if (s.match(/^[a-zA-Z\d.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*$/)) {
|
if (s.match(/^[a-zA-Z\d.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*$/)) {
|
||||||
return 'email';
|
return 'email';
|
||||||
}
|
}
|
||||||
|
if (s.match(/https?:\/\//)) {
|
||||||
|
return 'link';
|
||||||
|
}
|
||||||
return 'other';
|
return 'other';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -23,7 +23,8 @@ customElements.define('contact-type-icon',
|
|||||||
:host([type="mobile"]) #mobile-icon {
|
:host([type="mobile"]) #mobile-icon {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
path {
|
:host([type='link']) #link-icon {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -45,6 +46,9 @@ customElements.define('contact-type-icon',
|
|||||||
<circle cx="272.723" cy="55.769" r="5.977"/>
|
<circle cx="272.723" cy="55.769" r="5.977"/>
|
||||||
<circle cx="312.426" cy="55.769" r="5.977"/>
|
<circle cx="312.426" cy="55.769" r="5.977"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
<svg id="link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 162.656 162.656" xml:space="preserve">
|
||||||
|
<path d="M151.764 10.894c-14.522-14.522-38.152-14.525-52.676-.008l.003.003-22.979 22.983 10.607 10.605 22.983-22.988-.002-.002c8.678-8.663 22.785-8.658 31.457.014 8.673 8.672 8.672 22.786 0 31.461l-34.486 34.484a22.095 22.095 0 0 1-15.729 6.516 22.098 22.098 0 0 1-15.73-6.516L64.605 98.052c7.035 7.035 16.389 10.91 26.338 10.91 9.949 0 19.303-3.875 26.335-10.91l34.487-34.484c14.519-14.525 14.519-38.155-.001-52.674z"/><path d="M52.96 141.162c-8.675 8.67-22.788 8.668-31.461-.005-8.673-8.675-8.673-22.791-.001-31.465L55.98 75.21c8.675-8.674 22.789-8.674 31.462 0L98.05 64.604c-14.524-14.523-38.154-14.524-52.676 0L10.89 99.086c-14.519 14.523-14.519 38.154.001 52.678 7.263 7.262 16.801 10.893 26.341 10.892 9.536 0 19.074-3.629 26.333-10.887l.002-.001 22.984-22.99-10.608-10.606-22.983 22.99z"/>
|
||||||
|
</svg>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,15 @@ customElements.define('local-business-item', class extends Component {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super(`
|
super(`
|
||||||
<style>
|
<style>
|
||||||
:host { display: block; }
|
:host {
|
||||||
* { font-family: 'Cardo', sans-serif; }
|
display: block;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
font-family: 'Cardo', sans-serif;
|
||||||
|
--img-width: 128px;
|
||||||
|
--price-width: 160px;
|
||||||
|
--name-width: calc(100% - var(--price-width) - var(--img-width) - 20px);
|
||||||
|
}
|
||||||
:host([picture-url = '']) #img {
|
:host([picture-url = '']) #img {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -17,40 +24,47 @@ customElements.define('local-business-item', class extends Component {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: 'img name' 'img price';
|
grid-template-areas: 'img name' 'img price';
|
||||||
}
|
}
|
||||||
h3 {
|
h3#name {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
grid-area: name;
|
grid-area: name;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 16px;
|
||||||
|
width: var(--name-width);
|
||||||
}
|
}
|
||||||
#price {
|
#price {
|
||||||
grid-area: price; text-align: right;
|
grid-area: price;
|
||||||
|
text-align: right;
|
||||||
|
width: var(--price-width);
|
||||||
}
|
}
|
||||||
#img {
|
#img {
|
||||||
width: 128px;
|
width: var(--img-width);
|
||||||
max-width: 128px;
|
max-width: var(--img-width);
|
||||||
grid-area: img;
|
grid-area: img;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1000px) {
|
||||||
#item {
|
#item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
h3 {
|
h3#name {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
width: calc(100% - 450px);
|
width: var(--name-width);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
#price {
|
#price {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
width: 180px;
|
width: var(--price-width);
|
||||||
}
|
}
|
||||||
#img {
|
#img {
|
||||||
width: 128px;
|
width: var(--img-width);
|
||||||
max-width: 128px;
|
max-width: 128px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { Component } from "../shared";
|
|||||||
|
|
||||||
customElements.define('local-business', class extends Component {
|
customElements.define('local-business', class extends Component {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ['name', 'service-id', 'state']
|
return ['name', 'business-id', 'state']
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -42,7 +42,7 @@ customElements.define('local-business', class extends Component {
|
|||||||
|
|
||||||
set name(v) {
|
set name(v) {
|
||||||
this.setAttribute('name', v);
|
this.setAttribute('name', v);
|
||||||
this.shadowRoot.querySelector('#name').textContent = v;
|
this.#setNameHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
get state() {
|
get state() {
|
||||||
@ -53,12 +53,13 @@ customElements.define('local-business', class extends Component {
|
|||||||
this.setAttribute('state', v);
|
this.setAttribute('state', v);
|
||||||
}
|
}
|
||||||
|
|
||||||
get service_id() {
|
get business_id() {
|
||||||
return this.getAttribute('service-id');
|
return this.getAttribute('business-id');
|
||||||
}
|
}
|
||||||
|
|
||||||
set service_id(v) {
|
set business_id(v) {
|
||||||
this.setAttribute('service-id', v);
|
this.setAttribute('business-id', v);
|
||||||
|
this.#setNameHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
get description() {
|
get description() {
|
||||||
@ -70,4 +71,51 @@ customElements.define('local-business', class extends Component {
|
|||||||
this.description.any(s => s.match(regex)) ||
|
this.description.any(s => s.match(regex)) ||
|
||||||
Array.from(this.querySelectorAll('local-business-item')).any(el => el.matches(regex));
|
Array.from(this.querySelectorAll('local-business-item')).any(el => el.matches(regex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#setNameHeader() {
|
||||||
|
this.shadowRoot.querySelector('#name').innerHTML = `<a href="/local-businesses/${ this.business_id }">${ this.name }</a>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
customElements.define('business-description', class extends Component {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['truncate'];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(`<style>:host{display:block;}</style><p><article></article>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.#renderContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
get truncate() {
|
||||||
|
const v = parseInt(this.getAttribute('truncate'));
|
||||||
|
return isNaN(v) ? 0 : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
set truncate(v) {
|
||||||
|
if (v === false || v === 'false' || v == undefined) {
|
||||||
|
this.removeAttribute('truncate');
|
||||||
|
} else {
|
||||||
|
this.setAttribute('truncate', v);
|
||||||
|
}
|
||||||
|
this.#renderContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
#renderContent() {
|
||||||
|
let text = this.textContent;
|
||||||
|
const max = this.truncate;
|
||||||
|
const view = this.shadowRoot.querySelector('article');
|
||||||
|
const tail = text.length > max ? '...' : '';
|
||||||
|
if (max > 0) text = text.substring(0, max);
|
||||||
|
view.innerHTML = text
|
||||||
|
.trim()
|
||||||
|
.split("\n")
|
||||||
|
.filter(s => s && s.length)
|
||||||
|
.map((s, idx, a) => `<p>${ idx + 1 === a.length ? `${s}${tail}` : s }</p>`)
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -27,14 +27,35 @@ customElements.define('local-business-list', class extends Component {
|
|||||||
article {
|
article {
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
|
#items {
|
||||||
|
display: block;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
::slotted(local-business) {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
#search {
|
||||||
|
margin-bottom: 16px;;
|
||||||
|
margin-top: 16px;;
|
||||||
|
}
|
||||||
@media only screen and (min-device-width: 1000px) {
|
@media only screen and (min-device-width: 1000px) {
|
||||||
article {
|
article {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
#items {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-items: stretch;
|
||||||
|
}
|
||||||
|
::slotted(local-business) {
|
||||||
|
width: calc(50% - 40px);
|
||||||
|
margin: 0 20px 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<article>
|
<article>
|
||||||
<section>
|
<section id="search">
|
||||||
<search-input target="local-business"></search-input>
|
<search-input target="local-business"></search-input>
|
||||||
</section>
|
</section>
|
||||||
<section id="items">
|
<section id="items">
|
||||||
|
7
client/src/local-businesses/single-local-business.js
Normal file
7
client/src/local-businesses/single-local-business.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Component } from '../shared.js';
|
||||||
|
|
||||||
|
customElements.define('single-local-business', class extends Component {
|
||||||
|
constructor() {
|
||||||
|
super(`<style>:host{display:block;margin:0 8px;}@media only screen and (min-device-width: 100px){:host{margin:0;}}</style><article><slot></slot></article>`);
|
||||||
|
}
|
||||||
|
});
|
@ -20,6 +20,9 @@ customElements.define('marketplace-offer', class extends Component {
|
|||||||
|
|
||||||
section {
|
section {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview {
|
#preview {
|
||||||
@ -37,8 +40,7 @@ customElements.define('marketplace-offer', class extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host([price-range-max="0"]) #sep, :host([price-range]) #sep,
|
:host([price-range-max="0"]) #sep, :host([price-range]) #sep,
|
||||||
:host([price-range-max="0"]) #price-min, :host([price-range]) #price-min
|
:host([price-range-max="0"]) #price-min, :host([price-range]) #price-min {
|
||||||
{
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +89,7 @@ customElements.define('marketplace-offer', class extends Component {
|
|||||||
justify-content: end;
|
justify-content: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (min-device-width: 1200px) {
|
@media only screen and (min-device-width: 1000px) {
|
||||||
#details {
|
#details {
|
||||||
display: grid;
|
display: grid;
|
||||||
column-gap: 16px;
|
column-gap: 16px;
|
||||||
@ -125,24 +127,32 @@ customElements.define('marketplace-offer', class extends Component {
|
|||||||
width: 100px;
|
width: 100px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-style: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
${ INPUT_STYLE }
|
${ INPUT_STYLE }
|
||||||
</style>
|
</style>
|
||||||
<section id="details">
|
<article>
|
||||||
<div id="preview">
|
<section id="details">
|
||||||
<image-popup src="" id="picture">
|
<div id="preview">
|
||||||
</image-popup>
|
<image-popup src="" id="picture">
|
||||||
</div>
|
</image-popup>
|
||||||
<p id="description"></p>
|
</div>
|
||||||
<span id="price-min"></span>
|
<p id="description"></p>
|
||||||
<span id="sep">-</span>
|
<span id="price-min"></span>
|
||||||
<span id="price-max"></span>
|
<span id="sep">-</span>
|
||||||
</section>
|
<span id="price-max"></span>
|
||||||
<section id="contacts">
|
</section>
|
||||||
<span style="margin-right: 10px; font-weight: bold">Kontakt:</span>
|
<section id="contacts">
|
||||||
<slot name="contacts"></slot>
|
<span style="margin-right: 10px; font-weight: bold">Kontakt:</span>
|
||||||
</section>
|
<slot name="contacts"></slot>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
`);
|
`);
|
||||||
this.#price_range = new PriceRange(0, 0);
|
this.#price_range = new PriceRange(0, 0);
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,19 @@ customElements.define('marketplace-offers', class extends Component {
|
|||||||
:host([account-id]) #publishSection {
|
:host([account-id]) #publishSection {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
::slotted(marketplace-offer), ::slotted(user-edit-offer) {
|
::slotted(a), ::slotted(marketplace-offer), ::slotted(user-edit-offer) {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
font-style: normal;
|
||||||
}
|
}
|
||||||
::slotted([search-visible='invisible']) {
|
::slotted([search-visible='invisible']) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
#search {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
@media only screen and (min-device-width: 1000px) {
|
@media only screen and (min-device-width: 1000px) {
|
||||||
#offers {
|
#offers {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -39,9 +46,12 @@ customElements.define('marketplace-offers', class extends Component {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-items: stretch;
|
justify-items: stretch;
|
||||||
}
|
}
|
||||||
::slotted(marketplace-offer), ::slotted(user-edit-offer) {
|
::slotted(a), ::slotted(marketplace-offer), ::slotted(user-edit-offer) {
|
||||||
width: calc(33% - 40px);
|
width: calc(33% - 40px);
|
||||||
margin: 0 20px 20px;
|
margin: 0 20px 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
font-style: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${ BUTTON_STYLE }
|
${ BUTTON_STYLE }
|
||||||
@ -51,7 +61,7 @@ customElements.define('marketplace-offers', class extends Component {
|
|||||||
<button id="publish" class="btn">Dodaj ogłoszenie</button>
|
<button id="publish" class="btn">Dodaj ogłoszenie</button>
|
||||||
</section>
|
</section>
|
||||||
<section><slot></slot></section>
|
<section><slot></slot></section>
|
||||||
<section>
|
<section id="search">
|
||||||
<search-input target="user-edit-offer, marketplace-offer"></search-input>
|
<search-input target="user-edit-offer, marketplace-offer"></search-input>
|
||||||
</section>
|
</section>
|
||||||
<section id="offers">
|
<section id="offers">
|
||||||
|
@ -2,7 +2,7 @@ import { Component, FORM_STYLE, TIP_STYLE } from "../shared.js";
|
|||||||
|
|
||||||
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', 'price-range-min', 'price-range-max'];
|
return ['offer-id', 'description', 'picture-url', 'price-range-min', 'price-range-max', 'free'];
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -16,6 +16,9 @@ customElements.define('offer-form', class extends Component {
|
|||||||
section {
|
section {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
:host([free]) #priceMinSection {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
@media only screen and (min-device-width: 1000px) {
|
@media only screen and (min-device-width: 1000px) {
|
||||||
section > form {
|
section > form {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -52,17 +55,15 @@ customElements.define('offer-form', class extends Component {
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div id="priceSection">
|
<div id="priceSection">
|
||||||
<div>
|
<div id="priceMinSection">
|
||||||
<label>Cena minimalna</label>
|
<label>Cena</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" value="0" />
|
<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>Za darmo</label>
|
||||||
<price-input id="priceMaxUI" value="0"></price-input>
|
<input type="checkbox" id="free" />
|
||||||
<input name="price_max" id="priceMax" type="hidden" value="0" />
|
<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>
|
||||||
@ -80,13 +81,22 @@ customElements.define('offer-form', class extends Component {
|
|||||||
this.shadowRoot.querySelector('#priceMin').value = ev.target.value;
|
this.shadowRoot.querySelector('#priceMin').value = ev.target.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.shadowRoot.querySelector('#priceMaxUI').addEventListener('change', ev => {
|
// this.shadowRoot.querySelector('#priceMaxUI').addEventListener('change', ev => {
|
||||||
ev.stopPropagation();
|
// ev.stopPropagation();
|
||||||
this.shadowRoot.querySelector('#priceMax').value = ev.target.value;
|
// this.shadowRoot.querySelector('#priceMax').value = ev.target.value;
|
||||||
});
|
// });
|
||||||
this.addEventListener('image-input:uploaded', ev => {
|
this.addEventListener('image-input:uploaded', ev => {
|
||||||
this.picture_url = ev.detail;
|
this.picture_url = ev.detail;
|
||||||
});
|
});
|
||||||
|
this.shadowRoot.querySelector('#free').addEventListener('change', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.free = ev.target.checked;
|
||||||
|
if (this.free) {
|
||||||
|
this.price_range_min = 0;
|
||||||
|
this.price_range_max = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get offer_id() {
|
get offer_id() {
|
||||||
@ -120,7 +130,7 @@ customElements.define('offer-form', class extends Component {
|
|||||||
|
|
||||||
set price_range_max(v) {
|
set price_range_max(v) {
|
||||||
this.setAttribute('price-range-max', v);
|
this.setAttribute('price-range-max', v);
|
||||||
this.shadowRoot.querySelector('#priceMaxUI').value = v;
|
// this.shadowRoot.querySelector('#priceMaxUI').value = v;
|
||||||
this.shadowRoot.querySelector('#priceMax').value = v;
|
this.shadowRoot.querySelector('#priceMax').value = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,11 +153,25 @@ customElements.define('offer-form', class extends Component {
|
|||||||
this.shadowRoot.querySelector('image-input').url = v;
|
this.shadowRoot.querySelector('image-input').url = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get free() {
|
||||||
|
return this.getAttribute('free') === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
set free(v) {
|
||||||
|
if (v === true || v === 'true') {
|
||||||
|
this.setAttribute('free', 'true');
|
||||||
|
this.shadowRoot.querySelector('#free').checked = true;
|
||||||
|
} else {
|
||||||
|
this.removeAttribute('free');
|
||||||
|
this.shadowRoot.querySelector('#free').checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#removeId() {
|
#removeId() {
|
||||||
const form = this.shadowRoot.querySelector('form');
|
const form = this.shadowRoot.querySelector('form');
|
||||||
const input = form.querySelector('#offer-id');
|
const input = form.querySelector('#offer-id');
|
||||||
input && input.remove();
|
input && input.remove();
|
||||||
form.action = '/marketplace/create';
|
form.action = '/offers/create';
|
||||||
form.querySelector('input[type=submit]').value = 'Utwórz';
|
form.querySelector('input[type=submit]').value = 'Utwórz';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +183,7 @@ customElements.define('offer-form', class extends Component {
|
|||||||
input.setAttribute('name', 'id');
|
input.setAttribute('name', 'id');
|
||||||
input.setAttribute('id', 'offer-id');
|
input.setAttribute('id', 'offer-id');
|
||||||
input.value = v;
|
input.value = v;
|
||||||
form.action = '/marketplace/update';
|
form.action = '/offers/update';
|
||||||
form.querySelector('input[type=submit]').value = 'Zmień';
|
form.querySelector('input[type=submit]').value = 'Zmień';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -15,6 +15,11 @@ customElements.define('user-edit-offer', class extends Component {
|
|||||||
:host([mode='view']) #view { display: block; }
|
:host([mode='view']) #view { display: block; }
|
||||||
:host([mode='form']) #form { display: block; }
|
:host([mode='form']) #form { display: block; }
|
||||||
:host([state='Finished']) #finishForm { display: none; }
|
:host([state='Finished']) #finishForm { display: none; }
|
||||||
|
section, a, ::slotted(a) {
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
#actions {
|
#actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -18,12 +18,15 @@ export class RegisterFormComponent extends PseudoForm {
|
|||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
if (form.reportValidity()) {
|
this.shadowRoot.querySelector('form-navigation').next();
|
||||||
this.shadowRoot.querySelector('form-navigation').next();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
this.addEventListener('form:next', () => {
|
this.addEventListener('form:next', ev => {
|
||||||
dispatchForm()
|
if (form.reportValidity()) {
|
||||||
|
dispatchForm()
|
||||||
|
} else {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ customElements.define('register-business-account-form', class extends RegisterFo
|
|||||||
<input id="password" name="pass" placeholder="Hasło" type="password" required />
|
<input id="password" name="pass" placeholder="Hasło" type="password" required />
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" style="display: none">
|
<input type="submit" style="display: none">
|
||||||
<form-navigation></form-navigation>
|
<form-navigation prev="hidden" next="right"></form-navigation>
|
||||||
</form>
|
</form>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
@ -24,6 +24,10 @@ customElements.define('register-business-contacts-form', class extends RegisterF
|
|||||||
</style>
|
</style>
|
||||||
<article>
|
<article>
|
||||||
<h2>Edycja listy danych kontaktowych</h2>
|
<h2>Edycja listy danych kontaktowych</h2>
|
||||||
|
<p>
|
||||||
|
Adres e-mail podany w formularzu będzie domyślnym adresem kontaktowym.
|
||||||
|
Tutaj możesz dodać dodatkowe sposoby kontaktu takie jak numer telefonu lub link do facebook'a.
|
||||||
|
</p>
|
||||||
<form>
|
<form>
|
||||||
<section id="form">
|
<section id="form">
|
||||||
<contact-info-editor
|
<contact-info-editor
|
||||||
|
@ -88,7 +88,7 @@ customElements.define('register-business-submit-form', class extends PseudoForm
|
|||||||
const { error } = await api.register(this.#form);
|
const { error } = await api.register(this.#form);
|
||||||
console.info(error);
|
console.info(error);
|
||||||
|
|
||||||
if (error) {
|
if (!error) {
|
||||||
// Router.goTo("/account?success");
|
// Router.goTo("/account?success");
|
||||||
location.href = '/account?success';
|
location.href = '/account?success';
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,6 +22,9 @@ customElements.define('form-navigation', class extends Component {
|
|||||||
width: calc(50% - 16px);
|
width: calc(50% - 16px);
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
}
|
}
|
||||||
|
:host([next=right]) .actions {
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
@media only screen and (min-device-width: 1000px) {
|
@media only screen and (min-device-width: 1000px) {
|
||||||
form > .actions > input {
|
form > .actions > input {
|
||||||
width: auto;
|
width: auto;
|
||||||
@ -54,11 +57,11 @@ customElements.define('form-navigation', class extends Component {
|
|||||||
if (oldV === newV) return;
|
if (oldV === newV) return;
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'next': {
|
case 'next': {
|
||||||
this.shadowRoot.querySelector('#next').className = newV === 'hidden' ? 'hidden' : '';
|
this.shadowRoot.querySelector('#next').className = newV;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'prev': {
|
case 'prev': {
|
||||||
this.shadowRoot.querySelector('#prev').className = newV === 'hidden' ? 'hidden' : '';
|
this.shadowRoot.querySelector('#prev').className = newV;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,25 @@ customElements.define('search-input', class extends Component {
|
|||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
text-indent: 20px;
|
text-indent: 20px;
|
||||||
}
|
}
|
||||||
|
svg { height: 24px; }
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: start;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<section>
|
<section>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 310.42 310.42"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M273.587 214.965c49.11-49.111 49.11-129.021 0-178.132s-129.021-49.111-178.131 0C53.792 78.497 47.482 140.462 76.509 188.85c0 0 2.085 3.496-.731 6.312l-64.263 64.263c-12.791 12.79-15.837 30.675-4.493 42.02l1.953 1.951c11.343 11.345 29.229 8.301 42.019-4.49l64.128-64.128c2.951-2.951 6.448-.866 6.448-.866 48.387 29.026 110.353 22.717 152.017-18.947zM118.71 191.71c-36.288-36.288-36.287-95.332 0-131.62 36.288-36.287 95.333-36.288 131.62 0 36.288 36.287 36.288 95.332 0 131.62-36.287 36.287-95.331 36.287-131.62 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
<input type="text" id="filter" placeholder="Znajdź (wyrażenia regularne są wspierane)" />
|
<input type="text" id="filter" placeholder="Znajdź (wyrażenia regularne są wspierane)" />
|
||||||
</section>
|
</section>
|
||||||
`);
|
`);
|
||||||
@ -52,10 +69,8 @@ customElements.define('search-input', class extends Component {
|
|||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
let node = this;
|
let node = this;
|
||||||
while (node) {
|
while (node) {
|
||||||
console.warn(node)
|
|
||||||
if (node == null) return console.warn('no parent node', node);
|
if (node == null) return console.warn('no parent node', node);
|
||||||
if (node instanceof ShadowRoot) {
|
if (node instanceof ShadowRoot) {
|
||||||
console.warn('node is shadow')
|
|
||||||
this.#host = node.host;
|
this.#host = node.host;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "../layout.html" %}
|
{% extends "../layout.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<ow-admin>
|
<ow-admin>
|
||||||
<ow-offers>
|
<ow-admin-offers>
|
||||||
<h1>Admin - Sprzedaż niepotrzebnych rzeczy</h1>
|
<h1>Admin - Sprzedaż niepotrzebnych rzeczy</h1>
|
||||||
|
|
||||||
{% for offer in offers %}
|
{% for offer in offers %}
|
||||||
@ -27,6 +27,6 @@
|
|||||||
></marketplace-offer>
|
></marketplace-offer>
|
||||||
</admin-edit-offer>
|
</admin-edit-offer>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ow-offers>
|
</ow-admin-offers>
|
||||||
</ow-admin>
|
</ow-admin>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -4,14 +4,12 @@
|
|||||||
<local-business-list {{h.account_id_tag(account)}}>
|
<local-business-list {{h.account_id_tag(account)}}>
|
||||||
{% for business in businesses %}
|
{% for business in businesses %}
|
||||||
<local-business
|
<local-business
|
||||||
slot="business"
|
slot="business"
|
||||||
business-id="{{business.id}}"
|
business-id="{{business.id}}"
|
||||||
name="{{business.name}}"
|
name="{{business.name}}"
|
||||||
state="{{business.state.as_str()}}"
|
state="{{business.state.as_str()}}"
|
||||||
>
|
>
|
||||||
{% for line in business.description.lines() %}
|
<business-description slot="description" truncate="100">{{business.description}}</business-description>
|
||||||
<p slot="description">{{line}}</p>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% for item in business.items %}
|
{% for item in business.items %}
|
||||||
<local-business-item
|
<local-business-item
|
||||||
@ -28,7 +26,7 @@
|
|||||||
mode="icon"
|
mode="icon"
|
||||||
contact-id="{{contact.id}}"
|
contact-id="{{contact.id}}"
|
||||||
content="{{h.render_contact(contact.contact_type.as_str(), contact.content.as_str())}}"
|
content="{{h.render_contact(contact.contact_type.as_str(), contact.content.as_str())}}"
|
||||||
contact-type="{{contact.contact_type}}"
|
type="{{contact.contact_type}}"
|
||||||
></contact-info>
|
></contact-info>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</contact-info-list>
|
</contact-info-list>
|
||||||
|
34
server/assets/templates/businesses/show.html
Normal file
34
server/assets/templates/businesses/show.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{% extends "../base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<single-local-business {{h.account_id_tag(account)}}>
|
||||||
|
<local-business
|
||||||
|
business-id="{{business.id}}"
|
||||||
|
name="{{business.name}}"
|
||||||
|
state="{{business.state.as_str()}}"
|
||||||
|
>
|
||||||
|
{% for line in business.description.lines() %}
|
||||||
|
<p slot="description">{{line}}</p>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for item in business.items %}
|
||||||
|
<local-business-item
|
||||||
|
slot="item"
|
||||||
|
name="{{item.name}}"
|
||||||
|
price="{{item.price}}"
|
||||||
|
picture-url="{{item.picture_url}}"
|
||||||
|
>
|
||||||
|
</local-business-item>
|
||||||
|
{% endfor %}
|
||||||
|
<contact-info-list slot="contacts">
|
||||||
|
{% for contact in business.contacts %}
|
||||||
|
<contact-info
|
||||||
|
mode="icon"
|
||||||
|
contact-id="{{contact.id}}"
|
||||||
|
content="{{h.render_contact(contact.contact_type.as_str(), contact.content.as_str())}}"
|
||||||
|
type="{{contact.contact_type}}"
|
||||||
|
></contact-info>
|
||||||
|
{% endfor %}
|
||||||
|
</contact-info-list>
|
||||||
|
</local-business>
|
||||||
|
</single-local-business>
|
||||||
|
{% endblock %}
|
@ -1,8 +1,9 @@
|
|||||||
{% extends "../base.html" %}
|
{% extends "../base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<marketplace-offers {{h.account_id_tag(account)}}>
|
<article {{h.account_id_tag(account)}}>
|
||||||
<h2>Edycja oferty</h2>
|
<h2>Edycja oferty</h2>
|
||||||
<offer-form
|
<offer-form
|
||||||
|
{{h.account_id_tag(account)}}
|
||||||
state="{{offer.state.as_str()}}"
|
state="{{offer.state.as_str()}}"
|
||||||
offer-id="{{offer.id}}"
|
offer-id="{{offer.id}}"
|
||||||
description="{{offer.description}}"
|
description="{{offer.description}}"
|
||||||
@ -11,6 +12,7 @@
|
|||||||
{% when PriceRange::Free %}
|
{% when PriceRange::Free %}
|
||||||
price-range-min="0"
|
price-range-min="0"
|
||||||
price-range-max="0"
|
price-range-max="0"
|
||||||
|
free="true"
|
||||||
{% when PriceRange::Fixed with { value } %}
|
{% when PriceRange::Fixed with { value } %}
|
||||||
price-range-min="{{value}}"
|
price-range-min="{{value}}"
|
||||||
price-range-max="0"
|
price-range-max="0"
|
||||||
@ -19,5 +21,5 @@
|
|||||||
price-range-max="{{max}}"
|
price-range-max="{{max}}"
|
||||||
{% endmatch %}
|
{% endmatch %}
|
||||||
></offer-form>
|
></offer-form>
|
||||||
</marketplace-offers>
|
</article>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -22,17 +22,50 @@
|
|||||||
price-range-max="{{max}}"
|
price-range-max="{{max}}"
|
||||||
{% endmatch %}
|
{% endmatch %}
|
||||||
>
|
>
|
||||||
|
<a href="/marketplace/{{offer.id}}">
|
||||||
|
<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 %}
|
||||||
|
>
|
||||||
|
<contact-info-list slot="contacts">
|
||||||
|
{% for contact in offer.contacts %}
|
||||||
|
<contact-info
|
||||||
|
mode="icon"
|
||||||
|
contact-id="{{contact.id}}"
|
||||||
|
type="{{contact.contact_type}}"
|
||||||
|
content="{{h.render_contact(contact.contact_type.as_str(), contact.content.as_str())}}"
|
||||||
|
></contact-info>
|
||||||
|
{% endfor %}
|
||||||
|
</contact-info-list>
|
||||||
|
</marketplace-offer>
|
||||||
|
</a>
|
||||||
|
</user-edit-offer>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for offer in offers %}
|
||||||
|
<a href="/marketplace/{{offer.id}}" slot="offer">
|
||||||
<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-min="0"
|
price-range="free"
|
||||||
price-range-max="0"
|
|
||||||
{% when PriceRange::Fixed with { value } %}
|
{% when PriceRange::Fixed with { value } %}
|
||||||
price-range-min="{{value}}"
|
price-range="{{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}}"
|
||||||
@ -49,36 +82,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</contact-info-list>
|
</contact-info-list>
|
||||||
</marketplace-offer>
|
</marketplace-offer>
|
||||||
</user-edit-offer>
|
</a>
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% for offer in offers %}
|
|
||||||
<marketplace-offer
|
|
||||||
slot="offer"
|
|
||||||
offer-id="{{offer.id}}"
|
|
||||||
description="{{offer.description}}"
|
|
||||||
picture-url="{{offer.picture_url}}"
|
|
||||||
{% match offer.price_range %}
|
|
||||||
{% when PriceRange::Free %}
|
|
||||||
price-range="free"
|
|
||||||
{% when PriceRange::Fixed with { value } %}
|
|
||||||
price-range="{{value}}"
|
|
||||||
{% when PriceRange::Range with { min, max } %}
|
|
||||||
price-range-min="{{min}}"
|
|
||||||
price-range-max="{{max}}"
|
|
||||||
{% endmatch %}
|
|
||||||
>
|
|
||||||
<contact-info-list slot="contacts">
|
|
||||||
{% for contact in offer.contacts %}
|
|
||||||
<contact-info
|
|
||||||
mode="icon"
|
|
||||||
contact-id="{{contact.id}}"
|
|
||||||
type="{{contact.contact_type}}"
|
|
||||||
content="{{h.render_contact(contact.contact_type.as_str(), contact.content.as_str())}}"
|
|
||||||
></contact-info>
|
|
||||||
{% endfor %}
|
|
||||||
</contact-info-list>
|
|
||||||
</marketplace-offer>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</marketplace-offers>
|
</marketplace-offers>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
31
server/assets/templates/marketplace/show.html
Normal file
31
server/assets/templates/marketplace/show.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{% extends "../base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<marketplace-offer
|
||||||
|
{{h.account_id_tag(account)}}
|
||||||
|
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 %}
|
||||||
|
>
|
||||||
|
<contact-info-list slot="contacts">
|
||||||
|
{% for contact in offer.contacts %}
|
||||||
|
<contact-info
|
||||||
|
mode="icon"
|
||||||
|
contact-id="{{contact.id}}"
|
||||||
|
type="{{contact.contact_type}}"
|
||||||
|
content="{{h.render_contact(contact.contact_type.as_str(), contact.content.as_str())}}"
|
||||||
|
></contact-info>
|
||||||
|
{% endfor %}
|
||||||
|
</contact-info-list>
|
||||||
|
</marketplace-offer>
|
||||||
|
{% endblock %}
|
@ -22,20 +22,20 @@ pub enum AccountType {
|
|||||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, Type)]
|
||||||
pub enum LocalBusinessState {
|
pub enum LocalBusinessState {
|
||||||
#[default]
|
#[default]
|
||||||
Pending,
|
Pending = 1,
|
||||||
Approved,
|
Approved = 2,
|
||||||
Banned,
|
Banned = 3,
|
||||||
Pinned,
|
Pinned = 4,
|
||||||
Internal,
|
Internal = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize, Type)]
|
||||||
pub enum OfferState {
|
pub enum OfferState {
|
||||||
#[default]
|
#[default]
|
||||||
Pending,
|
Pending = 0,
|
||||||
Approved,
|
Approved = 1,
|
||||||
Banned,
|
Banned = 2,
|
||||||
Finished,
|
Finished = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OfferState {
|
impl OfferState {
|
||||||
@ -288,6 +288,7 @@ pub struct Offer {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CreateLocalBusinessItemInput {
|
pub struct CreateLocalBusinessItemInput {
|
||||||
|
pub account_id: i32,
|
||||||
pub local_business_id: i32,
|
pub local_business_id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub price: i64,
|
pub price: i64,
|
||||||
|
@ -22,6 +22,7 @@ pub enum Page {
|
|||||||
AdminOffers,
|
AdminOffers,
|
||||||
Terms,
|
Terms,
|
||||||
Privacy,
|
Privacy,
|
||||||
|
Business,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
@ -106,7 +107,7 @@ pub struct SetStateBusinessInput {
|
|||||||
pub state: db::LocalBusinessState,
|
pub state: db::LocalBusinessState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct LocalBusiness {
|
pub struct LocalBusiness {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub owner_id: i32,
|
pub owner_id: i32,
|
||||||
|
@ -88,9 +88,9 @@ pub async fn visible_businesses(t: &mut T<'_>) -> Result<Vec<db::LocalBusiness>>
|
|||||||
r#"
|
r#"
|
||||||
SELECT id, owner_id, name, description, state
|
SELECT id, owner_id, name, description, state
|
||||||
FROM local_businesses
|
FROM local_businesses
|
||||||
WHERE state != 'Banned'
|
WHERE state != 'Banned' AND state != 'Pending'
|
||||||
GROUP BY id, state
|
GROUP BY id, state
|
||||||
ORDER BY id DESC
|
ORDER BY name ASC
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.fetch_all(t)
|
.fetch_all(t)
|
||||||
@ -126,6 +126,25 @@ ORDER BY id DESC
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn business_by_id(t: &mut T<'_>, id: i32) -> Result<db::LocalBusiness> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT id, owner_id, name, description, state
|
||||||
|
FROM local_businesses
|
||||||
|
WHERE state != 'Banned' AND id = $1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::Business { id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn update_business(
|
pub async fn update_business(
|
||||||
t: &mut T<'_>,
|
t: &mut T<'_>,
|
||||||
|
@ -14,6 +14,7 @@ SELECT
|
|||||||
content
|
content
|
||||||
FROM
|
FROM
|
||||||
contacts
|
contacts
|
||||||
|
ORDER BY contact_type, id ASC
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.fetch_all(t)
|
.fetch_all(t)
|
||||||
|
@ -122,8 +122,92 @@ pub async fn move_item(
|
|||||||
item_id: i32,
|
item_id: i32,
|
||||||
item_order: i32,
|
item_order: i32,
|
||||||
) -> Result<db::LocalBusinessItem> {
|
) -> Result<db::LocalBusinessItem> {
|
||||||
let mut current = item_by_id(t, account_id, item_id).await?;
|
dbg!(item_id, item_order);
|
||||||
|
|
||||||
|
let _idx = reorder_items(t, account_id).await;
|
||||||
|
|
||||||
|
let mut current = item_by_id(t, account_id, item_id).await?;
|
||||||
|
dbg!(¤t);
|
||||||
|
|
||||||
|
match item_order.cmp(¤t.item_order) {
|
||||||
|
Ordering::Less => {
|
||||||
|
if let Some(prev_idx) = current.item_order.checked_sub(1) {
|
||||||
|
dbg!(prev_idx);
|
||||||
|
let prev = find_by_item_order(t, account_id, prev_idx).await?;
|
||||||
|
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) = current.item_order.checked_add(1) {
|
||||||
|
dbg!(next_idx);
|
||||||
|
let next = find_by_item_order(t, account_id, next_idx).await?;
|
||||||
|
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 find_by_item_order(
|
||||||
|
t: &mut T<'_>,
|
||||||
|
account_id: i32,
|
||||||
|
item_order: 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.item_order = $1 AND owner_id = $2
|
||||||
|
ORDER BY item_order ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(item_order)
|
||||||
|
.bind(account_id)
|
||||||
|
.fetch_one(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{e}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::AllItems
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn reorder_items(t: &mut T<'_>, account_id: i32) -> i32 {
|
||||||
let all: Vec<db::LocalBusinessItem> = sqlx::query_as(
|
let all: Vec<db::LocalBusinessItem> = sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
@ -143,63 +227,28 @@ ORDER BY item_order ASC
|
|||||||
.bind(account_id)
|
.bind(account_id)
|
||||||
.fetch_all(&mut *t)
|
.fetch_all(&mut *t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.unwrap_or_default();
|
||||||
error!("{e}");
|
|
||||||
dbg!(e);
|
|
||||||
Error::Item { item_id }
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let idx = all
|
if all.is_empty() {
|
||||||
.iter()
|
return 0;
|
||||||
.position(|p| p.id == item_id)
|
}
|
||||||
.ok_or(Error::Item { item_id })?;
|
let mut sql = String::from(
|
||||||
|
r#"
|
||||||
dbg!(idx);
|
UPDATE local_business_items AS a
|
||||||
|
SET item_order = b.item_order
|
||||||
match item_order.cmp(¤t.item_order) {
|
FROM (VALUES "#,
|
||||||
Ordering::Less => {
|
);
|
||||||
if let Some(prev) = idx.checked_sub(1).and_then(|prev_idx| {
|
all.iter().enumerate().fold(&mut sql, |memo, (idx, row)| {
|
||||||
dbg!(prev_idx);
|
if idx != 0 {
|
||||||
all.get(prev_idx)
|
memo.push_str(", ");
|
||||||
}) {
|
|
||||||
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 => {
|
memo.push_str(format!("({}, {})", row.id, idx).as_str());
|
||||||
dbg!("Equal, skipping...");
|
memo
|
||||||
}
|
});
|
||||||
Ordering::Greater => {
|
sql.push_str(") AS b (id, item_order) WHERE a.id = b.id");
|
||||||
if let Some(next) = idx.checked_add(1).and_then(|next_idx| {
|
dbg!(&sql);
|
||||||
dbg!(next_idx);
|
sqlx::query(&sql).execute(&mut *t).await.ok();
|
||||||
all.get(next_idx)
|
all.len() as i32
|
||||||
}) {
|
|
||||||
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]
|
#[tracing::instrument]
|
||||||
@ -284,6 +333,7 @@ pub async fn create_item(
|
|||||||
t: &mut T<'_>,
|
t: &mut T<'_>,
|
||||||
input: db::CreateLocalBusinessItemInput,
|
input: db::CreateLocalBusinessItemInput,
|
||||||
) -> Result<db::LocalBusinessItem> {
|
) -> Result<db::LocalBusinessItem> {
|
||||||
|
let item_order = reorder_items(t, input.account_id).await;
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO local_business_items (local_business_id, name, price, picture_url, item_order)
|
INSERT INTO local_business_items (local_business_id, name, price, picture_url, item_order)
|
||||||
@ -307,7 +357,7 @@ RETURNING
|
|||||||
.map(String::from)
|
.map(String::from)
|
||||||
.unwrap_or_else(|| format!("--{}", uuid::Uuid::new_v4())),
|
.unwrap_or_else(|| format!("--{}", uuid::Uuid::new_v4())),
|
||||||
)
|
)
|
||||||
.bind(input.item_order)
|
.bind(item_order)
|
||||||
.fetch_one(t)
|
.fetch_one(t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -52,6 +52,9 @@ pub enum Error {
|
|||||||
OwnedBusiness {
|
OwnedBusiness {
|
||||||
account_id: i32,
|
account_id: i32,
|
||||||
},
|
},
|
||||||
|
Business {
|
||||||
|
id: i32,
|
||||||
|
},
|
||||||
AccountByEmail {
|
AccountByEmail {
|
||||||
email: String,
|
email: String,
|
||||||
},
|
},
|
||||||
|
@ -57,9 +57,14 @@ ORDER BY id DESC
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn offer_by_id(t: &mut T<'_>, account_id: i32, offer_id: i32) -> Result<db::Offer> {
|
pub async fn offer_by_id(
|
||||||
sqlx::query_as(
|
t: &mut T<'_>,
|
||||||
r#"
|
account_id: Option<i32>,
|
||||||
|
offer_id: i32,
|
||||||
|
) -> Result<db::Offer> {
|
||||||
|
match account_id {
|
||||||
|
Some(account_id) => sqlx::query_as(
|
||||||
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
owner_id,
|
owner_id,
|
||||||
@ -72,9 +77,26 @@ FROM offers
|
|||||||
WHERE owner_id = $1 AND id = $2
|
WHERE owner_id = $1 AND id = $2
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(account_id)
|
.bind(account_id)
|
||||||
.bind(offer_id)
|
.bind(offer_id),
|
||||||
|
None => sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
owner_id,
|
||||||
|
price_range,
|
||||||
|
description,
|
||||||
|
picture_url,
|
||||||
|
state,
|
||||||
|
created_at
|
||||||
|
FROM offers
|
||||||
|
WHERE id = $1
|
||||||
|
LIMIT 1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(offer_id),
|
||||||
|
}
|
||||||
.fetch_one(t)
|
.fetch_one(t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -314,9 +314,15 @@ impl Responder for HttpResult {
|
|||||||
let status_code = self.status_code();
|
let status_code = self.status_code();
|
||||||
match self {
|
match self {
|
||||||
HttpResult::Json { body, code } => HttpResponse::build(code)
|
HttpResult::Json { body, code } => HttpResponse::build(code)
|
||||||
|
.append_header(("Sec-GPC-field-name", "Sec-GPC"))
|
||||||
|
.append_header(("Sec-GPC-field-value", "1"))
|
||||||
|
.append_header(("Sec-GPC", "1"))
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(body),
|
.body(body),
|
||||||
HttpResult::Html { body, code } => HttpResponse::build(code)
|
HttpResult::Html { body, code } => HttpResponse::build(code)
|
||||||
|
.append_header(("Sec-GPC-field-name", "Sec-GPC"))
|
||||||
|
.append_header(("Sec-GPC-field-value", "1"))
|
||||||
|
.append_header(("Sec-GPC", "1"))
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
.body(body),
|
.body(body),
|
||||||
HttpResult::Err {
|
HttpResult::Err {
|
||||||
|
@ -28,7 +28,8 @@ async fn admin_offers(req: HttpRequest, db: Data<PgPool>, id: Identity) -> HttpR
|
|||||||
let mut t = ok_or_internal!(&req, pool.begin().await);
|
let mut t = ok_or_internal!(&req, pool.begin().await);
|
||||||
let account = require_admin!(&req, &mut t, id);
|
let account = require_admin!(&req, &mut t, id);
|
||||||
|
|
||||||
let offers = queries::all_offers(&mut t).await.unwrap_or_default();
|
let mut offers = queries::all_offers(&mut t).await.unwrap_or_default();
|
||||||
|
offers.sort_by(|a, b| (a.state as u8).cmp(&(b.state as u8)));
|
||||||
|
|
||||||
t.commit().await.ok();
|
t.commit().await.ok();
|
||||||
|
|
||||||
|
@ -155,6 +155,7 @@ async fn new_business_item(
|
|||||||
if let Err(e) = queries::create_item(
|
if let Err(e) = queries::create_item(
|
||||||
&mut t,
|
&mut t,
|
||||||
db::CreateLocalBusinessItemInput {
|
db::CreateLocalBusinessItemInput {
|
||||||
|
account_id: account.id,
|
||||||
local_business_id: business.id,
|
local_business_id: business.id,
|
||||||
name: form.name,
|
name: form.name,
|
||||||
price: form.price,
|
price: form.price,
|
||||||
|
@ -37,6 +37,13 @@ pub async fn render_index() -> HttpResponse {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/.well-known/gpc.json")]
|
||||||
|
async fn gpc() -> HttpResponse {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.content_type("application/json")
|
||||||
|
.body("{\"gpc\":true,\"lastUpdate\":\"1997-03-10\"}")
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn index(req: HttpRequest) -> HttpResult {
|
async fn index(req: HttpRequest) -> HttpResult {
|
||||||
HttpResult::goto(&req, "/marketplace")
|
HttpResult::goto(&req, "/marketplace")
|
||||||
@ -62,5 +69,6 @@ pub fn configure(config: &mut ServiceConfig) {
|
|||||||
.prefer_utf8(true)
|
.prefer_utf8(true)
|
||||||
.show_files_listing(),
|
.show_files_listing(),
|
||||||
)
|
)
|
||||||
.service(index);
|
.service(index)
|
||||||
|
.service(gpc);
|
||||||
}
|
}
|
||||||
|
@ -417,6 +417,7 @@ async fn save_account_details(
|
|||||||
let res = queries::create_item(
|
let res = queries::create_item(
|
||||||
&mut t,
|
&mut t,
|
||||||
db::CreateLocalBusinessItemInput {
|
db::CreateLocalBusinessItemInput {
|
||||||
|
account_id: account.id,
|
||||||
local_business_id: business.id,
|
local_business_id: business.id,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
price: item.price as i64,
|
price: item.price as i64,
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
use actix_http::StatusCode;
|
use actix_http::StatusCode;
|
||||||
use actix_web::web::{Data, ServiceConfig};
|
use actix_web::web::{Data, ServiceConfig};
|
||||||
use actix_web::{get, HttpRequest};
|
use actix_web::{get, web, HttpRequest};
|
||||||
|
use askama::Template;
|
||||||
|
use serde::Serialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::model::view;
|
|
||||||
use crate::model::view::Page;
|
use crate::model::view::Page;
|
||||||
|
use crate::model::{db, view};
|
||||||
use crate::queries;
|
use crate::queries;
|
||||||
use crate::routes::unrestricted::IndexTemplate;
|
use crate::routes::unrestricted::IndexTemplate;
|
||||||
use crate::routes::{HttpResult, Identity};
|
use crate::routes::{HttpResult, Identity};
|
||||||
|
use crate::view::Helper;
|
||||||
|
|
||||||
#[get("/local-businesses")]
|
#[get("")]
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn businesses_page(req: HttpRequest, db: Data<PgPool>, id: Identity) -> HttpResult {
|
pub async fn businesses_page(req: HttpRequest, db: Data<PgPool>, id: Identity) -> HttpResult {
|
||||||
let pool = db.into_inner();
|
let pool = db.into_inner();
|
||||||
@ -51,6 +54,78 @@ pub async fn businesses_page(req: HttpRequest, db: Data<PgPool>, id: Identity) -
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configure(config: &mut ServiceConfig) {
|
#[derive(Default, Debug, Serialize, Template)]
|
||||||
config.service(businesses_page);
|
#[template(path = "businesses/show.html")]
|
||||||
|
pub struct ShowTemplate {
|
||||||
|
business: view::LocalBusiness,
|
||||||
|
account: Option<db::Account>,
|
||||||
|
error: Option<String>,
|
||||||
|
page: Page,
|
||||||
|
h: Helper,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/{id}")]
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn show_business_page(
|
||||||
|
req: HttpRequest,
|
||||||
|
db: Data<PgPool>,
|
||||||
|
id: Identity,
|
||||||
|
path: web::Path<(i32,)>,
|
||||||
|
) -> HttpResult {
|
||||||
|
let business_id = path.into_inner().0;
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = crate::ok_or_internal!(&req, pool.begin().await);
|
||||||
|
let account = match id.identity() {
|
||||||
|
Some(id) => queries::account_by_id(&mut t, id).await,
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let (business, mut items, mut contacts) = {
|
||||||
|
use crate::model::db::{LocalBusiness, LocalBusinessItem};
|
||||||
|
let business: LocalBusiness = match queries::business_by_id(&mut t, business_id).await {
|
||||||
|
Ok(business) => business,
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
return HttpResult::res(
|
||||||
|
&req,
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
ShowTemplate {
|
||||||
|
account,
|
||||||
|
page: Page::Business,
|
||||||
|
error: Some("Page not found".into()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let items: Vec<LocalBusinessItem> = queries::visible_business_items(&mut t)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let contacts = queries::all_contacts(&mut t).await.unwrap_or_default();
|
||||||
|
(business, items, contacts)
|
||||||
|
};
|
||||||
|
|
||||||
|
let business = view::LocalBusiness::from((business, &mut items, &mut contacts));
|
||||||
|
|
||||||
|
t.commit().await.ok();
|
||||||
|
|
||||||
|
HttpResult::res(
|
||||||
|
&req,
|
||||||
|
StatusCode::OK,
|
||||||
|
ShowTemplate {
|
||||||
|
business,
|
||||||
|
account,
|
||||||
|
page: Page::LocalBusinesses,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure(config: &mut ServiceConfig) {
|
||||||
|
config.service(
|
||||||
|
web::scope("/local-businesses")
|
||||||
|
.service(show_business_page)
|
||||||
|
.service(businesses_page),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ async fn edit_marketplace(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let offer = match queries::offer_by_id(&mut t, account.id, offer_id).await {
|
let offer = match queries::offer_by_id(&mut t, Some(account.id), offer_id).await {
|
||||||
Ok(offer) => offer,
|
Ok(offer) => offer,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
dbg!(e);
|
dbg!(e);
|
||||||
@ -145,10 +145,83 @@ async fn edit_marketplace(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Serialize, Template)]
|
||||||
|
#[template(path = "./marketplace/show.html")]
|
||||||
|
struct ShowOfferTemplate {
|
||||||
|
account: Option<db::Account>,
|
||||||
|
error: Option<String>,
|
||||||
|
page: Page,
|
||||||
|
#[serde(skip)]
|
||||||
|
h: Helper,
|
||||||
|
offer: view::Offer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/{id}")]
|
||||||
|
async fn show_marketplace(
|
||||||
|
req: HttpRequest,
|
||||||
|
db: Data<PgPool>,
|
||||||
|
id: Identity,
|
||||||
|
path: web::Path<(i32,)>,
|
||||||
|
) -> HttpResult {
|
||||||
|
let offer_id = path.into_inner().0;
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let mut t = crate::ok_or_internal!(&req, pool.begin().await);
|
||||||
|
let account = match id.identity() {
|
||||||
|
Some(id) => queries::account_by_id(&mut t, id).await,
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let account = match account {
|
||||||
|
Some(a) => a,
|
||||||
|
_ => {
|
||||||
|
return HttpResult::res(
|
||||||
|
&req,
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
ShowOfferTemplate {
|
||||||
|
page: Page::Marketplace,
|
||||||
|
account,
|
||||||
|
error: Some("Oferta nie istnieje".into()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let offer = match queries::offer_by_id(&mut t, None, offer_id).await {
|
||||||
|
Ok(offer) => offer,
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
return HttpResult::res(
|
||||||
|
&req,
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
ShowOfferTemplate {
|
||||||
|
page: Page::Marketplace,
|
||||||
|
account: Some(account),
|
||||||
|
error: Some("Oferta nie istnieje".into()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
t.commit().await.ok();
|
||||||
|
|
||||||
|
HttpResult::res(
|
||||||
|
&req,
|
||||||
|
StatusCode::OK,
|
||||||
|
ShowOfferTemplate {
|
||||||
|
page: Page::Marketplace,
|
||||||
|
account: Some(account),
|
||||||
|
offer: view::Offer::from((offer, &vec![])),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn configure(config: &mut ServiceConfig) {
|
pub fn configure(config: &mut ServiceConfig) {
|
||||||
config.service(
|
config.service(
|
||||||
web::scope("/marketplace")
|
web::scope("/marketplace")
|
||||||
.service(edit_marketplace)
|
.service(edit_marketplace)
|
||||||
|
.service(show_marketplace)
|
||||||
.service(marketplace),
|
.service(marketplace),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,41 +3,5 @@ name = "web"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
name = "web"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
sycamore = "0.8.2"
|
||||||
[dependencies.serde]
|
|
||||||
version = "1.0.140"
|
|
||||||
features = ['derive']
|
|
||||||
|
|
||||||
[dependencies.stdweb]
|
|
||||||
version = "0.4.20"
|
|
||||||
features = []
|
|
||||||
|
|
||||||
[dependencies.stdweb-derive]
|
|
||||||
version = "0.5.3"
|
|
||||||
features = []
|
|
||||||
|
|
||||||
[dependencies.stdweb-logger]
|
|
||||||
version = "0.1.1"
|
|
||||||
features = []
|
|
||||||
|
|
||||||
#[dependencies.wasm-bindgen]
|
|
||||||
#version = "0.2.81"
|
|
||||||
#features = []
|
|
||||||
#
|
|
||||||
#[dependencies.web-sys]
|
|
||||||
#version = "0.3.58"
|
|
||||||
#features = ['HtmlElement']
|
|
||||||
#
|
|
||||||
#[dependencies.js-sys]
|
|
||||||
#version = "0.3.58"
|
|
||||||
#features = []
|
|
||||||
|
|
||||||
[dependencies.wee_alloc]
|
|
||||||
version = "*"
|
|
||||||
features = ["static_array_backend"]
|
|
||||||
|
8
web/Trunk.toml
Normal file
8
web/Trunk.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[build]
|
||||||
|
target = "index.html"
|
||||||
|
dist = "dist"
|
||||||
|
|
||||||
|
[[hooks]]
|
||||||
|
stage = "build"
|
||||||
|
command = "zsh"
|
||||||
|
command_arguments = ["-c", "tailwindcss -i assets/tailwind.css -o $TRUNK_STAGING_DIR/tailwind.css"]
|
3
web/assets/tailwind.css
Normal file
3
web/assets/tailwind.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
9
web/index.html
Normal file
9
web/index.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>My first Sycamore app</title>
|
||||||
|
<link rel="stylesheet" href="/tailwind.css"/>
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
5
web/package.json
Normal file
5
web/package.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"tailwind": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
1389
web/pnpm-lock.yaml
Normal file
1389
web/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,65 +0,0 @@
|
|||||||
use wasm_bindgen::prelude::*;
|
|
||||||
use web_sys::HtmlElement;
|
|
||||||
|
|
||||||
#[global_allocator]
|
|
||||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
|
||||||
|
|
||||||
pub trait Component {
|
|
||||||
fn tag_name() -> String;
|
|
||||||
|
|
||||||
fn created(&mut self) {}
|
|
||||||
|
|
||||||
fn connected_callback(&self) {}
|
|
||||||
|
|
||||||
fn disconnected_callback(&mut self) {}
|
|
||||||
|
|
||||||
fn attribute_changed(
|
|
||||||
&mut self,
|
|
||||||
_name: String,
|
|
||||||
_old_value: Option<String>,
|
|
||||||
_new_value: Option<String>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(extends = web_sys::HtmlElement, extends = web_sys::Object)]
|
|
||||||
pub struct FooElement {
|
|
||||||
el: HtmlElement,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
impl FooElement {
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn tag_name() -> String {
|
|
||||||
"foo-element".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
pub fn new(el: HtmlElement) -> Self {
|
|
||||||
Self { el }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = connectedCallback)]
|
|
||||||
pub fn connected_callback(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(extends = web_sys::HtmlElement, extends = web_sys::Object)]
|
|
||||||
pub struct BarElement {
|
|
||||||
el: HtmlElement,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
impl BarElement {
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn tag_name() -> String {
|
|
||||||
"bar-element".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
pub fn new(el: HtmlElement) -> Self {
|
|
||||||
Self { el }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = connectedCallback)]
|
|
||||||
pub fn connected_callback(&self) {}
|
|
||||||
}
|
|
63
web/src/components/card.rs
Normal file
63
web/src/components/card.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Card<G: Html>(cx: Scope) -> View<G> {
|
||||||
|
view! {
|
||||||
|
cx,
|
||||||
|
div(class = "px-4 relative w-full md:w-4/12") {
|
||||||
|
div(class = "mb-6 text-center shadow-lg rounded-lg relative flex flex-col bg-white p-6 w-full mb-6") {
|
||||||
|
div(class = "flex items-center justify-between pb-4 pt-2 border-b border-blueGray-300") {
|
||||||
|
div(class="float-left ml-1") {
|
||||||
|
i(class="opacity-75 inline mr-2 fas fa-shopping-basket undefined") {}
|
||||||
|
p(class="inline text-lg") { "Work in Progress" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div(class = "py-6 flex-auto") {
|
||||||
|
div(class = "shadow-lg mt-6 rounded-full my-6 mx-auto w-100-px p-6 bg-white") {
|
||||||
|
img(class = "mx-auto", src = "data:image/png;base64,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") {}
|
||||||
|
}
|
||||||
|
h4(class = "text-2xl font-bold leading-tight mt-0 mb-2") { "Slack" }
|
||||||
|
p(class="text-blueGray-500 px-8") {"We are more than happy to work at such a great project." }
|
||||||
|
div(class = "flex justify-center mt-8 mb-2 text-blueGray-400") {
|
||||||
|
//
|
||||||
|
div(class = "flex items-center") {
|
||||||
|
a(class = "text-white bg-blueGray-500 inline-flex items-center justify-center shadow-lg rounded rounded-full relative border-2 border-white -ml-4 hover:z-1 w-10 h-10") {
|
||||||
|
img(class="rounded-full w-full", src="https://demos.creative-tim.com/notus-pro-react/static/media/team-1.26905a67.jpg") {}
|
||||||
|
div(class = "hidden") {
|
||||||
|
div(class = "border-0 mb-3 block z-50 font-normal leading-normal text-sm text-left no-underline break-words rounded") {
|
||||||
|
div(class = "py-1 px-2 text-center rounded text-white bg-black") { "Slack" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a(class = "text-white bg-blueGray-500 inline-flex items-center justify-center shadow-lg rounded rounded-full relative border-2 border-white -ml-4 hover:z-1 w-10 h-10") {
|
||||||
|
img(class="rounded-full w-full", src="https://demos.creative-tim.com/notus-pro-react/static/media/face-2.33e80fee.jpg") {}
|
||||||
|
div(class = "hidden") {
|
||||||
|
div(class = "border-0 mb-3 block z-50 font-normal leading-normal text-sm text-left no-underline break-words rounded") {
|
||||||
|
div(class = "py-1 px-2 text-center rounded text-white bg-black") { "Slack" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a(class = "text-white bg-blueGray-500 inline-flex items-center justify-center shadow-lg rounded rounded-full relative border-2 border-white -ml-4 hover:z-1 w-10 h-10") {
|
||||||
|
img(class="rounded-full w-full", src="https://demos.creative-tim.com/notus-pro-react/static/media/team-3.c5d0c11c.jpg") {}
|
||||||
|
div(class = "hidden") {
|
||||||
|
div(class = "border-0 mb-3 block z-50 font-normal leading-normal text-sm text-left no-underline break-words rounded") {
|
||||||
|
div(class = "py-1 px-2 text-center rounded text-white bg-black") { "Slack" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a(class = "text-white bg-blueGray-500 inline-flex items-center justify-center shadow-lg rounded rounded-full relative border-2 border-white -ml-4 hover:z-1 w-10 h-10") {
|
||||||
|
img(class="rounded-full w-full", src="https://demos.creative-tim.com/notus-pro-react/static/media/team-4.639c2559.jpg") {}
|
||||||
|
div(class = "hidden") {
|
||||||
|
div(class = "border-0 mb-3 block z-50 font-normal leading-normal text-sm text-left no-underline break-words rounded") {
|
||||||
|
div(class = "py-1 px-2 text-center rounded text-white bg-black") { "Slack" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
small(class="pl-2 font-bold mb-1") {"and 30+ more"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
web/src/components/mod.rs
Normal file
3
web/src/components/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mod card;
|
||||||
|
|
||||||
|
pub use card::*;
|
@ -1 +0,0 @@
|
|||||||
|
|
20
web/src/main.rs
Normal file
20
web/src/main.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
mod components;
|
||||||
|
|
||||||
|
use components::Card;
|
||||||
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
sycamore::render(|cx| {
|
||||||
|
view! { cx,
|
||||||
|
div(class = "mb-12 flex flex-wrap -mx-4") {
|
||||||
|
Card {}
|
||||||
|
Card {}
|
||||||
|
Card {}
|
||||||
|
Card {}
|
||||||
|
Card {}
|
||||||
|
Card {}
|
||||||
|
Card {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
12
web/tailwind.config.js
Normal file
12
web/tailwind.config.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./src/**/*.rs",
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.html",
|
||||||
|
"./src/**/*.css",
|
||||||
|
],
|
||||||
|
theme: {},
|
||||||
|
variants: {},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user