UI tuning

This commit is contained in:
Adrian Woźniak 2022-07-29 16:08:59 +02:00
parent a9669432ec
commit f91b4fb4f9
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
50 changed files with 936 additions and 534 deletions

View File

@ -2,6 +2,34 @@
@import 'noto-sans.css'; @import 'noto-sans.css';
@import 'beam-weapon.css'; @import 'beam-weapon.css';
/* latin-ext */
@font-face {
font-family: 'Cardo';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/cardo/v19/wlp_gwjKBV1pqhv23IEp2A.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Cardo';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/cardo/v19/wlp_gwjKBV1pqhv43IE.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Garamond Premier Pro Display';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/assets/fonts/Garamond Premier Pro Display.otf') format('otf');
}
* { * {
--hover-color: #f18902; --hover-color: #f18902;
--border-slim-color: #495057; --border-slim-color: #495057;
@ -9,6 +37,8 @@
--red-color: #9D0208; --red-color: #9D0208;
--orange-color: #E85D04; --orange-color: #E85D04;
--blue-color: #023e8a; --blue-color: #023e8a;
--ast-border-color: #dddddd;
} }
header > h1 { header > h1 {
@ -16,23 +46,6 @@ header > h1 {
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
header > h1 {
font-family: 'Beam Weapon', sans-serif;
font-weight: bold;
text-shadow: 2px 1px 2px #fff, -1px -1px 2px #fff;
display: block;
font-size: 115px;
padding: 0;
text-align: center;
background: no-repeat center url("/assets/images/background.webp");
background: no-repeat center image-set(url("/assets/images/background.webp") 1x, url("/assets/images/background.jpeg") 1x);
border-radius: 4px;
background-size: 1280px 200px;
width: 1280px;
height: 200px;
margin: auto;
}
article { article {
width: 1280px; width: 1280px;
margin: auto auto; margin: auto auto;
@ -69,18 +82,19 @@ article {
} }
* { * {
font-family: 'Noto Sans', sans-serif; font-family: 'Cardo', sans-serif;
line-height: 1.6; line-height: 1.6;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-family: Georgia, "Times New Roman", Times, serif; font-family: 'Cardo', serif;
line-height: 1.2; line-height: 1.2;
} }
h1 { h1 {
font-size: 3.4rem; font-family: "Garamond Premier Pro Display", Sans-serif;
margin-bottom: .8rem; font-size: 39px;
font-weight: 600;
} }
h2 { h2 {
@ -122,11 +136,11 @@ blockquote {
} }
blockquote p { blockquote p {
font: italic 1.2rem/1.6 Georgia, "Times New Roman", Times, serif; font: italic 1.2rem/1.6 'Cardo', serif;
} }
local-businesses local-business p { local-businesses local-business p {
font-family: 'Noto Sans', sans-serif; font-family: 'Cardo', sans-serif;
} }
.error { .error {
@ -162,44 +176,41 @@ ow-nav > ow-path[selected="selected"] > svg {
.btn { .btn {
cursor: pointer; cursor: pointer;
border-radius: 5px;
box-shadow: 0 10px 20px -6px rgba(0, 0, 0, .12);
position: relative; position: relative;
margin-bottom: 20px;
display: inline-block; display: inline-block;
font-weight: 400;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
user-select: none; user-select: none;
font-size: 1rem;
line-height: 1.5;
transition: color .15s ease-in-out,
background-color .15s ease-in-out,
border-color .15s ease-in-out,
box-shadow .15s ease-in-out;
width: auto; width: auto;
height: calc(1.5em + 0.75rem + 2px); height: calc(1.5em + 0.75rem + 2px);
padding: .375rem .75rem; border: 1px solid black;
color: white;
border: 1px solid #495057; background: black;
color: #495057; padding: 10px 20px;
background: white; font-family: "Cardo", Sans-serif;
font-size: 20px;
font-weight: 500;
line-height: 1em;
letter-spacing: 0;
transition: all 0.2s;
} }
a.btn { a.btn {
text-decoration: none; text-decoration: none;
line-height: 2; line-height: 2;
} }
privacy-policy h2 { privacy-policy h2 {
display: block; display: block;
text-align: center; text-align: center;
} }
privacy-policy h4 { privacy-policy h4 {
display: block; display: block;
text-align: center; text-align: center;
margin-bottom: 8px; margin-bottom: 8px;
} }
privacy-policy ul li { privacy-policy ul li {
list-style: circle; list-style: circle;
} }

BIN
assets/fonts/Cardo-Bold.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -55,11 +55,13 @@ customElements.define('business-item-editor', class extends Component {
<article id="list"> <article id="list">
<slot></slot> <slot></slot>
</article> </article>
<article id="form"> <article id="form">
<register-item-form-row remove="hidden" save="show" idx="0"> <register-business-item-form remove="hidden" save="show" idx="0">
<div id="shadow" slot="head">&nbsp;</div> <div id="shadow" slot="head">&nbsp;</div>
</register-item-form-row> </register-business-item-form>
</article> </article>
<article id="new-business-item-form"> <article id="new-business-item-form">
<form id="create-new-item-form" method="post" action="/business-item/new"> <form id="create-new-item-form" method="post" action="/business-item/new">
<input type="hidden" name="name" id="name" /> <input type="hidden" name="name" id="name" />
@ -68,6 +70,7 @@ customElements.define('business-item-editor', class extends Component {
<input type="hidden" name="item_order" id="item_order" /> <input type="hidden" name="item_order" id="item_order" />
</form> </form>
</article> </article>
<form id="moveForm" action="/business-item/move" method="post"> <form id="moveForm" action="/business-item/move" method="post">
<input name="id" type="hidden" /> <input name="id" type="hidden" />
<input name="item_order" type="hidden" /> <input name="item_order" type="hidden" />

View File

@ -19,6 +19,9 @@ customElements.define('business-item', class extends Component {
#move svg { #move svg {
cursor: pointer; cursor: pointer;
} }
form > #fields > div, form > #fields > div > label, form > #fields > div > .input {
width: 90% !important;
}
@media(min-width: 1280px) { @media(min-width: 1280px) {
section > form { section > form {
display: flex; display: flex;
@ -28,9 +31,15 @@ customElements.define('business-item', class extends Component {
width: 25%; width: 25%;
max-width: 25%; max-width: 25%;
} }
form > #fields {
width: 50%;
}
#move { #move {
width: 33px; width: 33px;
} }
form > #fields > div, form > #fields > div > label, form > #fields > div > .input {
width: 90% !important;
}
} }
#move-up, #move-down { #move-up, #move-down {
border: none; border: none;
@ -52,14 +61,18 @@ customElements.define('business-item', class extends Component {
</button> </button>
</div> </div>
<image-input send-original="true"></image-input> <image-input send-original="true"></image-input>
<div id="fields">
<div> <div>
<label>Nazwa</label> <label>Nazwa</label>
<input name="name" id="name" /> <input name="name" id="name" class="input" />
</div> </div>
<div> <div>
<label>Cena</label> <label>Cena</label>
<price-input></price-input> <price-input class="input"></price-input>
</div> </div>
</div>
<div id="actions"> <div id="actions">
<input type="submit" id="save" value="Zapisz" /> <input type="submit" id="save" value="Zapisz" />
<input type="button" id="delete" value="Usuń" /> <input type="button" id="delete" value="Usuń" />

View File

@ -8,9 +8,8 @@ customElements.define('contact-info-editor', class extends Component {
constructor() { constructor() {
super(` super(`
<style> <style>
:host { :host { display: block; }
display: block; * { font-family: 'Cardo', sans-serif; }
}
#contact-wrapper contact-type-icon { #contact-wrapper contact-type-icon {
width: 24px; width: 24px;
margin-right: 8px; margin-right: 8px;
@ -29,6 +28,10 @@ customElements.define('contact-info-editor', class extends Component {
:host([save="false"]) #submit { :host([save="false"]) #submit {
display: none; display: none;
} }
input[type=submit] {
width: 100%;
max-width: 100%;
}
@media only screen and (min-device-width: 1000px) { @media only screen and (min-device-width: 1000px) {
article { article {
margin: 0; margin: 0;
@ -37,7 +40,6 @@ customElements.define('contact-info-editor', class extends Component {
${ FORM_STYLE } ${ FORM_STYLE }
</style> </style>
<article> <article>
<h2>Edycja listy danych kontaktowych</h2>
<section> <section>
<form method="post" action="/contacts/create"> <form method="post" action="/contacts/create">
<div id="contact-wrapper"> <div id="contact-wrapper">

View File

@ -10,19 +10,14 @@ customElements.define('edit-contact-info', class extends Component {
<style> <style>
:host { display: block; } :host { display: block; }
article { article {
display: flex; display: block;
justify-content: space-between;
width: 100%;
}
#actions {
display: flex;
justify-content: space-between;
} }
#buttons { #buttons {
display: flex; display: flex;
justify-content: start; justify-content: start;
margin: 8px 0;
} }
#actions > *:not(:last-child) { #actions input {
margin-right: 8px; margin-right: 8px;
} }
:host([mode = 'view']) contact-info-editor { :host([mode = 'view']) contact-info-editor {
@ -44,6 +39,17 @@ customElements.define('edit-contact-info', class extends Component {
:host([delete = "false"]) #deleteButton { :host([delete = "false"]) #deleteButton {
display: none; display: none;
} }
@media only screen and (min-device-width: 1000px) {
article {
display: flex;
justify-content: space-between;
width: 100%;
}
#actions {
display: flex;
justify-content: space-between;
}
}
${ BUTTON_STYLE } ${ BUTTON_STYLE }
</style> </style>
<article> <article>

View File

@ -9,7 +9,7 @@ customElements.define('local-business-item', class extends Component {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
:host([picture-url = '']) #img { :host([picture-url = '']) #img {
display: none; display: none;
} }

View File

@ -9,7 +9,7 @@ customElements.define('local-business', class extends Component {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
#items { #items {
margin-top: 16px; margin-top: 16px;
} }
@ -36,14 +36,6 @@ customElements.define('local-business', class extends Component {
`); `);
} }
connectedCallback() {
super.connectedCallback();
}
attributeChangedCallback(name, oldV, newV) {
super.attributeChangedCallback(name, oldV, newV);
}
get name() { get name() {
return this.getAttribute('name') || '' return this.getAttribute('name') || ''
} }

View File

@ -9,7 +9,7 @@ customElements.define('local-business-list', class extends Component {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
::slotted(local-business[local-business-visible="invisible"]) { ::slotted(local-business[local-business-visible="invisible"]) {
display: none; display: none;
} }

View File

@ -5,12 +5,12 @@ customElements.define('login-form', class extends Component {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
${ FORM_STYLE } ${ FORM_STYLE }
</style> </style>
<form action="/login" method="post"> <form action="/login" method="post">
<div> <div>
<label>Login</label> <label>E-Mail</label>
<input name="email" placeholder="E-Mail" type="email" required /> <input name="email" placeholder="E-Mail" type="email" required />
</div> </div>
<div> <div>

View File

@ -8,6 +8,7 @@ customElements.define('marketplace-offer', class extends Component {
} }
constructor() { constructor() {
// language=HTML
super(` super(`
<style> <style>
:host { :host {
@ -16,23 +17,31 @@ customElements.define('marketplace-offer', class extends Component {
border-bottom: 1px solid var(--border-light-gray-color); border-bottom: 1px solid var(--border-light-gray-color);
width: 100%; width: 100%;
} }
section { section {
margin-bottom: 16px; margin-bottom: 16px;
} }
#preview { #preview {
width: 100%; width: 100%;
} }
image-popup { image-popup {
max-width: 100%; max-width: 100%;
} }
#sep { #sep {
display: block; display: block;
grid-area: sep; grid-area: sep;
text-align: center; text-align: center;
} }
:host([price-range-max="0"]) #sep {
:host([price-range-max="0"]) #sep, :host([price-range]) #sep,
:host([price-range-max="0"]) #price-min, :host([price-range]) #price-min
{
display: none; display: none;
} }
#details { #details {
display: grid; display: grid;
column-gap: 16px; column-gap: 16px;
@ -40,24 +49,39 @@ customElements.define('marketplace-offer', class extends Component {
"desc desc desc" "desc desc desc"
"min sep max"; "min sep max";
grid-template-columns: auto 10px auto; grid-template-columns: auto 10px auto;
width: 90%;
} }
#preview { #preview {
grid-area: img; grid-area: img;
max-width: 200px;
margin: auto; margin: auto;
} }
#description { #description {
grid-area: desc; grid-area: desc;
text-align: justify;
font-style: italic;
} }
#price-min { #price-min {
grid-area: min; grid-area: min;
justify-self: end; justify-self: end;
-webkit-transition: background .3s, border .3s, border-radius .3s, -webkit-box-shadow .3s;
transition: background .3s, border .3s, border-radius .3s, -webkit-box-shadow .3s;
-o-transition: background .3s, border .3s, border-radius .3s, box-shadow .3s;
transition: background .3s, border .3s, border-radius .3s, box-shadow .3s;
transition: background .3s, border .3s, border-radius .3s, box-shadow .3s, -webkit-box-shadow .3s;
} }
#price-max { #price-max {
grid-area: max; grid-area: max;
justify-self: start; justify-self: start;
-webkit-transition: background .3s, border .3s, border-radius .3s, -webkit-box-shadow .3s;
transition: background .3s, border .3s, border-radius .3s, -webkit-box-shadow .3s;
-o-transition: background .3s, border .3s, border-radius .3s, box-shadow .3s;
transition: background .3s, border .3s, border-radius .3s, box-shadow .3s;
transition: background .3s, border .3s, border-radius .3s, box-shadow .3s, -webkit-box-shadow .3s;
} }
#contacts { #contacts {
display: flex; display: flex;
justify-content: end; justify-content: end;
@ -67,41 +91,42 @@ customElements.define('marketplace-offer', class extends Component {
#details { #details {
display: grid; display: grid;
column-gap: 16px; column-gap: 16px;
grid-template-areas: "img desc desc desc desc desc" grid-template-areas: "img img img" "desc desc desc" "min sep max";
"img _ _ min sep max"; grid-template-columns: auto 10px auto;
grid-template-columns: 400px auto auto 100px 10px 100px; margin: 0 20px 0 20px;
}
image-popup {
width: 100%; width: 100%;
} }
image-popup {
width: 400px;
height: 400px;
}
#preview { #preview {
max-width: 400px;
max-height: 400px;
grid-area: img; grid-area: img;
align-self: center; align-self: center;
text-align: center; text-align: center;
} }
#description { #description {
min-height: 200px;
grid-area: desc; grid-area: desc;
justify-self: stretch; justify-self: stretch;
margin-top: 0; margin-top: 0;
} }
#price-min { #price-min {
grid-area: min; grid-area: min;
justify-self: end; justify-self: end;
width: 100px; width: 100px;
text-align: right; text-align: right;
} }
#price-max { #price-max {
grid-area: max; grid-area: max;
justify-self: start; justify-self: start;
width: 100px; width: 100px;
text-align: left; text-align: right;
} }
} }
${ INPUT_STYLE } ${ INPUT_STYLE }
</style> </style>
<section id="details"> <section id="details">
@ -198,12 +223,12 @@ customElements.define('marketplace-offer', class extends Component {
max.innerHTML = `Za darmo`; max.innerHTML = `Za darmo`;
} }
if (this.#price_range.isRange) { if (this.#price_range.isRange) {
min.innerHTML = `<price-view value="${this.#price_range.min}"></price-view>` min.innerHTML = `<price-view value="${ this.#price_range.min }"></price-view>`
max.innerHTML = `<price-view value="${this.#price_range.max}"></price-view>`; max.innerHTML = `<price-view value="${ this.#price_range.max }"></price-view>`;
} }
if (this.#price_range.isFixed) { if (this.#price_range.isFixed) {
min.innerHTML = ``; min.innerHTML = ``;
max.innerHTML = `<price-view value="${this.#price_range.min}"></price-view>`; max.innerHTML = `<price-view value="${ this.#price_range.min }"></price-view>`;
} }
return ''; return '';
} }

View File

@ -1,6 +1,10 @@
import { Component } from "../shared"; import { Component, BUTTON_STYLE } from "../shared";
customElements.define('marketplace-offers', class extends Component { customElements.define('marketplace-offers', class extends Component {
static get observedAttributes() {
return ['account-id'];
}
constructor() { constructor() {
super(` super(`
<style> <style>
@ -11,18 +15,48 @@ customElements.define('marketplace-offers', class extends Component {
display: block; display: block;
text-align: center; text-align: center;
} }
article { #offers {
margin: 8px; display: block;
}
::slotted(marketplace-offer), ::slotted(user-edit-offer) {
width: 100%;
margin: 0 20px 20px;
} }
@media only screen and (min-device-width: 1000px) { @media only screen and (min-device-width: 1000px) {
article { #offers {
margin: 0; display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
::slotted(marketplace-offer), ::slotted(user-edit-offer) {
width: calc(33% - 40px);
margin: 0 20px 20px;
} }
} }
${ BUTTON_STYLE }
</style> </style>
<article> <article>
<slot></slot> <section>
<button id="publish" class="btn">Dodaj ogłoszenie</button>
</section>
<section><slot></slot></section>
<section id="offers">
<slot name="offer"></slot>
</section>
</article> </article>
`); `);
this.shadowRoot.querySelector('#publish').addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();
location.href = '/account/offers';
});
}
set account_id(v) {
this.setAttribute('account-id', v)
}
get account_id() {
return this.getAttribute('account-id');
} }
}); });

View File

@ -16,15 +16,11 @@ customElements.define('user-edit-offer', class extends Component {
:host([mode='form']) #form { display: block; } :host([mode='form']) #form { display: block; }
:host([state='Finished']) #finishForm { display: none; } :host([state='Finished']) #finishForm { display: none; }
#actions { #actions {
width: 120px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
#actions > * {
margin-right: 16px;
}
#actions input { #actions input {
width: 100%; width: 140px;
} }
#state { #state {
font-weight: bold; font-weight: bold;
@ -34,26 +30,19 @@ customElements.define('user-edit-offer', class extends Component {
} }
@media only screen and (min-device-width: 1200px) { @media only screen and (min-device-width: 1200px) {
:host([mode='view']) #view { display: flex; } :host([mode='view']) #view { display: flex; }
#actions {
margin-left: 16px;
display: block;
}
#actions > * {
margin-right: 0;
}
} }
${ BUTTON_STYLE } ${ BUTTON_STYLE }
</style> </style>
<section id="state"></section>
<section id="view"> <section id="view">
<slot></slot> <slot></slot>
<div id="actions"> </section>
<div id="state"></div> <section id="actions">
<input type="button" value="Edytuj" id="edit" /> <input type="button" value="Edytuj" id="edit" />
<form id="finishForm" action="/offers/finish" method="post"> <form id="finishForm" action="/offers/finish" method="post">
<input type="hidden" name="id" class="id" value="" /> <input type="hidden" name="id" class="id" value="" />
<input type="submit" slot="action" id="finish" value="Zakończ" /> <input type="submit" slot="action" id="finish" value="Zakończ" />
</form> </form>
</div>
</section> </section>
<section id="form"> <section id="form">
<offer-form> <offer-form>
@ -65,7 +54,7 @@ customElements.define('user-edit-offer', class extends Component {
this.shadowRoot.querySelector('#edit').addEventListener('click', ev => { this.shadowRoot.querySelector('#edit').addEventListener('click', ev => {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
this.mode = 'form'; location.href = `/marketplace/${this.offer_id}/edit`
}); });
this.shadowRoot.querySelector('#cancel').addEventListener('click', ev => { this.shadowRoot.querySelector('#cancel').addEventListener('click', ev => {
ev.preventDefault(); ev.preventDefault();

View File

@ -40,7 +40,11 @@ customElements.define('account-view', class extends Component {
} }
#logout { #logout {
border-color: var(--red-color); border-color: var(--red-color);
color: var(--red-color); background: var(--red-color);
color: white;
}
#logoutSection {
margin-bottom: 16px;
} }
section input[type="button"], section input[type="button"],
section input[type="button"], section input[type="button"],
@ -48,13 +52,10 @@ customElements.define('account-view', class extends Component {
width: 100%; width: 100%;
} }
section input[type="submit"], section input[type="submit"],
section a.btn { section a {
padding-bottom: 0; color: black;
padding-top: 0;
display: block;
} }
::slotted(#editService) { ::slotted(#editService) {
width: calc(100% - 1.5rem) !important;
padding-bottom: 0 !important; padding-bottom: 0 !important;
padding-top: 0 !important; padding-top: 0 !important;
display: block !important; display: block !important;
@ -77,11 +78,39 @@ customElements.define('account-view', class extends Component {
width: auto; width: auto;
} }
::slotted(#editService) { ::slotted(#editService) {
min-width: 120px !important;
width: auto !important;
display: inline-block !important; display: inline-block !important;
} }
} }
::slotted(button) {
cursor: pointer;
position: relative;
display: inline-block;
text-align: center;
vertical-align: middle;
user-select: none;
height: calc(1.5em + 0.75rem + 2px);
border: 1px solid black;
color: white;
background: black;
padding: 10px 20px;
font-family: "Cardo", Sans-serif;
font-size: 20px;
font-weight: 500;
line-height: 1em;
letter-spacing: 0;
transition: all 0.2s;
width: 100%;
}
#editServiceSection {
margin-bottom: 16px;
width: 100%;
}
label {
width: 120px;
}
.input {
margin-bottom: 16px;
}
${ FORM_STYLE }${ BUTTON_STYLE } ${ FORM_STYLE }${ BUTTON_STYLE }
</style> </style>
<article> <article>
@ -89,11 +118,11 @@ customElements.define('account-view', class extends Component {
<section> <section>
<input id="id" name="id" readonly type="hidden" /> <input id="id" name="id" readonly type="hidden" />
</section> </section>
<section> <section class="input">
<label>Login</label> <label>Login</label>
<input id="name" name="name" readonly /> <input id="name" name="name" readonly />
</section> </section>
<section> <section class="input">
<label>E-Mail</label> <label>E-Mail</label>
<input id="email" name="email" readonly /> <input id="email" name="email" readonly />
</section> </section>
@ -106,17 +135,17 @@ customElements.define('account-view', class extends Component {
<label>Powiązane konto Facebook</label> <label>Powiązane konto Facebook</label>
<input id="facebook_id" name="facebook_id" readonly /> <input id="facebook_id" name="facebook_id" readonly />
</section> </section>
<section> <section id="editServiceSection">
<slot name="editService"></slot> <slot name="editService"></slot>
</section> </section>
<section> <section id="logoutSection">
<form action="/logout" method="post"> <form action="/logout" method="post">
<input id="logout" value="Wyloguj" type="submit" /> <input id="logout" value="Wyloguj" type="submit" />
</form> </form>
</section> </section>
<section id="rules"> <section id="rules">
<a class="btn" href="/terms-and-condition" target="_blank">Regulamin</a> <a class="" href="/terms-and-condition" target="_blank">Regulamin</a>
<a class="btn" href="/privacy-policy" target="_blank">Polityka prywatności</a> <a class="" href="/privacy-policy" target="_blank">Polityka prywatności</a>
</section> </section>
</article> </article>
`); `);
@ -124,6 +153,14 @@ customElements.define('account-view', class extends Component {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
}); });
{
const input = this.querySelector('#editService');
if (input) input.addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();
location.href = "/account/business-items";
})
}
} }
connectedCallback() { connectedCallback() {

View File

@ -1,4 +1,4 @@
import { Component, FORM_STYLE, goTo, historyDetails } from "../shared"; import { Component, BUTTON_STYLE, Router } from "../shared";
customElements.define('ow-account', class extends Component { customElements.define('ow-account', class extends Component {
static get observedAttributes() { static get observedAttributes() {
@ -9,7 +9,7 @@ customElements.define('ow-account', class extends Component {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
#switch-register, #switch-login { #switch-register, #switch-login {
display: none; display: none;
} }
@ -19,38 +19,39 @@ customElements.define('ow-account', class extends Component {
:host([mode="register"]) #switch-login { :host([mode="register"]) #switch-login {
display: block !important; display: block !important;
} }
a { input {
display: block; display: block;
cursor: pointer; cursor: pointer;
} }
a:hover { section > input[type=button] {
color: var(--hover-color); width: 100%;
border-bottom-color: var(--hover-color); max-width: 100%;
text-decoration: none; }
@media only screen and (min-device-width: 1000px) {
} }
${ FORM_STYLE } ${ BUTTON_STYLE }
</style> </style>
<article> <article>
<slot></slot> <slot></slot>
<section id="switch-register"> <section id="switch-register">
<a class="btn">Nie masz konta? Utwórz nowe</a> <input type="button" class="btn" value="Nie masz konta? Utwórz nowe" />
</section> </section>
<section id="switch-login"> <section id="switch-login">
<a class="btn">Posiadasz konto? Zaloguj się</a> <input type="button" class="btn" value="Posiadasz konto? Zaloguj się" />
</section> </section>
</article> </article>
`); `);
this.shadowRoot.querySelector('#switch-login > a').addEventListener('click', ev => { this.shadowRoot.querySelector('#switch-login > input').addEventListener('click', ev => {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
goTo('/login'); Router.goTo('/login');
}); });
this.shadowRoot.querySelector('#switch-register > a').addEventListener('click', ev => { this.shadowRoot.querySelector('#switch-register > input').addEventListener('click', ev => {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
goTo('/register/account-type'); Router.goTo('/register/account-type');
}); });
this.addEventListener('facebook:account', ev => { this.addEventListener('facebook:account', ev => {
ev.stopPropagation(); ev.stopPropagation();
@ -60,24 +61,24 @@ customElements.define('ow-account', class extends Component {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
const parts = historyDetails().parts; this.listenHistory(Router.historyDetails());
}
listenHistory = ({ parts }) => {
switch (parts.first) { switch (parts.first) {
case 'register': { case 'register': {
if (this.mode === 'register')
return;
this.mode = 'register'; this.mode = 'register';
break; break;
} }
default: default: {
if (this.mode === 'login')
return;
this.mode = 'login'; this.mode = 'login';
break; break;
} }
} }
listenHistory = ({ parts }) => {
console.warn(parts);
}
attributeChangedCallback(name, oldV, newV) {
super.attributeChangedCallback(name, oldV, newV);
} }
get mode() { get mode() {
@ -87,5 +88,14 @@ customElements.define('ow-account', class extends Component {
set mode(value) { set mode(value) {
value = ['login', 'register', 'display'].includes(value) ? value : 'login'; value = ['login', 'register', 'display'].includes(value) ? value : 'login';
this.setAttribute('mode', value); this.setAttribute('mode', value);
switch (value) {
case 'register': {
this.innerHTML = `<register-form></register-form>`;
break;
}
default:
this.innerHTML = `<login-form></login-form>`;
break;
}
} }
}); });

View File

@ -1,4 +1,4 @@
import { FORM_STYLE, Component, goTo } from "./shared.js"; import { FORM_STYLE, Component, Router } from "./shared.js";
import { RegisterForm } from "./register-form/model.js"; import { RegisterForm } from "./register-form/model.js";
customElements.define('register-form', class extends Component { customElements.define('register-form', class extends Component {
@ -12,7 +12,7 @@ customElements.define('register-form', class extends Component {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
.actions { .actions {
display: flex; display: flex;
@ -73,15 +73,15 @@ customElements.define('register-form', class extends Component {
#loadCache() { #loadCache() {
let register = {}; let register = {};
try { try {
register = JSON.parse(localStorage.getItem('register')); register = JSON.parse(localStorage.getItem('register') || '{}');
} catch (e) { } catch (e) {
localStorage.removeItem('register');
} }
this.#form.from(register); this.#form.from(register);
} }
#copyDetail(ev) { #copyDetail(ev) {
ev.stopPropagation(); ev.stopPropagation();
console.info('ev.detail', ev.detail);
for (const [key, value] of Object.entries(ev.detail)) { for (const [key, value] of Object.entries(ev.detail)) {
this.#form[key] = value; this.#form[key] = value;
} }
@ -203,7 +203,7 @@ customElements.define('register-form', class extends Component {
? this.#nextPage() ? this.#nextPage()
: this.#prevPage(); : this.#prevPage();
this.current = current; this.current = current;
goTo(`/register/${ current }`); Router.goTo(`/register/${ current }`);
} }
get current() { get current() {

View File

@ -159,7 +159,8 @@ export class RegisterForm {
}, {}) }, {})
} }
from(object) { from(object = {}) {
object = object || {};
[ [
'email', 'email',
'login', 'login',

View File

@ -21,8 +21,6 @@ customElements.define('register-account-type', class extends Component {
} }
a { a {
display: block; display: block;
border: 1px solid var(--border-slim-color);
border-radius: 16px;
padding: 16px; padding: 16px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
@ -57,21 +55,22 @@ customElements.define('register-account-type', class extends Component {
} }
#rules > a { #rules > a {
display: block; display: block;
font-size: 20px;
} }
} }
${ BUTTON_STYLE }${ TIP_STYLE } ${ BUTTON_STYLE }${ TIP_STYLE }
</style> </style>
<article> <article>
<section id="rules"> <section id="rules">
<a class="btn" target="_blank" href="/terms-and-condition"> <a target="_blank" href="/terms-and-condition">
Regulamin Regulamin
</a> </a>
<a class="btn" target="_blank" href="/privacy-policy"> <a target="_blank" href="/privacy-policy">
Polityka prywatności Polityka prywatności
</a> </a>
</section> </section>
<button id="accept-terms"> <button id="accept-terms">
<h5>Zapoznałem się i zgadzam się</h5> Zapoznałem się i zgadzam się
</button> </button>
</article> </article>
`); `);
@ -110,7 +109,7 @@ customElements.define('register-account-type', class extends Component {
</a> </a>
</li> </li>
</ul> </ul>
<form-navigation></form-navigation> <form-navigation style="display: none;"></form-navigation>
`; `;
const user = this.shadowRoot.querySelector('#user'); const user = this.shadowRoot.querySelector('#user');

View File

@ -10,15 +10,24 @@ customElements.define('register-business-contacts-form', class extends RegisterF
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
contact-info-editor > input[type='button'] {
width: 100%;
max-width: 100%;
}
::slotted(edit-contact-info) {
margin-bottom: 16px;
}
${ BUTTON_STYLE } ${ BUTTON_STYLE }
</style> </style>
<article> <article>
<h2>Edycja listy danych kontaktowych</h2>
<form> <form>
<section> <section>
<contact-info-editor <contact-info-editor
save="false" save="false"
></contact-info-editor> >
<input type="button" id="addButton" value="Dodaj" /> <input type="button" id="addButton" value="Dodaj" />
</contact-info-editor>
</section> </section>
<slot></slot> <slot></slot>
<form-navigation></form-navigation> <form-navigation></form-navigation>
@ -26,19 +35,16 @@ customElements.define('register-business-contacts-form', class extends RegisterF
</article> </article>
`); `);
this.shadowRoot.querySelector('#addButton').addEventListener('click', ev => { const editor = this.shadowRoot.querySelector('contact-info-editor');
editor.addEventListener('submit', ev => {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
const form = this.shadowRoot.querySelector('contact-info-editor'); this.#addContact(editor);
const { type, content } = form; });
this.innerHTML += ` editor.querySelector('#addButton').addEventListener('click', ev => {
<edit-contact-info mode="view" delete="false"> ev.stopPropagation();
<contact-info ev.preventDefault();
type="${ type }" this.#addContact(editor);
content="${ content }"
></contact-info>
</edit-contact-info>
`;
}); });
this.mountFormHandler(() => this.#emitChange()); this.mountFormHandler(() => this.#emitChange());
@ -52,6 +58,18 @@ customElements.define('register-business-contacts-form', class extends RegisterF
return Array.from(this.querySelectorAll('contact-info')); return Array.from(this.querySelectorAll('contact-info'));
} }
#addContact(editor) {
const { type, content } = editor;
this.innerHTML += `
<edit-contact-info mode="view" delete="false">
<contact-info
type="${ type }"
content="${ content }"
></contact-info>
</edit-contact-info>
`;
}
#emitChange() { #emitChange() {
const rows = this.#rows; const rows = this.#rows;
const contacts = rows.map(({ type, content }) => ({ const contacts = rows.map(({ type, content }) => ({

View File

@ -5,12 +5,17 @@ customElements.define('register-business-details-form', class extends RegisterFo
static get observedAttributes() { static get observedAttributes() {
return ["name", "description"]; return ["name", "description"];
} }
constructor() { constructor() {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
textarea { min-height: 200px; } textarea { min-height: 200px; }
form > div > input,
form > div > textarea {
width: 100%;
}
${ FORM_STYLE }${ TIP_STYLE } ${ FORM_STYLE }${ TIP_STYLE }
</style> </style>
<form id="step-2"> <form id="step-2">

View File

@ -10,16 +10,30 @@ customElements.define('register-business-item-form', class extends RegisterFormC
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
section > form input[type=button], section > form input[type=submit] {
width: 100%;
max-width: 100%;
}
form > #fields > div, form > #fields > div > label, form > #fields > div > .input {
width: 90% !important;
}
@media only screen and (min-device-width: 1200px) { @media only screen and (min-device-width: 1200px) {
section > form { section > form {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
} section > form input[type=button], section > form input[type=submit] {
form > input[type=submit] { width: 25%;
max-width: 25%; max-width: 25%;
} }
form > #fields {
width: 50%;
}
form > #fields > div, form > #fields > div > label, form > #fields > div > .input {
width: 90% !important;
}
}
img[src=""] { display: none; } img[src=""] { display: none; }
${ FORM_STYLE } ${ FORM_STYLE }
</style> </style>
@ -27,6 +41,7 @@ customElements.define('register-business-item-form', class extends RegisterFormC
<form method="post"> <form method="post">
<slot name="head"></slot> <slot name="head"></slot>
<image-input send-original="true"></image-input> <image-input send-original="true"></image-input>
<div id="fields">
<div id="name"> <div id="name">
<label>Nazwa</label> <label>Nazwa</label>
<input id="name" class="item-name" name="name" type="text" required /> <input id="name" class="item-name" name="name" type="text" required />
@ -36,6 +51,7 @@ customElements.define('register-business-item-form', class extends RegisterFormC
<price-input id="price" class="item-price" name="price" required > <price-input id="price" class="item-price" name="price" required >
</price-input> </price-input>
</div> </div>
</div>
<input id="submit-button" type="submit" value="Zapisz" /> <input id="submit-button" type="submit" value="Zapisz" />
<input id="remove-button" type="submit" value="Usuń" /> <input id="remove-button" type="submit" value="Usuń" />
@ -137,7 +153,6 @@ customElements.define('register-business-item-form', class extends RegisterFormC
set price(v) { set price(v) {
v = parseInt(v); v = parseInt(v);
console.info('set price', v);
this.setAttribute('price', v); this.setAttribute('price', v);
this.shadowRoot.querySelector('#price').value = v / 100.0; this.shadowRoot.querySelector('#price').value = v / 100.0;
} }

View File

@ -14,7 +14,7 @@ customElements.define('register-business-items-form', class extends RegisterForm
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
::slotted(section) { ::slotted(section) {
display: flex; display: flex;
@ -23,6 +23,10 @@ customElements.define('register-business-items-form', class extends RegisterForm
::slotted(register-business-item-form) { ::slotted(register-business-item-form) {
margin-bottom: 18px; margin-bottom: 18px;
} }
form input[type=button], form input[type=submit] {
width: 100%;
max-width: 100%;
}
${ FORM_STYLE } ${ FORM_STYLE }
</style> </style>
@ -68,10 +72,7 @@ customElements.define('register-business-items-form', class extends RegisterForm
} }
#emitChange() { #emitChange() {
const rows = this.#rows;
console.warn(rows);
const items = this.#rows.map(({ price, picture_url, name }) => ({ price, picture_url, name })); const items = this.#rows.map(({ price, picture_url, name }) => ({ price, picture_url, name }));
console.warn(items);
this.dispatchEvent(new CustomEvent(this.submitEventName, { bubbles: true, composed: true, detail: { items } })); this.dispatchEvent(new CustomEvent(this.submitEventName, { bubbles: true, composed: true, detail: { items } }));
} }
}); });

View File

@ -1,4 +1,5 @@
import { FORM_STYLE, goTo, PseudoForm } from "../shared.js"; import { FORM_STYLE, Router, PseudoForm } from "../shared.js";
import { ErrorMessage } from "../shared/error-message.js";
customElements.define('register-business-submit-form', class extends PseudoForm { customElements.define('register-business-submit-form', class extends PseudoForm {
static get observedAttributes() { static get observedAttributes() {
@ -9,10 +10,14 @@ customElements.define('register-business-submit-form', class extends PseudoForm
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
${ FORM_STYLE } ${ FORM_STYLE }
img[src=''] { display: none; } img[src=''] { display: none; }
@media only screen and (min-device-width: 1200px) { input[type=submit] {
width: 100%;
max-width: 100%;
}
@media only screen and (min-device-width: 1000px) {
.item-view { .item-view {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -75,10 +80,11 @@ customElements.define('register-business-submit-form', class extends PseudoForm
}); });
if (res.ok) { if (res.ok) {
goTo("/account?success"); // Router.goTo("/account?success");
location.href ='/account?success';
} else { } else {
const { error } = await res.json(); const { error } = await res.json();
document.querySelector('error-message').message = error; ErrorMessage.errorMessage = error;
} }
}); });
} }

View File

@ -1,5 +1,6 @@
import { FORM_STYLE } from "../shared"; import { FORM_STYLE, Router } from "../shared.js";
import { RegisterFormComponent } from "./model"; import { RegisterFormComponent } from "./model.js";
import { ErrorMessage } from "../shared/error-message";
customElements.define('register-user-account-form', class extends RegisterFormComponent { customElements.define('register-user-account-form', class extends RegisterFormComponent {
static get observedAttributes() { static get observedAttributes() {
@ -10,13 +11,7 @@ customElements.define('register-user-account-form', class extends RegisterFormCo
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
:host([mode="email"]) #form {
display: block;
}
:host([mode="email"]) #email-icon {
display: none;
}
svg > path { svg > path {
fill: var(--border-slim-color); fill: var(--border-slim-color);
} }
@ -99,6 +94,34 @@ customElements.define('register-user-account-form', class extends RegisterFormCo
form.querySelector('#account_type').value = 'User'; form.querySelector('#account_type').value = 'User';
form.submit(); form.submit();
}); });
form.addEventListener('submit', async ev => {
ev.stopPropagation();
ev.preventDefault();
const json = Array.from(form.elements).filter(el => el.name && el.name.trim().length).reduce((memo, {
name,
value
}) => ({
...memo,
[name]: value.trim(),
}), {});
const res = await fetch('/register', {
method: 'POST',
body: JSON.stringify(json),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
});
if (res.ok) {
// Router.goTo("/account?success");
location.href ='/account?success';
} else {
const { error } = await res.json();
ErrorMessage.errorMessage = error;
}
});
} }
get submitEventName() { get submitEventName() {
@ -106,21 +129,10 @@ customElements.define('register-user-account-form', class extends RegisterFormCo
} }
connectedCallback() { connectedCallback() {
super.connectedCallback();
this.mode = 'email'; this.mode = 'email';
} }
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
case 'mode': {
if (newV !== 'email' && newV !== 'facebook' && newV !== '')
return;
this.mode = newV;
break;
}
}
}
get mode() { get mode() {
return this.getAttribute('mode') || '' return this.getAttribute('mode') || ''
} }

View File

@ -1,53 +1,43 @@
export const BUTTON_STYLE = ` export const BUTTON_STYLE = `
input[type="button"], input[type="submit"], button { input[type="button"], input[type="submit"], button {
cursor: pointer; cursor: pointer;
border-radius: 5px;
box-shadow: 0 10px 20px -6px rgba(0,0,0,.12);
position: relative; position: relative;
margin-bottom: 20px;
transition: .3s;
display: inline-block; display: inline-block;
font-weight: 400;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
user-select: none; user-select: none;
font-size: 1rem;
line-height: 1.5;
transition: color .15s ease-in-out,
background-color .15s ease-in-out,
border-color .15s ease-in-out,
box-shadow .15s ease-in-out;
width: auto; width: auto;
height: calc(1.5em + 0.75rem + 2px); height: calc(1.5em + 0.75rem + 2px);
padding: .375rem .75rem; border: 1px solid black;
color: white;
border: 1px solid #495057; background: black;
color: #495057; padding: 10px 20px;
background: white; font-family: "Cardo", Sans-serif;
font-size: 20px;
font-weight: 500;
line-height: 1em;
letter-spacing: 0;
transition: all 0.2s;
} }
.btn { .btn {
cursor: pointer; cursor: pointer;
border-radius: 5px;
box-shadow: 0 10px 20px -6px rgba(0, 0, 0, .12);
position: relative; position: relative;
margin-bottom: 20px;
display: inline-block; display: inline-block;
font-weight: 400;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
user-select: none; user-select: none;
font-size: 1rem;
line-height: 1.5;
transition: color .15s ease-in-out,
background-color .15s ease-in-out,
border-color .15s ease-in-out,
box-shadow .15s ease-in-out;
width: auto; width: auto;
height: calc(1.5em + 0.75rem + 2px); height: calc(1.5em + 0.75rem + 2px);
padding: .375rem .75rem; border: 1px solid black;
border: 1px solid #495057; color: white;
color: #495057; background: black;
background: white; padding: 10px 20px;
font-family: "Cardo", Sans-serif;
font-size: 20px;
font-weight: 500;
line-height: 1em;
letter-spacing: 0;
transition: all 0.2s;
} }
a.btn { a.btn {
text-decoration: none; text-decoration: none;
@ -66,34 +56,25 @@ input.link:hover {
export const INPUT_STYLE = ` export const INPUT_STYLE = `
input, textarea, select, option { input, textarea, select, option {
font-size: 16px; box-sizing: border-box;
border: 1px solid var(--ast-border-color);
padding: 6px 6px 5px;
margin: 0 4px 0 0;
outline: 0;
line-height: 1;
border: none; color: #666;
border-bottom-style: none; padding: .75em;
border-bottom-width: medium; height: auto;
border-bottom: 1px solid rgba(0,0,0,.1); border-width: 1px;
border-style: solid;
border-color: var(--ast-border-color);
border-radius: 2px; border-radius: 2px;
padding: 0; background: #fafafa;
background-color: rgb(250, 250, 250);
height: 36px; box-shadow: none;
background: #fff; box-sizing: border-box;
color: rgba(0,0,0,.8); transition: all .2s linear;
font-size: 14px;
box-shadow: none !important;
display: block;
width: 100%;
height: calc(1.5em + 0.75rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-clip: padding-box;
transition: border-color .15s ease-in-out , -webkit-box-shadow .15s ease-in-out;
transition: border-color .15s ease-in-out , box-shadow .15s ease-in-out;
transition: border-color .15s ease-in-out , box-shadow .15s ease-in-out , -webkit-box-shadow .15s ease-in-out;
} }
input[type="text"], input[type="text"],
input[type="number"], input[type="number"],
@ -170,8 +151,8 @@ export class Component extends HTMLElement {
} }
{ {
const listener = this.listenHistory; const listener = this.listenHistory;
listener && listener(historyDetails()); listener && listener(Router.historyDetails());
this.#dropHistory = subscribeHistory(listener); this.#dropHistory = Router.subscribeHistory(listener);
} }
} }
@ -180,7 +161,9 @@ export class Component extends HTMLElement {
} }
#dropHistory; #dropHistory;
listenHistory = null
listenHistory() {
}
attributeChangedCallback(name, oldV, newV) { attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) if (oldV === newV)
@ -274,7 +257,7 @@ export class PriceRange {
toString() { toString() {
if (this.isFixed) return this.min.toString() if (this.isFixed) return this.min.toString()
if (this.isFree) return 'free'; if (this.isFree) return 'free';
return `${this.min}|${this.max}`; return `${ this.min }|${ this.max }`;
} }
} }
@ -289,22 +272,38 @@ export const runFbReady = (fn) => {
const fbQueue = []; const fbQueue = [];
let fbReady = false; let fbReady = false;
const historyQueue = new Set; export class Router {
export const goTo = (url) => { static historyQueue = new Set;
static goTo(url) {
history.pushState({}, document.title, url); history.pushState({}, document.title, url);
for (const call of historyQueue) call(); Router.onChange();
document.dispatchEvent(new CustomEvent('history:push', { composed: true, bubbles: true, details: historyDetails() })); }
}
export const historyDetails = () => { static historyDetails() {
const parts = location.pathname.split('/').filter(s => s && s.length) return {
return { parts } parts: location.pathname.split('/').filter(s => s && s.length),
} }
const subscribeHistory = (cb) => { }
static subscribeHistory(cb) {
if (cb) { if (cb) {
const call = () => { const call = () => {
cb(historyDetails()); cb(Router.historyDetails());
} }
historyQueue.add(call); Router.historyQueue.add(call);
return () => historyQueue.delete(call); return () => Router.historyQueue.delete(call);
}
}
static onChange() {
for (const call of Router.historyQueue) call();
document.dispatchEvent(new CustomEvent('history:push', {
composed: true,
bubbles: true,
details: Router.historyDetails()
}));
} }
} }
window.addEventListener('popstate', () => Router.onChange());

View File

@ -1,6 +1,6 @@
import { Component } from "../shared.js"; import { Component } from "../shared.js";
customElements.define('error-message', class extends Component { export class ErrorMessage extends Component {
static get observedAttributes() { static get observedAttributes() {
return ['message']; return ['message'];
} }
@ -8,7 +8,8 @@ customElements.define('error-message', class extends Component {
constructor() { constructor() {
super(` super(`
<style> <style>
:host { display: block; } :host { display: none; }
:host([message]) { display: block; }
div { div {
width: 1280px; width: 1280px;
background: #ffe0e0; background: #ffe0e0;
@ -30,4 +31,12 @@ customElements.define('error-message', class extends Component {
this.setAttribute('message', m); this.setAttribute('message', m);
this.shadowRoot.querySelector('div').textContent = m; this.shadowRoot.querySelector('div').textContent = m;
} }
})
static set errorMessage(v) {
const el = document.querySelector('error-message');
if (!el) return;
el.message = v;
}
}
customElements.define('error-message', ErrorMessage)

View File

@ -9,17 +9,25 @@ customElements.define('form-navigation', class extends Component {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
.actions { .actions {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} margin: 16px 0;
form > .actions > input {
max-width: 200px;
} }
form > .actions > input.hidden { form > .actions > input.hidden {
display: none !important; display: none !important;
} }
form > .actions > input {
width: calc(50% - 16px);
max-width: 200px;
}
@media only screen and (min-device-width: 1000px) {
form > .actions > input {
width: auto;
max-width: 200px;
}
}
${ BUTTON_STYLE } ${ BUTTON_STYLE }
</style> </style>
<form> <form>

View File

@ -1,41 +1,109 @@
import { Component } from "../../shared"; import { Component, Router } from "../../shared.js";
customElements.define('ow-nav', class extends Component { customElements.define('ow-nav', class extends Component {
constructor() { constructor() {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } article {
section { border-style: solid;
display: flex; border-width: 0 0 1px 0;
align-items: stretch; border-color: #F2F2F2;
align-content: stretch; transition: background 0.3s, border 0.3s, border-radius 0.3s, box-shadow 0.3s;
margin-top: 0;
margin-bottom: 12px; margin-bottom: 16px;
color: #2b2727; padding: 13px 10px 13px 10px;
font-family: Raleway, sans-serif;
font-size: 14px;
line-height: 32px;
} }
section::after { section, section > div {
display: block; display: -webkit-box;
content: ' '; display: -ms-flexbox;
border: none; display: flex;
padding: 10px 18px; position: relative;
text-decoration: none; }
color: #2b2727; svg {
text-transform: uppercase; height: 48px;
align-self: stretch; }
flex-shrink: 20; #logo {
flex-grow: 20; display: none;
} }
@media print { @media print {
:host { display: none; } :host { display: none; }
} }
@media only screen and (min-device-width: 1000px) {
section > div {
width: 33%;
margin: 0 auto;
}
#logo {
display: block;
text-align: center;
}
#right {
justify-content: flex-end;
}
}
</style> </style>
<article>
<section> <section>
<slot></slot> <div class="left">
<slot name="left"></slot>
</div>
<div id="logo">
<a href="/">
<svg viewBox="0 0 911 550" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="293" cy="335.2" rx="121" ry="170.61" fill="none" stroke="#555" style="opacity:.5" opacity=".5"/>
<ellipse cx="653.783" cy="113.849" rx="121.002" ry="170.613" fill="none" stroke="#555" transform="rotate(11.25)" style="opacity:.5" opacity=".5"/>
<ellipse cx="428.125" cy="166.014" rx="125.003" ry="176.254" fill="none" stroke="#555" transform="rotate(22.5)" style="opacity:.5" opacity=".5"/>
<ellipse cx="402.034" cy="23.889" rx="123.005" ry="173.437" fill="none" stroke="#555" transform="rotate(33.75)" style="opacity:.5" opacity=".5"/>
<ellipse cx="464.706" cy="-31.682" rx="124.999" ry="176.248" fill="none" stroke="#555" transform="rotate(45)" style="opacity:.5" opacity=".5"/>
<ellipse cx="540.521" cy="-306.337" rx="129.005" ry="181.898" fill="none" stroke="#555" transform="rotate(56.25)" style="opacity:.5" opacity=".5"/>
<ellipse cx="494.207" cy="-442.566" rx="124.003" ry="174.844" fill="none" stroke="#555" transform="rotate(67.5)" style="opacity:.5" opacity=".5"/>
<ellipse cx="239.113" cy="-150.236" rx="126.002" ry="177.663" fill="none" stroke="#555" transform="rotate(78.75)" style="opacity:.5" opacity=".5"/>
<ellipse cx="304.2" cy="-330" rx="128" ry="180.48" fill="none" stroke="#555" transform="rotate(90)" style="opacity:.5" opacity=".5"/>
<ellipse cx="143.181" cy="-572.932" rx="120.002" ry="169.203" fill="none" stroke="#555" transform="rotate(101.25)" style="opacity:.5" opacity=".5"/>
<ellipse cx="142" cy="-386.769" rx="123.003" ry="173.434" fill="none" stroke="#555" transform="rotate(112.5)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-91.151" cy="-617.377" rx="123.005" ry="173.437" fill="none" stroke="#555" transform="rotate(123.75)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-17.541" cy="-483.099" rx="124.999" ry="176.248" fill="none" stroke="#555" transform="rotate(135)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-89.814" cy="-445.118" rx="128.005" ry="180.488" fill="none" stroke="#555" transform="rotate(146.25)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-309.998" cy="-338.613" rx="125.003" ry="176.254" fill="none" stroke="#555" transform="rotate(157.5)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-514.833" cy="-456.414" rx="120.002" ry="169.203" fill="none" stroke="#555" transform="rotate(168.75)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-534" cy="-351.2" rx="126" ry="177.66" fill="none" stroke="#555" transform="scale(-1)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-434.371" cy="-246.176" rx="128.002" ry="180.483" fill="none" stroke="#555" transform="rotate(-168.75)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-710.531" cy="4.041" rx="126.003" ry="177.664" fill="none" stroke="#555" transform="rotate(-157.5)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-382.027" cy="-19.162" rx="130.005" ry="183.308" fill="none" stroke="#555" transform="rotate(-146.25)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-721.398" cy="240.271" rx="129.999" ry="183.298" fill="none" stroke="#555" transform="rotate(-135)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-304.791" cy="106.633" rx="123.005" ry="173.437" fill="none" stroke="#555" transform="rotate(-123.75)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-495.302" cy="413.961" rx="128.003" ry="180.485" fill="none" stroke="#555" transform="rotate(-112.5)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-265.515" cy="206.161" rx="125.002" ry="176.253" fill="none" stroke="#555" transform="rotate(-101.25)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-180.2" cy="605" rx="122" ry="172.02" fill="none" stroke="#555" transform="rotate(-90)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-137.223" cy="326.045" rx="121.002" ry="170.613" fill="none" stroke="#555" transform="rotate(-78.75)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-80.373" cy="352.613" rx="125.003" ry="176.254" fill="none" stroke="#555" transform="rotate(-67.5)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-50.619" cy="275.587" rx="126.005" ry="177.667" fill="none" stroke="#555" transform="rotate(-56.25)" style="opacity:.5" opacity=".5"/>
<ellipse cx="-22.773" cy="406.017" rx="126.999" ry="179.068" fill="none" stroke="#555" transform="rotate(-45)" style="opacity:.5" opacity=".5"/>
<ellipse cx="97.486" cy="309.523" rx="121.005" ry="170.617" fill="none" stroke="#555" transform="rotate(-33.75)" style="opacity:.5" opacity=".5"/>
<ellipse cx="507.007" cy="435.361" rx="129.003" ry="181.895" fill="none" stroke="#555" transform="rotate(-22.5)" style="opacity:.5" opacity=".5"/>
<ellipse cx="261.461" cy="254.093" rx="130.002" ry="183.303" fill="none" stroke="#555" transform="rotate(-11.25)" style="opacity:.5" opacity=".5"/>
<text xml:space="preserve" style="font-style:normal;font-weight:400;font-size:200px;line-height:1.25;font-family:sans-serif;fill:#000;fill-opacity:1;stroke:none" x="36.404" y="311.43">
<tspan x="36.404" y="311.43" style="font-style:normal;font-variant:normal;font-weight:400;font-stretch:normal;font-family:'Cardo';">OS Wilno</tspan>
</text>
</svg>
</a>
</div>
<div id="right">
<slot name="right"></slot>
</div>
</section> </section>
</article>
`); `);
} }
#mount(selector, path) {
const el = this.querySelector(selector);
if (!el) return;
el.addEventListener('click', (ev) => {
ev.stopPropagation();
ev.preventDefault();
Router.goTo(path);
});
}
}); });

View File

@ -9,7 +9,7 @@ customElements.define('ow-path', class extends Component {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
a { a {
padding: 8px; padding: 8px;
text-decoration: none; text-decoration: none;

View File

@ -9,7 +9,7 @@ customElements.define('price-input', class extends Component {
super(` super(`
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
#price { #price {
font-weight: bold; font-weight: bold;
} }

View File

@ -11,7 +11,7 @@ customElements.define('price-view', class extends HTMLElement {
shadow.innerHTML = ` shadow.innerHTML = `
<style> <style>
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Cardo', sans-serif; }
#price { #price {
font-weight: bold; font-weight: bold;
} }

View File

@ -9,9 +9,9 @@
facebook-id="{{a.facebook_id.as_deref().unwrap_or_default()}}" facebook-id="{{a.facebook_id.as_deref().unwrap_or_default()}}"
> >
{% if h.is_above_user(account) -%} {% if h.is_above_user(account) -%}
<a id="editService" slot="editService" href="/account/business-items" title="Moje usługi" class="btn"> <button id="editService" slot="editService" title="Moje usługi" class="">
Edytuj usługi Edytuj usługi
</a> </button>
{%- endif %} {%- endif %}
</account-view> </account-view>
{% when None %} {% when None %}

View File

@ -14,12 +14,12 @@
<main> <main>
{% match error %} {% match error %}
{% when Some with (e) %} {% when Some with (e) %}
<error-message message="{{e}"></error-message> <error-message message="{{e}}"></error-message>
{% when None %} {% when None %}
<error-message></error-message> <error-message></error-message>
{% endmatch %} {% endmatch %}
<article>
{% include "nav.html" %} {% include "nav.html" %}
<article>
{% block content %}{% endblock %} {% block content %}{% endblock %}
</article> </article>
<privacy-policy-bar></privacy-policy-bar> <privacy-policy-bar></privacy-policy-bar>

View File

@ -0,0 +1,23 @@
{% extends "../base.html" %}
{% block content %}
<marketplace-offers>
<h2>Edycja oferty</h2>
<offer-form
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 %}
></offer-form>
</marketplace-offers>
{% endblock %}

View File

@ -1,17 +1,17 @@
{% extends "../base.html" %} {% extends "../base.html" %}
{% block content %} {% block content %}
<marketplace-offers
{% if account.is_some() %} {% match account %}
<section> {% when Some with (a) %}
<a href="/account/offers" class="btn">Opublikuj</a> account-id="{{a.id}}"
</section> {% when None %}
{% endif %} {% endmatch %}
>
<marketplace-offers>
<h2 style="text-align: center">Rzeczy wystawione na sprzedaż</h2> <h2 style="text-align: center">Rzeczy wystawione na sprzedaż</h2>
{% for offer in my_offers %} {% for offer in my_offers %}
<user-edit-offer <user-edit-offer
slot="offer"
state="{{offer.state.as_str()}}" state="{{offer.state.as_str()}}"
offer-id="{{offer.id}}" offer-id="{{offer.id}}"
description="{{offer.description}}" description="{{offer.description}}"
@ -60,6 +60,7 @@
{% for offer in offers %} {% for offer in offers %}
<marketplace-offer <marketplace-offer
slot="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}}"

View File

@ -1,23 +1,16 @@
<ow-nav> <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"> <ow-path path="/zero-waste" selected="{{ page.select_marketplace() }}" title="Za darmo">
<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>
{# <ow-path path="/zero-waste" selected="{{ page.select_marketplace() }}" title="Za darmo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve">
<path d="M21.787 265.97c0-93.686 60.433-175.027 147.281-203.282l-24.251 32.157c-3.623 4.804-2.666 11.634 2.137 15.256a10.84 10.84 0 0 0 6.551 2.197c3.302 0 6.565-1.496 8.706-4.335l42.916-56.904a10.893 10.893 0 0 0-5.155-16.861l-67.399-23.176c-5.69-1.954-11.888 1.07-13.844 6.759-1.955 5.69 1.07 11.888 6.759 13.844l33.485 11.515c-39.951 13.768-75.875 38.241-103.475 70.935C19.71 156.465 0 210.409 0 265.97c0 27.261 4.631 54.007 13.765 79.493 1.595 4.45 5.786 7.221 10.255 7.221 1.22 0 2.461-.207 3.674-.642 5.664-2.029 8.609-8.266 6.58-13.93-8.285-23.121-12.487-47.393-12.487-72.142zM414.025 402.991c-4.588-3.893-11.461-3.332-15.356 1.255-40.749 48.005-100.185 75.538-163.068 75.538-53.27 0-104.061-19.763-143.119-54.987l40.061 4.936c5.965.74 11.408-3.508 12.143-9.48.735-5.971-3.509-11.408-9.48-12.143l-70.739-8.715a10.896 10.896 0 0 0-12.025 12.895l13.628 69.958c1.013 5.202 5.572 8.813 10.681 8.813.691 0 1.391-.066 2.095-.204 5.905-1.15 9.76-6.871 8.609-12.776l-6.743-34.615c42.647 37.25 97.454 58.105 154.886 58.105 69.292 0 134.783-30.334 179.679-83.226 3.898-4.587 3.334-11.462-1.252-15.354zM509.325 288.789c-3.949-4.538-10.829-5.017-15.369-1.069l-27.12 23.593a236.333 236.333 0 0 0 4.366-45.343c0-55.56-19.71-109.503-55.497-151.896-35.361-41.885-84.382-70.28-138.033-79.955-5.917-1.072-11.586 2.866-12.653 8.788-1.068 5.921 2.866 11.586 8.787 12.654C375.561 73.91 449.415 162.4 449.415 265.969c0 14.664-1.487 29.157-4.413 43.346l-15.46-36.465c-2.349-5.539-8.745-8.124-14.281-5.777-5.539 2.349-8.126 8.742-5.778 14.281l27.821 65.619a10.89 10.89 0 0 0 17.179 3.966l53.772-46.781c4.54-3.95 5.018-10.83 1.07-15.369z"/> <path d="M21.787 265.97c0-93.686 60.433-175.027 147.281-203.282l-24.251 32.157c-3.623 4.804-2.666 11.634 2.137 15.256a10.84 10.84 0 0 0 6.551 2.197c3.302 0 6.565-1.496 8.706-4.335l42.916-56.904a10.893 10.893 0 0 0-5.155-16.861l-67.399-23.176c-5.69-1.954-11.888 1.07-13.844 6.759-1.955 5.69 1.07 11.888 6.759 13.844l33.485 11.515c-39.951 13.768-75.875 38.241-103.475 70.935C19.71 156.465 0 210.409 0 265.97c0 27.261 4.631 54.007 13.765 79.493 1.595 4.45 5.786 7.221 10.255 7.221 1.22 0 2.461-.207 3.674-.642 5.664-2.029 8.609-8.266 6.58-13.93-8.285-23.121-12.487-47.393-12.487-72.142zM414.025 402.991c-4.588-3.893-11.461-3.332-15.356 1.255-40.749 48.005-100.185 75.538-163.068 75.538-53.27 0-104.061-19.763-143.119-54.987l40.061 4.936c5.965.74 11.408-3.508 12.143-9.48.735-5.971-3.509-11.408-9.48-12.143l-70.739-8.715a10.896 10.896 0 0 0-12.025 12.895l13.628 69.958c1.013 5.202 5.572 8.813 10.681 8.813.691 0 1.391-.066 2.095-.204 5.905-1.15 9.76-6.871 8.609-12.776l-6.743-34.615c42.647 37.25 97.454 58.105 154.886 58.105 69.292 0 134.783-30.334 179.679-83.226 3.898-4.587 3.334-11.462-1.252-15.354zM509.325 288.789c-3.949-4.538-10.829-5.017-15.369-1.069l-27.12 23.593a236.333 236.333 0 0 0 4.366-45.343c0-55.56-19.71-109.503-55.497-151.896-35.361-41.885-84.382-70.28-138.033-79.955-5.917-1.072-11.586 2.866-12.653 8.788-1.068 5.921 2.866 11.586 8.787 12.654C375.561 73.91 449.415 162.4 449.415 265.969c0 14.664-1.487 29.157-4.413 43.346l-15.46-36.465c-2.349-5.539-8.745-8.124-14.281-5.777-5.539 2.349-8.126 8.742-5.778 14.281l27.821 65.619a10.89 10.89 0 0 0 17.179 3.966l53.772-46.781c4.54-3.95 5.018-10.83 1.07-15.369z"/>
<path d="M188.028 130.89a50.918 50.918 0 0 0-4.574-.207c-21.752 0-40.793 14.02-47.564 34.164-17.981 7.019-30.042 24.311-30.042 44.123a47.444 47.444 0 0 0 11.151 30.53 67.61 67.61 0 0 0-1.206 12.72c0 36.454 29.03 66.112 64.714 66.112 6.157 0 12.253-.886 18.119-2.63.11-.033.222-.059.333-.092a529.09 529.09 0 0 1-.877 107.238 10.891 10.891 0 0 0 10.829 12.088h53.379a10.896 10.896 0 0 0 10.828-12.088 529.532 529.532 0 0 1-1.495-100.106c17.407-.964 33.696-8.121 46.232-20.417 11.715-11.489 19.022-26.339 21-42.412 15.913-7.877 26.498-24.415 26.498-42.538 0-23.342-16.958-42.798-39.2-46.695-7.81-27.348-31.586-46.41-59.334-46.41a57.88 57.88 0 0 0-4.029.141c-9.275-8.797-21.549-13.741-34.507-13.741-16.076.004-30.876 7.585-40.255 20.22zm32.91 282.263a550.944 550.944 0 0 0-.314-99.862 68.285 68.285 0 0 1 20.675 4.615 71.561 71.561 0 0 0 8.631 2.762 551.612 551.612 0 0 0 .333 92.485h-29.325zm-17.889-265.307c4.906-9.489 14.577-15.385 25.237-15.385 8.511 0 16.501 3.771 21.924 10.348a10.89 10.89 0 0 0 10.256 3.805 37.464 37.464 0 0 1 6.354-.551c19.726 0 36.41 15.343 39.668 36.483.83 5.388 5.53 9.307 10.966 9.232l.493-.016c14.127 0 25.62 11.493 25.62 25.62 0 11.212-7.49 21.298-18.217 24.529a10.896 10.896 0 0 0-7.749 10.2c-.573 27.004-23.015 48.973-50.026 48.973-6.323 0-12.48-1.161-18.3-3.45-10.586-4.165-21.761-6.264-32.818-6.264-8.172 0-16.283 1.147-24.042 3.455a41.728 41.728 0 0 1-11.907 1.726c-23.671 0-42.927-19.884-42.927-44.325 0-4.38.624-8.722 1.855-12.905a10.895 10.895 0 0 0-3.206-11.212 25.651 25.651 0 0 1-8.595-19.135c0-11.949 8.114-22.2 19.73-24.93a10.893 10.893 0 0 0 8.207-8.556c2.555-13.336 14.279-23.015 27.881-23.015 2.429 0 4.843.307 7.174.914a10.9 10.9 0 0 0 12.422-5.541z"/> <path d="M188.028 130.89a50.918 50.918 0 0 0-4.574-.207c-21.752 0-40.793 14.02-47.564 34.164-17.981 7.019-30.042 24.311-30.042 44.123a47.444 47.444 0 0 0 11.151 30.53 67.61 67.61 0 0 0-1.206 12.72c0 36.454 29.03 66.112 64.714 66.112 6.157 0 12.253-.886 18.119-2.63.11-.033.222-.059.333-.092a529.09 529.09 0 0 1-.877 107.238 10.891 10.891 0 0 0 10.829 12.088h53.379a10.896 10.896 0 0 0 10.828-12.088 529.532 529.532 0 0 1-1.495-100.106c17.407-.964 33.696-8.121 46.232-20.417 11.715-11.489 19.022-26.339 21-42.412 15.913-7.877 26.498-24.415 26.498-42.538 0-23.342-16.958-42.798-39.2-46.695-7.81-27.348-31.586-46.41-59.334-46.41a57.88 57.88 0 0 0-4.029.141c-9.275-8.797-21.549-13.741-34.507-13.741-16.076.004-30.876 7.585-40.255 20.22zm32.91 282.263a550.944 550.944 0 0 0-.314-99.862 68.285 68.285 0 0 1 20.675 4.615 71.561 71.561 0 0 0 8.631 2.762 551.612 551.612 0 0 0 .333 92.485h-29.325zm-17.889-265.307c4.906-9.489 14.577-15.385 25.237-15.385 8.511 0 16.501 3.771 21.924 10.348a10.89 10.89 0 0 0 10.256 3.805 37.464 37.464 0 0 1 6.354-.551c19.726 0 36.41 15.343 39.668 36.483.83 5.388 5.53 9.307 10.966 9.232l.493-.016c14.127 0 25.62 11.493 25.62 25.62 0 11.212-7.49 21.298-18.217 24.529a10.896 10.896 0 0 0-7.749 10.2c-.573 27.004-23.015 48.973-50.026 48.973-6.323 0-12.48-1.161-18.3-3.45-10.586-4.165-21.761-6.264-32.818-6.264-8.172 0-16.283 1.147-24.042 3.455a41.728 41.728 0 0 1-11.907 1.726c-23.671 0-42.927-19.884-42.927-44.325 0-4.38.624-8.722 1.855-12.905a10.895 10.895 0 0 0-3.206-11.212 25.651 25.651 0 0 1-8.595-19.135c0-11.949 8.114-22.2 19.73-24.93a10.893 10.893 0 0 0 8.207-8.556c2.555-13.336 14.279-23.015 27.881-23.015 2.429 0 4.843.307 7.174.914a10.9 10.9 0 0 0 12.422-5.541z"/>
<path d="M264.285 208.492c9.069 6.257 14.271 15.333 14.271 24.897 0 6.017 4.878 10.894 10.894 10.894 6.015 0 10.894-4.877 10.894-10.894 0-16.834-8.632-32.445-23.683-42.83-4.953-3.416-11.736-2.171-15.153 2.78-3.419 4.951-2.174 11.736 2.777 15.153zM234.981 191.995l.338.001h.085c5.977 0 10.846-4.823 10.891-10.811.046-6.017-4.794-10.931-10.81-10.976l-.503-.002c-6.017 0-10.894 4.877-10.894 10.894.001 6.016 4.877 10.894 10.893 10.894z"/> <path d="M264.285 208.492c9.069 6.257 14.271 15.333 14.271 24.897 0 6.017 4.878 10.894 10.894 10.894 6.015 0 10.894-4.877 10.894-10.894 0-16.834-8.632-32.445-23.683-42.83-4.953-3.416-11.736-2.171-15.153 2.78-3.419 4.951-2.174 11.736 2.777 15.153zM234.981 191.995l.338.001h.085c5.977 0 10.846-4.823 10.891-10.811.046-6.017-4.794-10.931-10.81-10.976l-.503-.002c-6.017 0-10.894 4.877-10.894 10.894.001 6.016 4.877 10.894 10.893 10.894z"/>
</svg> </svg>
</ow-path> #} </ow-path>
#}
{% if page.is_public() -%} {% if page.is_public() -%}
<ow-path path="/marketplace" selected="{{ page.select_marketplace() }}" title="Targ"> <ow-path path="/marketplace" selected="{{ page.select_marketplace() }}" title="Targ" slot="left">
<svg viewBox="0 0 484.909 484.909" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"> <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="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="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"/>
@ -26,14 +19,14 @@
</svg> </svg>
<div>Targ</div> <div>Targ</div>
</ow-path> </ow-path>
<ow-path path="/" selected="{{ page.select_index() }}" title="Lokalne usługi"> <ow-path path="/local-businesses" selected="{{ page.select_index() }}" title="Lokalne usługi" slot="left">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.999 511.999" xml:space="preserve"> <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"/> <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"/> <circle cx="304.734" cy="129.707" r="34.286"/>
</svg> </svg>
<div>Lokalne Usługi</div> <div>Lokalne Usługi</div>
</ow-path> </ow-path>
<ow-path path="/news" selected="{{ page.select_news() }}" title="Aktualności"> <ow-path path="/news" selected="{{ page.select_news() }}" title="Aktualności" slot="left">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 508 508" xml:space="preserve"> <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="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="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"/>
@ -41,7 +34,7 @@
</svg> </svg>
<div>Aktualności</div> <div>Aktualności</div>
</ow-path> </ow-path>
<ow-path path="/account" selected="{{ page.select_account() }}" title="Konto"> <ow-path path="/account" selected="{{ page.select_account() }}" title="Konto" slot="right">
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"> <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"/> <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> </svg>
@ -49,7 +42,7 @@
</ow-path> </ow-path>
{% if h.is_admin(account) -%} {% if h.is_admin(account) -%}
<ow-path path="/admin" selected="{{ page.select_admin_news() }}" title="Admin"> <ow-path path="/admin" selected="{{ page.select_admin_news() }}" title="Admin" slot="right">
<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"> <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="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="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"/>
@ -62,7 +55,7 @@
{%- endif %} {%- endif %}
{% else if page.is_admin() -%} {% else if page.is_admin() -%}
<ow-path path="/admin/news" selected="{{ page.select_admin_news() }}" title="Zarządzaj aktualnościami"> <ow-path path="/admin/news" selected="{{ page.select_admin_news() }}" title="Zarządzaj aktualnościami" slot="left">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 508 508" xml:space="preserve"> <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="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="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"/>
@ -71,8 +64,11 @@
<div>Aktualności</div> <div>Aktualności</div>
</ow-path> </ow-path>
<ow-path path="/admin/businesses" selected="{{ page.select_admin_businesses() }}" <ow-path
title="Zarządzaj usługami"> path="/admin/businesses" selected="{{ page.select_admin_businesses() }}"
title="Zarządzaj usługami"
slot="left"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.999 511.999" xml:space="preserve"> <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"/> <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"/> <circle cx="304.734" cy="129.707" r="34.286"/>
@ -80,8 +76,11 @@
<div>Lokalne Usługi</div> <div>Lokalne Usługi</div>
</ow-path> </ow-path>
<ow-path path="/admin/offers" selected="{{ page.select_admin_offers() }}" <ow-path
title="Zarządzaj ofertami"> path="/admin/offers" selected="{{ page.select_admin_offers() }}"
title="Zarządzaj ofertami"
slot="left"
>
<svg viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
<path <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" 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"

View File

@ -182,11 +182,17 @@ pub struct UpdateLocalBusinessInput {
pub description: String, pub description: String,
} }
#[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
pub enum PriceRange { pub enum PriceRange {
#[default]
Free, Free,
Fixed { value: i32 }, Fixed {
Range { min: i32, max: i32 }, value: i32,
},
Range {
min: i32,
max: i32,
},
} }
impl From<(i32, i32)> for PriceRange { impl From<(i32, i32)> for PriceRange {
@ -286,7 +292,7 @@ pub struct CreateLocalBusinessItemInput {
pub name: String, pub name: String,
pub price: i64, pub price: i64,
pub item_order: i32, pub item_order: i32,
pub picture_url: String, pub picture_url: Option<String>,
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -97,7 +97,7 @@ impl Page {
pub struct BusinessItemInput { pub struct BusinessItemInput {
pub name: String, pub name: String,
pub price: u32, pub price: u32,
pub picture_url: String, pub picture_url: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -250,6 +250,21 @@ pub struct Offer {
pub contacts: Vec<Arc<db::ContactInfo>>, pub contacts: Vec<Arc<db::ContactInfo>>,
} }
impl Default for Offer {
fn default() -> Self {
Self {
id: 0,
owner_id: 0,
price_range: Default::default(),
description: "".to_string(),
picture_url: "".to_string(),
state: Default::default(),
created_at: chrono::Utc::now().naive_utc(),
contacts: vec![],
}
}
}
impl From<(db::Offer, &Vec<Arc<db::ContactInfo>>)> for Offer { impl From<(db::Offer, &Vec<Arc<db::ContactInfo>>)> for Offer {
fn from((offer, contacts): (db::Offer, &Vec<Arc<db::ContactInfo>>)) -> Self { fn from((offer, contacts): (db::Offer, &Vec<Arc<db::ContactInfo>>)) -> Self {
let db::Offer { let db::Offer {

View File

@ -77,3 +77,14 @@ RETURNING id, login, email, pass, facebook_id, account_type
} }
}) })
} }
/* async fn delete_all_accounts() {
let _ = sqlx::query_as::<sqlx::Postgres, (i32,)>(
r#"
delete from contacts;
delete from local_business_items;
delete from local_businesses;
delete from accounts;
"#,
);
}*/

View File

@ -300,11 +300,13 @@ RETURNING
.bind(input.local_business_id) .bind(input.local_business_id)
.bind(&input.name) .bind(&input.name)
.bind(input.price) .bind(input.price)
.bind(if input.picture_url.is_empty() { .bind(
format!("--{}", uuid::Uuid::new_v4()) input
} else { .picture_url
input.picture_url.clone() .as_deref()
}) .map(String::from)
.unwrap_or_else(|| format!("--{}", uuid::Uuid::new_v4())),
)
.bind(input.item_order) .bind(input.item_order)
.fetch_one(t) .fetch_one(t)
.await .await

View File

@ -104,6 +104,9 @@ pub enum Error {
offer_id: i32, offer_id: i32,
state: db::OfferState, state: db::OfferState,
}, },
Offer {
offer_id: i32,
},
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;

View File

@ -56,6 +56,34 @@ ORDER BY id DESC
}) })
} }
#[tracing::instrument]
pub async fn offer_by_id(t: &mut T<'_>, account_id: i32, offer_id: i32) -> Result<db::Offer> {
sqlx::query_as(
r#"
SELECT
id,
owner_id,
price_range,
description,
picture_url,
state,
created_at
FROM offers
WHERE owner_id = $1 AND id = $2
LIMIT 1
"#,
)
.bind(account_id)
.bind(offer_id)
.fetch_one(t)
.await
.map_err(|e| {
error!("{e}");
dbg!(e);
Error::Offer { offer_id }
})
}
#[tracing::instrument] #[tracing::instrument]
pub async fn account_offers(t: &mut T<'_>, account_id: i32) -> Result<Vec<db::Offer>> { pub async fn account_offers(t: &mut T<'_>, account_id: i32) -> Result<Vec<db::Offer>> {
sqlx::query_as( sqlx::query_as(

View File

@ -159,7 +159,7 @@ async fn new_business_item(
name: form.name, name: form.name,
price: form.price, price: form.price,
item_order: form.item_order, item_order: form.item_order,
picture_url: form.picture_url, picture_url: Some(form.picture_url),
}, },
) )
.await .await

View File

@ -12,6 +12,7 @@ use serde::Serialize;
use crate::model::db; use crate::model::db;
use crate::model::view::{self, Page}; use crate::model::view::{self, Page};
use crate::routes::HttpResult;
use crate::view::Helper; use crate::view::Helper;
#[derive(Default, Debug, Serialize, Template)] #[derive(Default, Debug, Serialize, Template)]
@ -36,6 +37,11 @@ pub async fn render_index() -> HttpResponse {
) )
} }
#[get("/")]
async fn index(req: HttpRequest) -> HttpResult {
HttpResult::goto(&req, "/marketplace")
}
pub fn configure(config: &mut ServiceConfig) { pub fn configure(config: &mut ServiceConfig) {
std::fs::create_dir_all("./uploads").expect("Failed to create ./uploads directory"); std::fs::create_dir_all("./uploads").expect("Failed to create ./uploads directory");
@ -43,15 +49,18 @@ pub fn configure(config: &mut ServiceConfig) {
account::configure(config); account::configure(config);
news::configure(config); news::configure(config);
marketplace::configure(config); marketplace::configure(config);
businesses::configure(config);
config config
.service(Files::new("/uploads", "./uploads")) .service(Files::new("/uploads", "./uploads"))
.service(Files::new("/assets/images", "./assets/images")) .service(Files::new("/assets/images", "./assets/images"))
.service(Files::new("/assets/fonts", "./assets/fonts"))
.service(Files::new("/assets/css", "./client/dist")) .service(Files::new("/assets/css", "./client/dist"))
.service( .service(
Files::new("/assets/js", "./client/dist") Files::new("/assets/js", "./client/dist")
.use_etag(true) .use_etag(true)
.prefer_utf8(true) .prefer_utf8(true)
.show_files_listing(), .show_files_listing(),
); )
businesses::configure(config); .service(index);
} }

View File

@ -1,5 +1,3 @@
use std::collections::HashMap;
use actix_http::StatusCode; use actix_http::StatusCode;
use actix_web::web::{Data, Path, ServiceConfig}; use actix_web::web::{Data, Path, ServiceConfig};
use actix_web::{get, post, web, HttpRequest, HttpResponse}; use actix_web::{get, post, web, HttpRequest, HttpResponse};
@ -237,6 +235,20 @@ impl RegisterPage {
} }
} }
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
struct RegisterForm {
email: String,
login: String,
password: String,
facebook_id: Option<String>,
account_type: AccountType,
items: Option<Vec<view::BusinessItemInput>>,
contacts: Option<Vec<view::CreateContactInfoInput>>,
name: Option<String>,
description: Option<String>,
}
#[post("/register")] #[post("/register")]
#[tracing::instrument] #[tracing::instrument]
async fn save_account_details( async fn save_account_details(
@ -245,12 +257,11 @@ async fn save_account_details(
db: Data<PgPool>, db: Data<PgPool>,
id: Identity, id: Identity,
) -> HttpResult { ) -> HttpResult {
let mut form = form.into_inner(); let form = form.into_inner();
dbg!(&form); dbg!(&form);
process_items(form.items.get_or_insert_default(), form.names);
let pool = db.into_inner(); let pool = db.into_inner();
if form.account_type == db::AccountType::Admin { if form.account_type == AccountType::Admin {
return HttpResult::err(&req, routes::Error::XSS); return HttpResult::err(&req, routes::Error::XSS);
} }
@ -300,6 +311,48 @@ async fn save_account_details(
} }
}; };
if let Err(e) = queries::create_contact(
&mut t,
db::CreateContactInput {
owner_id: account.id,
contact_type: "email".into(),
content: account.email.clone(),
},
)
.await
{
dbg!(e);
t.rollback().await.unwrap();
return RegisterTemplate::bad_request(
&req,
"Problem z utworzeniem danych kontaktowych konta",
Page::Register,
);
}
if let Some(contacts) = form.contacts {
for contact in contacts {
if let Err(e) = queries::create_contact(
&mut t,
db::CreateContactInput {
owner_id: account.id,
contact_type: contact.contact_type,
content: contact.content,
},
)
.await
{
dbg!(e);
t.rollback().await.unwrap();
return RegisterTemplate::bad_request(
&req,
"Problem z utworzeniem danych kontaktowych konta",
Page::Register,
);
}
}
}
if matches!(form.account_type, db::AccountType::Business) { if matches!(form.account_type, db::AccountType::Business) {
let name = form.name.as_deref().unwrap_or_default(); let name = form.name.as_deref().unwrap_or_default();
let owner_id = account.id; let owner_id = account.id;
@ -325,23 +378,13 @@ async fn save_account_details(
for (idx, item) in form.items.unwrap_or_default().into_iter().enumerate() { for (idx, item) in form.items.unwrap_or_default().into_iter().enumerate() {
not_xss!(&req, &item.name, t); not_xss!(&req, &item.name, t);
not_xss!(&req, &item.picture_url, t); not_xss!(&req, item.picture_url.as_deref().unwrap_or_default(), t);
let res: sqlx::Result<db::LocalBusinessItem> = sqlx::query_as( let picture_url = match item.picture_url {
r#" Some(url) => Some(if url.is_empty() {
INSERT INTO local_business_items (local_business_id, name, price, item_order, picture_url)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, local_business_id, name, price, item_order, picture_url
"#,
)
.bind(business.id)
.bind(&item.name)
.bind(item.price as i32)
.bind(idx as i32)
.bind(if item.picture_url.is_empty() {
format!("--{}", uuid::Uuid::new_v4()) format!("--{}", uuid::Uuid::new_v4())
} else { } else {
let name = item.picture_url.split('/').last().unwrap_or_default(); let name = url.split('/').last().unwrap_or_default();
let dir = utils::item_picture_write_dir(format!("{}", account.id)); let dir = utils::item_picture_write_dir(format!("{}", account.id));
if let Err(e) = std::fs::create_dir_all(&dir) { if let Err(e) = std::fs::create_dir_all(&dir) {
error!("{e} {:?}", dir); error!("{e} {:?}", dir);
@ -354,8 +397,8 @@ RETURNING id, local_business_id, name, price, item_order, picture_url
); );
} }
let path = dir.join(name); let path = dir.join(name);
if let Err(e) = std::fs::rename(format!(".{}", item.picture_url), &path) { if let Err(e) = std::fs::rename(format!(".{}", url), &path) {
error!("{e} {:?}", item.picture_url); error!("{e} {:?}", url);
dbg!(e); dbg!(e);
t.rollback().await.unwrap(); t.rollback().await.unwrap();
return RegisterTemplate::bad_request( return RegisterTemplate::bad_request(
@ -368,13 +411,24 @@ RETURNING id, local_business_id, name, price, item_order, picture_url
path.strip_prefix('.') path.strip_prefix('.')
.map(String::from) .map(String::from)
.unwrap_or_else(|| path) .unwrap_or_else(|| path)
}) }),
.fetch_one(&mut t) None => None,
};
let res = queries::create_item(
&mut t,
db::CreateLocalBusinessItemInput {
local_business_id: business.id,
name: item.name,
price: item.price as i64,
item_order: idx as i32,
picture_url,
},
)
.await; .await;
match res { match res {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
error!("{e}"); error!("{e:?}");
dbg!(e); dbg!(e);
t.rollback().await.unwrap(); t.rollback().await.unwrap();
return RegisterTemplate::bad_request( return RegisterTemplate::bad_request(
@ -427,68 +481,6 @@ async fn display_register_page(
) )
} }
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
struct RegisterForm {
email: String,
login: String,
password: String,
facebook_id: Option<String>,
account_type: db::AccountType,
items: Option<Vec<view::BusinessItemInput>>,
contacts: Option<Vec<view::CreateContactInfoInput>>,
name: Option<String>,
description: Option<String>,
#[serde(flatten)]
names: HashMap<String, String>,
}
#[tracing::instrument]
fn process_items(items: &mut Vec<view::BusinessItemInput>, names: HashMap<String, String>) {
let mut h = names
.into_iter()
.filter_map(|(name, value)| {
let mut name = name
.strip_prefix("items")?
.split('[')
.filter(|s| !s.is_empty())
.map(|s| s.strip_suffix(']').unwrap_or(s));
let idx: u16 = name.next().and_then(|s| s.parse().ok())?;
match name.next() {
Some(s @ ("name" | "price" | "picture_url")) => Some((idx, s.to_string(), value)),
_ => None,
}
})
.fold(
HashMap::with_capacity(60),
|mut memo, (idx, field, value)| {
let item = memo
.entry(idx)
.or_insert_with(view::BusinessItemInput::default);
match field.as_str() {
"name" => {
item.name = value;
}
"price" => {
item.price = value.parse().unwrap_or_default();
}
"picture_url" => {
item.picture_url = value;
}
_ => {}
};
memo
},
);
let mut ids = { h.keys().copied().collect::<Vec<_>>() };
ids.sort();
for id in ids {
if let Some(item) = h.remove(&id) {
items.push(item);
}
}
}
#[post("/upload")] #[post("/upload")]
async fn upload( async fn upload(
req: HttpRequest, req: HttpRequest,
@ -523,30 +515,4 @@ mod tests {
use crate::model::view; use crate::model::view;
use crate::model::view::BusinessItemInput; use crate::model::view::BusinessItemInput;
impl BusinessItemInput {
pub fn new<S: Into<String>, P: Into<String>>(name: S, price: u32, picture_url: P) -> Self {
Self {
name: name.into(),
price,
picture_url: picture_url.into(),
}
}
}
#[test]
fn parse_items() {
let mut items = Vec::with_capacity(0);
let mut names: HashMap<String, String> = HashMap::with_capacity(4);
names.insert("items[0][name]".into(), "a".into());
names.insert("items[0][price]".into(), "10".into());
names.insert("items[1][name]".into(), "b".into());
names.insert("items[1][price]".into(), "20".into());
super::process_items(&mut items, names);
let expected = vec![
BusinessItemInput::new("a", 10, "/a"),
BusinessItemInput::new("b", 20, "/b"),
];
assert_eq!(items, expected);
}
} }

View File

@ -9,7 +9,7 @@ use crate::queries;
use crate::routes::unrestricted::IndexTemplate; use crate::routes::unrestricted::IndexTemplate;
use crate::routes::{HttpResult, Identity}; use crate::routes::{HttpResult, Identity};
#[get("/")] #[get("/local-businesses")]
#[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();

View File

@ -73,6 +73,82 @@ async fn marketplace(req: HttpRequest, db: Data<PgPool>, id: Identity) -> HttpRe
) )
} }
pub fn configure(config: &mut ServiceConfig) { #[derive(Default, Serialize, Template)]
config.service(web::scope("/marketplace").service(marketplace)); #[template(path = "./marketplace/edit.html")]
struct EditOfferTemplate {
account: Option<db::Account>,
error: Option<String>,
page: Page,
#[serde(skip)]
h: Helper,
offer: view::Offer,
}
#[get("/{id}/edit")]
async fn edit_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,
EditOfferTemplate {
page: Page::Marketplace,
account,
error: Some("Oferta nie istnieje".into()),
..Default::default()
},
)
}
};
let offer = match queries::offer_by_id(&mut t, account.id, offer_id).await {
Ok(offer) => offer,
Err(e) => {
dbg!(e);
return HttpResult::res(
&req,
StatusCode::NOT_FOUND,
EditOfferTemplate {
page: Page::Marketplace,
account: Some(account),
error: Some("Oferta nie istnieje".into()),
..Default::default()
},
);
}
};
t.commit().await.ok();
HttpResult::res(
&req,
StatusCode::OK,
EditOfferTemplate {
page: Page::Marketplace,
account: Some(account),
offer: view::Offer::from((offer, &vec![])),
..Default::default()
},
)
}
pub fn configure(config: &mut ServiceConfig) {
config.service(
web::scope("/marketplace")
.service(edit_marketplace)
.service(marketplace),
);
} }