From a9669432ec79563a2be5ccb5e37aa8a75fc66da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Wo=C5=BAniak?= Date: Thu, 28 Jul 2022 15:28:28 +0200 Subject: [PATCH] Improve register company --- client/src/app.js | 21 +- .../business-items/business-item-editor.js | 2 +- client/src/contacts/contact-info-editor.js | 19 +- client/src/contacts/contact-info.js | 18 +- client/src/contacts/contact-type-icon.js | 22 +- client/src/contacts/edit-contact-info.js | 9 +- client/src/ow-account/account-view.js | 1 + client/src/ow-account/ow-account.js | 77 +-- client/src/poly.js | 22 + client/src/register-form.js | 266 ++++++--- client/src/register-form/model.js | 39 +- .../register-form/register-account-type.js | 12 +- .../register-business-contact-form.js | 22 - .../register-business-contacts-form.js | 65 +++ ...-row.js => register-business-item-form.js} | 95 ++- .../register-business-items-form.js | 32 +- .../register-business-submit-form.js | 111 ++-- client/src/shared.js | 29 + client/src/shared/error-message.js | 33 ++ client/src/shared/image-input.js | 5 + server/assets/templates/account.html | 6 - server/assets/templates/base.html | 3 +- server/assets/templates/register.html | 9 + server/assets/templates/sign-in.html | 6 + server/src/model/view.rs | 1 + server/src/routes/unrestricted/account.rs | 546 ++++++++++-------- 26 files changed, 886 insertions(+), 585 deletions(-) create mode 100644 client/src/poly.js delete mode 100644 client/src/register-form/register-business-contact-form.js create mode 100644 client/src/register-form/register-business-contacts-form.js rename client/src/register-form/{register-item-form-row.js => register-business-item-form.js} (74%) create mode 100644 client/src/shared/error-message.js create mode 100644 server/assets/templates/register.html create mode 100644 server/assets/templates/sign-in.html diff --git a/client/src/app.js b/client/src/app.js index d29ca55..963a563 100644 --- a/client/src/app.js +++ b/client/src/app.js @@ -1,3 +1,7 @@ +import "./poly.js"; + +import "./shared/error-message.js"; + import "./shared/rich-text-editor.js"; import "./shared/form-navigation.js"; import "./shared/image-popup.js"; @@ -8,6 +12,7 @@ import "./shared/price/price-input.js"; import "./shared/price/price-view.js"; import "./ow-account/ow-account.js"; +import "./ow-account/account-view.js"; import "./local-businesses/local-businesses.js"; import "./local-businesses/local-business-item.js"; @@ -16,12 +21,12 @@ import "./local-businesses/local-business.js"; import "./login-form.js"; import "./register-form.js"; import "./register-form/register-business-account-form"; -import "./register-form/register-item-form-row.js"; +import "./register-form/register-business-item-form.js"; import "./register-form/register-business-items-form.js"; import "./register-form/register-business-details-form.js"; import "./register-form/register-account-type.js"; import "./register-form/register-user-account-form.js"; -import "./register-form/register-business-contact-form.js"; +import "./register-form/register-business-contacts-form.js"; import "./register-form/register-business-submit-form.js"; import "./business-items/business-item.js"; @@ -64,15 +69,3 @@ if (!document.querySelector('#facebook-jssdk')) { js.src = "https://connect.facebook.net/en_US/sdk.js"; document.head.appendChild(js); } - -Object.prototype.entry = function (key) { - const owner = this; - return { - owner, orElse(v) { - if (owner[key] === undefined) owner[key] = v; - return owner[key]; - }, get() { - return owner[key]; - } - } -}; diff --git a/client/src/business-items/business-item-editor.js b/client/src/business-items/business-item-editor.js index e197f62..3b2fea2 100644 --- a/client/src/business-items/business-item-editor.js +++ b/client/src/business-items/business-item-editor.js @@ -1,6 +1,6 @@ import { Component, FORM_STYLE } from "../shared"; -import "../register-form/register-item-form-row"; +import "../register-form/register-business-item-form"; customElements.define('business-item-editor', class extends Component { #idx; diff --git a/client/src/contacts/contact-info-editor.js b/client/src/contacts/contact-info-editor.js index 125371b..b3ea035 100644 --- a/client/src/contacts/contact-info-editor.js +++ b/client/src/contacts/contact-info-editor.js @@ -2,7 +2,7 @@ import { Component, FORM_STYLE } from "../shared"; customElements.define('contact-info-editor', class extends Component { static get observedAttributes() { - return ['type', "contact-id", "content"]; + return ['type', "contact-id", "content", 'save']; } constructor() { @@ -26,6 +26,9 @@ customElements.define('contact-info-editor', class extends Component { article { margin: 8px; } + :host([save="false"]) #submit { + display: none; + } @media only screen and (min-device-width: 1000px) { article { margin: 0; @@ -41,14 +44,14 @@ customElements.define('contact-info-editor', class extends Component {
- +
- +
-
+
@@ -65,6 +68,7 @@ customElements.define('contact-info-editor', class extends Component { input.addEventListener('change', ev => { ev.stopPropagation(); this.#updateContactType(input.value, null); + this.content = input.value; }); input.addEventListener('keyup', ev => { ev.stopPropagation(); @@ -72,11 +76,12 @@ customElements.define('contact-info-editor', class extends Component { timeout = setTimeout(() => { timeout = null; this.#updateContactType(input.value, null); + this.content = input.value; }, 1000 / 3) }); } - this.shadowRoot.querySelector('#contact_type').addEventListener('change', ev => { + this.shadowRoot.querySelector('#type').addEventListener('change', ev => { ev.stopPropagation(); this.#updateContactType(null, ev.target.value); }); @@ -136,8 +141,8 @@ customElements.define('contact-info-editor', class extends Component { type = type || this.#resolveContactType(value); this.setAttribute('type', type); const icon = this.shadowRoot.querySelector('contact-type-icon'); - icon.setAttribute('contact-type', type); - this.shadowRoot.querySelector('#contact_type').value = type; + icon.type = type; + this.shadowRoot.querySelector('#type').value = type; } #resolveContactType(s) { diff --git a/client/src/contacts/contact-info.js b/client/src/contacts/contact-info.js index a92f8d8..153782a 100644 --- a/client/src/contacts/contact-info.js +++ b/client/src/contacts/contact-info.js @@ -2,7 +2,7 @@ import { Component } from "../shared"; customElements.define('contact-info', class extends Component { static get observedAttributes() { - return ['contact-type', 'content', 'contact-id', 'mode']; + return ['type', 'content', 'contact-id', 'mode']; } constructor() { @@ -24,13 +24,13 @@ customElements.define('contact-info', class extends Component {
- +
`); this.shadowRoot.querySelector('a').addEventListener('click', ev => { - if (this.contact_type === 'mobile') { + if (this.type === 'mobile') { ev.preventDefault(); const decoded = atob(this.content); if (this.#isMobile()) { @@ -44,16 +44,16 @@ customElements.define('contact-info', class extends Component { }); } - set contact_type(v) { + set type(v) { if (!v) return; - this.setAttribute('contact-type', v); - this.shadowRoot.querySelector('contact-type-icon').setAttribute('contact-type', v); + this.setAttribute('type', v); + this.shadowRoot.querySelector('contact-type-icon').type = v; this.#setHref(); } - get contact_type() { - return this.getAttribute('contact-type'); + get type() { + return this.getAttribute('type'); } set content(v) { @@ -90,7 +90,7 @@ customElements.define('contact-info', class extends Component { #createLinkPath() { const s = this.shadowRoot.querySelector('#content').textContent || ''; - switch (this.contact_type) { + switch (this.type) { case 'email': return `mailto:${ s }`; default: diff --git a/client/src/contacts/contact-type-icon.js b/client/src/contacts/contact-type-icon.js index 8db899b..58b59c5 100644 --- a/client/src/contacts/contact-type-icon.js +++ b/client/src/contacts/contact-type-icon.js @@ -3,7 +3,7 @@ import { Component } from "../shared"; customElements.define('contact-type-icon', class extends Component { static get observedAttributes() { - return ['contact-type']; + return ['type']; } constructor() { @@ -11,16 +11,16 @@ customElements.define('contact-type-icon',
@@ -49,7 +52,7 @@ customElements.define('edit-contact-info', class extends Component {
-
+
@@ -67,7 +70,7 @@ customElements.define('edit-contact-info', class extends Component { if (!info) return; form.contact_id = info.contact_id; - form.type = info.contact_type; + form.type = info.type; form.content = info.content; this.mode = 'edit'; diff --git a/client/src/ow-account/account-view.js b/client/src/ow-account/account-view.js index 32f2cf4..560c519 100644 --- a/client/src/ow-account/account-view.js +++ b/client/src/ow-account/account-view.js @@ -129,6 +129,7 @@ customElements.define('account-view', class extends Component { connectedCallback() { super.connectedCallback(); this.register_success = (location.search || '').includes('success'); + localStorage.removeItem('register'); } get name() { diff --git a/client/src/ow-account/ow-account.js b/client/src/ow-account/ow-account.js index 3bc6096..838ccc1 100644 --- a/client/src/ow-account/ow-account.js +++ b/client/src/ow-account/ow-account.js @@ -1,10 +1,8 @@ -import { Component, FORM_STYLE } from "../shared"; -import '../shared/facebook-button'; -import './account-view'; +import { Component, FORM_STYLE, goTo, historyDetails } from "../shared"; customElements.define('ow-account', class extends Component { static get observedAttributes() { - return ['mode', "id", "name", 'email', "facebook-id"] + return ['mode'] } constructor() { @@ -12,23 +10,15 @@ customElements.define('ow-account', class extends Component { -
- - + - `); this.shadowRoot.querySelector('#switch-login > a').addEventListener('click', ev => { ev.stopPropagation(); ev.preventDefault(); - this.mode = 'login'; + goTo('/login'); }); this.shadowRoot.querySelector('#switch-register > a').addEventListener('click', ev => { ev.stopPropagation(); ev.preventDefault(); - this.mode = 'register'; + goTo('/register/account-type'); }); this.addEventListener('facebook:account', ev => { ev.stopPropagation(); ev.preventDefault(); - this.mode = 'facebook'; }); } connectedCallback() { - if (this.mode === '') this.mode = 'login'; - this.name = this.name; - this.email = this.email; - this.facebook_id = this.facebook_id; + super.connectedCallback(); + const parts = historyDetails().parts; + switch (parts.first) { + case 'register': { + this.mode = 'register'; + break; + } + default: + this.mode = 'login'; + break; + } + } + + listenHistory = ({ parts }) => { + console.warn(parts); } attributeChangedCallback(name, oldV, newV) { @@ -90,31 +88,4 @@ customElements.define('ow-account', class extends Component { value = ['login', 'register', 'display'].includes(value) ? value : 'login'; this.setAttribute('mode', value); } - - get name() { - return this.getAttribute('name') || ''; - } - - set name(v) { - this.setAttribute('name', v); - this.shadowRoot.querySelector('account-view').name = v; - } - - get email() { - return this.getAttribute('email') || ''; - } - - set email(v) { - this.setAttribute('email', v); - this.shadowRoot.querySelector('account-view').email = v; - } - - get facebook_id() { - return this.getAttribute('facebook-id'); - } - - set facebook_id(v) { - this.setAttribute('facebook-id', v); - this.shadowRoot.querySelector('account-view').facebook_id = v; - } }); diff --git a/client/src/poly.js b/client/src/poly.js new file mode 100644 index 0000000..a8da5e5 --- /dev/null +++ b/client/src/poly.js @@ -0,0 +1,22 @@ +Object.defineProperties(Object.prototype, { + 'entry': { + value(key) { + const owner = this; + return { + owner, orElse(v) { + if (owner[key] === undefined) owner[key] = v; + return owner[key]; + }, get() { + return owner[key]; + } + } + } + } +}) + +Object.defineProperties(Array.prototype, { + 'last': { get() { return this[this.length - 1] } }, + 'tail': { get() { return this[this.length - 1] } }, + 'first': { get() { return this[0] } }, + 'head': { get() { return this[0] } }, +}) diff --git a/client/src/register-form.js b/client/src/register-form.js index bdfc5dc..c37aa36 100644 --- a/client/src/register-form.js +++ b/client/src/register-form.js @@ -1,24 +1,11 @@ -import { FORM_STYLE, Component } from "./shared.js"; -import { RegisterForm } from "./register-form/model.js"; - -/* - -
- - - - - - - -
-*/ +import { FORM_STYLE, Component, goTo } from "./shared.js"; +import { RegisterForm } from "./register-form/model.js"; customElements.define('register-form', class extends Component { #form = new RegisterForm; static get observedAttributes() { - return ['step'] + return ['current'] } constructor() { @@ -26,13 +13,6 @@ customElements.define('register-form', class extends Component { -
+
+
`); this.shadowRoot.addEventListener('form:next', ev => { ev.stopPropagation(); - this.step = this.step + 1; + this.#transfer('next'); }); this.shadowRoot.addEventListener('form:prev', ev => { ev.stopPropagation(); - this.step = this.step - 1; + this.#transfer('prev'); }); this.shadowRoot.addEventListener('account:type', ev => { ev.stopPropagation(); - this.#form.account_type = ev.detail; - if (this.#form.account_type === 'User') { - this.#showUserAccountForm(); - } else { - this.#showBusinessAccountForm(); - } + this.#copyDetail(ev); }); this.addEventListener('account:basic', ev => { - this.#copyDetail(ev) - this.#showBusinessDetailsForm(); + this.#copyDetail(ev); }); this.addEventListener('account:business', ev => { - this.#copyDetail(ev) + this.#copyDetail(ev); }); this.addEventListener('account:items', ev => { - const items = Object.entries(ev.detail).reduce((items, [fieldName, value]) => { - const m = fieldName.match(/(items)\[(\d+)]\[(\w+)]/); - if (!Array.isArray(m)) return items; - const [_full, _items, n, name] = m; - items.entry(n).orElse({})[name] = value; - return items; - }, {}); - this.#form.items = Array.from(Object.values(items)); + this.#copyDetail(ev); }); + this.addEventListener('account:contacts', ev => { + this.#copyDetail(ev); + }); + this.#loadCache(); + } + + #loadCache() { + let register = {}; + try { + register = JSON.parse(localStorage.getItem('register')); + } catch (e) { + } + this.#form.from(register); } #copyDetail(ev) { ev.stopPropagation(); + console.info('ev.detail', ev.detail); for (const [key, value] of Object.entries(ev.detail)) { this.#form[key] = value; } + localStorage.setItem('register', JSON.stringify(this.#form.payload)); } - connectedCallback() { - super.connectedCallback(); - this.#showAccountTypeForm(); - } - - get step() { - const step = parseInt(this.getAttribute('step')); - return isNaN(step) ? 1 : step; - } - - set step(n) { - if (n < 0) return; - this.setAttribute('step', n); + listenHistory = ({ parts }) => { + this.current = parts.last; } #host(body) { - this.shadowRoot.querySelector('#host').innerHTML = body; + this.innerHTML = body; } #showAccountTypeForm() { this.#host(` - + `); } #showUserAccountForm() { this.#host(` - + `); } #showBusinessAccountForm() { this.#host(` - + `); } #showBusinessDetailsForm() { this.#host(` - + `); } - #transfer(page, direction) { - const current = direction === 'next' - ? this.#nextPage(page) - : this.#prevPage(page); - if (!current) return; + #showBusinessItemsForm() { + this.#host(` + + ${ this.#form.items.map( + ({ name, price, picture_url }) => ` + +` + ).join('') } + + `); } - #nextPage(page) { - switch (page) { + #showBusinessContactsForm() { + this.#host(` + + ${ this.#form.contacts.map( + ({ type, content }) => ` + + + + `).join('') } + + `); + } + + #showBusinessSubmitForm() { + this.#host(` + + ${ this.#form.items.map( + ({ name, price, picture_url }) => ` + +` + ).join('') } + ${ this.#form.contacts.map( + ({ type, content }) => ` + + `).join('') } + + `); + } + + #transfer(direction) { + const current = direction === 'next' + ? this.#nextPage() + : this.#prevPage(); + this.current = current; + goTo(`/register/${ current }`); + } + + get current() { + return this.getAttribute('current'); + } + + set current(current) { + if (!current) return; + this.setAttribute('current', current); + + switch (current) { + case "account-type": + return this.#showAccountTypeForm(); + case "user-account": + return this.#showUserAccountForm(); + case "business-account": + return this.#showBusinessAccountForm(); + case "business-details": + return this.#showBusinessDetailsForm(); + case "business-items": + return this.#showBusinessItemsForm(); + case "business-contacts": + return this.#showBusinessContactsForm(); + case "business-submit": + return this.#showBusinessSubmitForm(); + default: + throw new Error(`Unknown page "${ current }"`); + } + } + + #nextPage() { + switch (this.current) { case "account-type": return this.#form.account_type === 'User' ? 'user-account' : 'business-account'; - case "user-account": return null; - case "business-account": return 'business-details'; - case "business-details": return 'business-items'; - case "business-items": return 'business-contact'; - case "business-contact": return 'business-submit'; - case "business-submit": return null; + case "user-account": + return null; + case "business-account": + return 'business-details'; + case "business-details": + return 'business-items'; + case "business-items": + return 'business-contacts'; + case "business-contacts": + return 'business-submit'; + case "business-submit": + return null; } } - #prevPage(page) { - switch (page) { + #prevPage() { + switch (this.current) { case "account-type": return null; - case "user-account": return 'account-type'; - case "business-account": return 'account-type'; - case "business-details": return 'business-account'; - case "business-items": return 'business-details'; - case "business-contact": return 'business-items'; - case "business-submit": return 'business-contact'; + case "user-account": + return 'account-type'; + case "business-account": + return 'account-type'; + case "business-details": + return 'business-account'; + case "business-items": + return 'business-details'; + case "business-contacts": + return 'business-items'; + case "business-submit": + return 'business-contacts'; } } }); diff --git a/client/src/register-form/model.js b/client/src/register-form/model.js index 5a018ab..31f63e9 100644 --- a/client/src/register-form/model.js +++ b/client/src/register-form/model.js @@ -7,10 +7,12 @@ export class RegisterFormComponent extends PseudoForm { return null; } - mountFormHandler() { + mountFormHandler(dispatchForm) { if (this.#mounted) return; this.#mounted = true; + if (!dispatchForm) dispatchForm = () => this.#dispatchForm(); + const form = this.shadowRoot.querySelector('form'); form.addEventListener('submit', ev => { ev.preventDefault(); @@ -21,7 +23,7 @@ export class RegisterFormComponent extends PseudoForm { } }); this.addEventListener('form:next', () => { - this.#dispatchForm() + dispatchForm() }); } @@ -30,7 +32,6 @@ export class RegisterFormComponent extends PseudoForm { ...memo, [el.name]: el.value, }), {}); - console.warn('#dispatchForm', detail, this.elements) this.dispatchEvent(new CustomEvent(this.submitEventName, { composed: true, bubbles: true, detail })); } @@ -106,25 +107,19 @@ export class RegisterForm { } get items() { - return this.#items; + return this.#items || []; } set items(a) { this.#items = a; } - appendItem(item) { - this.#items = this.#items || []; - this.#items.push(item); - } - get contacts() { - return this.#contacts; + return this.#contacts || []; } - appendContact(contact) { - this.#contacts = this.#contacts || []; - this.#contacts.push(contact); + set contacts(c) { + this.#contacts = c; } get name() { @@ -163,4 +158,22 @@ export class RegisterForm { return m; }, {}) } + + from(object) { + [ + 'email', + 'login', + 'password', + 'facebook_id', + 'account_type', + 'items', + 'contacts', + 'name', + 'description' + ].forEach(key => { + const value = object[key]; + if (!value) return; + this[key] = value; + }); + } } diff --git a/client/src/register-form/register-account-type.js b/client/src/register-form/register-account-type.js index 2f53a10..a689585 100644 --- a/client/src/register-form/register-account-type.js +++ b/client/src/register-form/register-account-type.js @@ -117,14 +117,22 @@ customElements.define('register-account-type', class extends Component { user.addEventListener('click', ev => { ev.preventDefault(); ev.stopPropagation(); - this.dispatchEvent(new CustomEvent('account:type', { bubbles: true, composed: true, detail: 'User' })); + this.dispatchEvent(new CustomEvent('account:type', { + bubbles: true, + composed: true, + detail: { account_type: 'User' } + })); this.shadowRoot.querySelector('form-navigation').next(); }); const service = this.shadowRoot.querySelector('#local-service'); service.addEventListener('click', ev => { ev.preventDefault(); ev.stopPropagation(); - this.dispatchEvent(new CustomEvent('account:type', { bubbles: true, composed: true, detail: 'Business' })); + this.dispatchEvent(new CustomEvent('account:type', { + bubbles: true, + composed: true, + detail: { account_type: 'Business' } + })); this.shadowRoot.querySelector('form-navigation').next(); }); } diff --git a/client/src/register-form/register-business-contact-form.js b/client/src/register-form/register-business-contact-form.js deleted file mode 100644 index 8767d3a..0000000 --- a/client/src/register-form/register-business-contact-form.js +++ /dev/null @@ -1,22 +0,0 @@ -import { RegisterFormComponent } from "./model.js"; - -customElements.define('register-business-contact-form', class extends RegisterFormComponent { - constructor() { - super(` - -
-
- -
-
- `); - - this.mountFormHandler(); - } - - get submitEventName() { - return 'account:contacts'; - } -}); diff --git a/client/src/register-form/register-business-contacts-form.js b/client/src/register-form/register-business-contacts-form.js new file mode 100644 index 0000000..1a9a8e1 --- /dev/null +++ b/client/src/register-form/register-business-contacts-form.js @@ -0,0 +1,65 @@ +import { BUTTON_STYLE } from "../shared.js"; +import { RegisterFormComponent } from "./model.js"; + +customElements.define('register-business-contacts-form', class extends RegisterFormComponent { + constructor() { + super(` + +
+
+
+ + +
+ + +
+
+ `); + + this.shadowRoot.querySelector('#addButton').addEventListener('click', ev => { + ev.stopPropagation(); + ev.preventDefault(); + const form = this.shadowRoot.querySelector('contact-info-editor'); + const { type, content } = form; + this.innerHTML += ` + + + + `; + }); + + this.mountFormHandler(() => this.#emitChange()); + } + + get submitEventName() { + return 'account:contacts'; + } + + get #rows() { + return Array.from(this.querySelectorAll('contact-info')); + } + + #emitChange() { + const rows = this.#rows; + const contacts = rows.map(({ type, content }) => ({ + type, + content + })); + this.dispatchEvent(new CustomEvent(this.submitEventName, { + bubbles: true, composed: true, detail: { contacts: contacts.length ? contacts : null }, + })); + } +}); diff --git a/client/src/register-form/register-item-form-row.js b/client/src/register-form/register-business-item-form.js similarity index 74% rename from client/src/register-form/register-item-form-row.js rename to client/src/register-form/register-business-item-form.js index 99567b8..a6ae583 100644 --- a/client/src/register-form/register-item-form-row.js +++ b/client/src/register-form/register-business-item-form.js @@ -1,9 +1,9 @@ import { FORM_STYLE } from "../shared"; import { RegisterFormComponent } from "./model"; -customElements.define('register-item-form-row', class extends RegisterFormComponent { +customElements.define('register-business-item-form', class extends RegisterFormComponent { static get observedAttributes() { - return ['idx', 'name', 'picture-url', 'action', 'remove', 'save'] + return ['idx', 'name', 'price', 'picture-url', 'action', 'remove', 'save'] } constructor() { @@ -29,23 +29,22 @@ customElements.define('register-item-form-row', class extends RegisterFormCompon
- +
-
+
- +
- + `); - const form = this.shadowRoot.querySelector('form'); const imageInput = this.shadowRoot.querySelector('image-input'); this.addEventListener('item:removed', () => { this.setAttribute('removed', 'removed'); @@ -58,17 +57,40 @@ customElements.define('register-item-form-row', class extends RegisterFormCompon ev.stopPropagation(); this.picture_url = imageInput.url; }); - this.shadowRoot.querySelector('form').addEventListener('submit', ev => { - ev.preventDefault(); - ev.stopPropagation(); - const detail = { - name: form.querySelector('.item-name').value, - price: form.querySelector('price-input').value, - picture_url: this.picture_url, - item_order: this.idx, - }; + this.shadowRoot.querySelector('form').addEventListener('change', ev => { + ev.stopPropagation(); + const input = ev.target; + const { name, value } = input; + + switch (name) { + case 'price': { + this.price = parseInt(value); + break; + } + case 'name': { + this.name = value; + break; + } + case 'picture_url': { + this.picture_url = value; + break; + } + default: { + break; + } + } + }); + this.shadowRoot.querySelector('form').addEventListener('submit', ev => { + ev.stopPropagation(); + ev.preventDefault(); if (this.reportValidity()) { + const detail = { + name: this.name, + price: this.price, + picture_url: this.picture_url, + item_order: this.idx, + }; this.dispatchEvent(new CustomEvent('item:submit', { bubbles: true, composed: true, detail })); } }); @@ -77,21 +99,13 @@ customElements.define('register-item-form-row', class extends RegisterFormCompon ev.stopPropagation(); this.dispatchEvent(new CustomEvent('item:removed', { bubbles: true, composed: false })); }); + } get submitEventName() { return 'account:items'; } - connectedCallback() { - super.connectedCallback(); - this.#updateNames(this.idx); - } - - attributeChangedCallback(name, oldV, newV) { - super.attributeChangedCallback(name, oldV, newV); - } - static attr2Field(name) { const field = super.attr2Field(name); if (field === 'remove') return 'showRemove'; @@ -99,30 +113,6 @@ customElements.define('register-item-form-row', class extends RegisterFormCompon return field; } - get inputs() { - return this.#inputs.map(extract); - } - - get elements() { - console.warn('item form elements', this.#inputs) - return this.#inputs - } - - get #inputs() { - return [ - this.shadowRoot.querySelector('.item-name'), - this.shadowRoot.querySelector('.item-price'), - this.shadowRoot.querySelector('#picture_url'), - ]; - } - - #updateNames() { - const idx = this.idx; - for (const el of this.#inputs) { - el.setAttribute('name', `items[${ idx }][${ el.id }]`); - } - } - get idx() { const idx = parseInt(this.getAttribute('idx')); return isNaN(idx) ? null : idx; @@ -130,7 +120,6 @@ customElements.define('register-item-form-row', class extends RegisterFormCompon set idx(idx) { this.setAttribute('idx', idx); - this.#updateNames(idx); } get name() { @@ -143,12 +132,14 @@ customElements.define('register-item-form-row', class extends RegisterFormCompon } get price() { - return this.getAttribute('name'); + return this.getAttribute('price'); } set price(v) { + v = parseInt(v); + console.info('set price', v); this.setAttribute('price', v); - this.shadowRoot.querySelector('.item-price').value = v; + this.shadowRoot.querySelector('#price').value = v / 100.0; } get picture_url() { diff --git a/client/src/register-form/register-business-items-form.js b/client/src/register-form/register-business-items-form.js index 2263497..764914c 100644 --- a/client/src/register-form/register-business-items-form.js +++ b/client/src/register-form/register-business-items-form.js @@ -1,9 +1,9 @@ import { FORM_STYLE } from "../shared"; import { RegisterFormComponent } from "./model"; -const updateItems = (form) => { +const updateItems = (rows) => { let idx = 0; - for (const el of form.querySelectorAll('register-item-form-row')) { + for (const el of rows) { el.idx = idx++; } return idx; @@ -20,6 +20,10 @@ customElements.define('register-business-items-form', class extends RegisterForm display: flex; } + ::slotted(register-business-item-form) { + margin-bottom: 18px; + } + ${ FORM_STYLE }
@@ -35,11 +39,11 @@ customElements.define('register-business-items-form', class extends RegisterForm `); this.addEventListener('item:removed', ev => { ev.stopPropagation(); - updateItems(this) + updateItems(this.#rows) }); this.addEventListener('form:next', ev => { - updateItems(this); - for (const el of this.querySelectorAll('item-form-row')) { + updateItems(this.#rows); + for (const el of this.#rows) { if (!el.reportValidity()) { ev.stopPropagation(); ev.preventDefault(); @@ -49,17 +53,25 @@ customElements.define('register-business-items-form', class extends RegisterForm this.shadowRoot.querySelector('#add-item').addEventListener('click', ev => { ev.stopPropagation(); ev.preventDefault(); - this.appendChild(document.createElement('register-item-form-row')); - updateItems(this); + this.appendChild(document.createElement('register-business-item-form')); + updateItems(this.#rows); }); - this.mountFormHandler(); + this.mountFormHandler(() => this.#emitChange()); } get submitEventName() { return 'account:items'; } - get elements() { - return Array.from(this.querySelectorAll("register-item-form-row")).map(form => form.elements).flat(); + get #rows() { + return Array.from(this.querySelectorAll("register-business-item-form")); + } + + #emitChange() { + const rows = this.#rows; + console.warn(rows); + 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 } })); } }); diff --git a/client/src/register-form/register-business-submit-form.js b/client/src/register-form/register-business-submit-form.js index 27c977a..39d0258 100644 --- a/client/src/register-form/register-business-submit-form.js +++ b/client/src/register-form/register-business-submit-form.js @@ -1,8 +1,8 @@ -import { FORM_STYLE, PseudoForm } from "../shared"; +import { FORM_STYLE, goTo, PseudoForm } from "../shared.js"; customElements.define('register-business-submit-form', class extends PseudoForm { static get observedAttributes() { - return ['name', 'description', 'login', 'email', 'password', 'account_type'] + return ['name', 'description', 'login', 'email', 'password', 'account-type'] } constructor() { @@ -24,35 +24,36 @@ customElements.define('register-business-submit-form', class extends PseudoForm } -
- - - - - -
- +
- +
- +
- +
- +
- + + +
+

Dane kontaktowe

+ +
+
+

Produkty/usługi

+
@@ -61,32 +62,25 @@ customElements.define('register-business-submit-form', class extends PseudoForm
`); - } + this.shadowRoot.querySelector('form').addEventListener('submit', async (ev) => { + ev.stopPropagation(); + ev.preventDefault(); + const res = await fetch('/register', { + method: 'POST', + body: JSON.stringify(this.#form), + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + }); - updateField(name, value) { - this.shadowRoot.querySelector(`[id="hidden-${ name }"]`).value = value; - this.shadowRoot.querySelector(`[id="preview-${ name }"]`).value = value; - } - - set items(items) { - const host = this.shadowRoot.querySelector('#items'); - host.innerHTML = ``; - for (const row of items) { - const el = host.appendChild(document.createElement('div')); - el.className = 'item-view'; - const [name, price, img] = row; - - el.innerHTML = ` - ${ img.value !== '' - ? `` - : `Brak zdjęcia` - } - - - - - `; - } + if (res.ok) { + goTo("/account?success"); + } else { + const { error } = await res.json(); + document.querySelector('error-message').message = error; + } + }); } set account_type(v) { @@ -96,35 +90,70 @@ customElements.define('register-business-submit-form', class extends PseudoForm get name() { return this.#hidden('name').value } + set name(v) { this.#hidden('name').value = v; } + get description() { return this.#hidden('description').value } + set description(v) { this.#hidden('description').value = v; } + get login() { return this.#hidden('login').value } + set login(v) { this.#hidden('login').value = v; } + get email() { return this.#hidden('email').value } + set email(v) { this.#hidden('email').value = v; } + get password() { return this.#hidden('password').value } + set password(v) { this.#hidden('password').value = v; } #hidden(selector) { - return this.shadowRoot.querySelector(`#hidden-${selector}`) + return this.shadowRoot.querySelector(`#${ selector }`) + } + + get #form() { + return [ + 'login', + 'email', + 'password', + 'name', + 'description', + 'account_type', + ].reduce((memo, name) => ({ + ...memo, + [name]: this.#hidden(name).value, + }), { + items: Array.from(this.querySelectorAll('local-business-item')).map(({ name, price, picture_url }) => ({ + name, price, picture_url + })), + contacts: Array.from(this.querySelectorAll('contact-info')).map(({ content, type }) => ({ + content, + type + })), + }); + } + + get payload() { + return this.#form } }); diff --git a/client/src/shared.js b/client/src/shared.js index 5d028a2..1a5eed5 100644 --- a/client/src/shared.js +++ b/client/src/shared.js @@ -168,11 +168,20 @@ export class Component extends HTMLElement { if (!field) continue; this.#setFieldValue(field, this[field]); } + { + const listener = this.listenHistory; + listener && listener(historyDetails()); + this.#dropHistory = subscribeHistory(listener); + } } disconnectedCallback() { + if (this.#dropHistory) this.#dropHistory(); } + #dropHistory; + listenHistory = null + attributeChangedCallback(name, oldV, newV) { if (oldV === newV) return; @@ -279,3 +288,23 @@ export const runFbReady = (fn) => { }; const fbQueue = []; let fbReady = false; + +const historyQueue = new Set; +export const goTo = (url) => { + history.pushState({}, document.title, url); + for (const call of historyQueue) call(); + document.dispatchEvent(new CustomEvent('history:push', { composed: true, bubbles: true, details: historyDetails() })); +} +export const historyDetails = () => { + const parts = location.pathname.split('/').filter(s => s && s.length) + return { parts } +} +const subscribeHistory = (cb) => { + if (cb) { + const call = () => { + cb(historyDetails()); + } + historyQueue.add(call); + return () => historyQueue.delete(call); + } +} diff --git a/client/src/shared/error-message.js b/client/src/shared/error-message.js new file mode 100644 index 0000000..af61a01 --- /dev/null +++ b/client/src/shared/error-message.js @@ -0,0 +1,33 @@ +import { Component } from "../shared.js"; + +customElements.define('error-message', class extends Component { + static get observedAttributes() { + return ['message']; + } + + constructor() { + super(` + +
+ `); + } + + get message() { + return this.getAttribute('message'); + } + + set message(m) { + this.setAttribute('message', m); + this.shadowRoot.querySelector('div').textContent = m; + } +}) diff --git a/client/src/shared/image-input.js b/client/src/shared/image-input.js index 9e56206..17ed72c 100644 --- a/client/src/shared/image-input.js +++ b/client/src/shared/image-input.js @@ -188,6 +188,11 @@ customElements.define('image-input', class extends Component { composed: true, detail: path })); + this.dispatchEvent(new CustomEvent('change', { + bubbles: true, + composed: true, + detail: path + })); }); } diff --git a/server/assets/templates/account.html b/server/assets/templates/account.html index 8d05450..984608a 100644 --- a/server/assets/templates/account.html +++ b/server/assets/templates/account.html @@ -15,11 +15,5 @@ {%- endif %} {% when None %} - - - {% endmatch %} {% endblock %} diff --git a/server/assets/templates/base.html b/server/assets/templates/base.html index 6dc5fe2..4255db5 100644 --- a/server/assets/templates/base.html +++ b/server/assets/templates/base.html @@ -14,8 +14,9 @@
{% match error %} {% when Some with (e) %} -

{{e}}

+ {% when None %} + {% endmatch %}
{% include "nav.html" %} diff --git a/server/assets/templates/register.html b/server/assets/templates/register.html new file mode 100644 index 0000000..5707c77 --- /dev/null +++ b/server/assets/templates/register.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{% block content %} + + + + +{% endblock %} diff --git a/server/assets/templates/sign-in.html b/server/assets/templates/sign-in.html new file mode 100644 index 0000000..a3a7fbc --- /dev/null +++ b/server/assets/templates/sign-in.html @@ -0,0 +1,6 @@ +{% extends "base.html" %} +{% block content %} + + + +{% endblock %} diff --git a/server/src/model/view.rs b/server/src/model/view.rs index 5466a9e..d8d37ee 100644 --- a/server/src/model/view.rs +++ b/server/src/model/view.rs @@ -204,6 +204,7 @@ pub struct DeleteNewsArticleInput { #[derive(Debug, Deserialize)] pub struct CreateContactInfoInput { + #[serde(rename = "type")] pub contact_type: String, pub content: String, } diff --git a/server/src/routes/unrestricted/account.rs b/server/src/routes/unrestricted/account.rs index d0d2fc6..f5fc0b6 100644 --- a/server/src/routes/unrestricted/account.rs +++ b/server/src/routes/unrestricted/account.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use sqlx::PgPool; use tracing::error; +use crate::model::db::AccountType; use crate::model::view::Page; use crate::model::{db, view}; use crate::routes::unrestricted::IndexTemplate; @@ -15,198 +16,6 @@ use crate::routes::{HttpResult, Identity}; use crate::view::Helper; use crate::{not_xss, queries, routes, utils}; -#[derive(Default, Serialize, Template)] -#[template(path = "account.html")] -struct AccountTemplate { - account: Option, - error: Option, - #[serde(skip)] - page: Page, - h: Helper, - register_page: RegisterPage, -} - -impl AccountTemplate { - pub fn error>(error: Error, page: Page) -> Self { - AccountTemplate { - error: Some(error.into()), - page, - ..Default::default() - } - } - - pub fn bad_request>( - req: &HttpRequest, - error: Error, - page: Page, - ) -> HttpResult { - HttpResult::res( - req, - StatusCode::BAD_REQUEST, - AccountTemplate::error(error, page), - ) - } -} - -#[post("/register")] -#[tracing::instrument] -async fn register( - req: HttpRequest, - form: web::Form, - db: Data, - id: Identity, -) -> HttpResult { - let mut form = form.into_inner(); - dbg!(&form); - process_items(form.items.get_or_insert_default(), form.names); - - let pool = db.into_inner(); - if form.account_type == db::AccountType::Admin { - return HttpResult::err(&req, routes::Error::XSS); - } - - let mut t = pool.begin().await.unwrap(); - - let pass = match utils::encrypt(&form.password) { - Ok(pass) => pass, - Err(e) => { - error!("{:?}", e); - dbg!(e); - t.rollback().await.unwrap(); - return AccountTemplate::bad_request( - &req, - "Zapisanie hasła nie powiodło się", - Page::Register, - ); - } - }; - - let res = queries::create_account( - &mut t, - db::CreateAccountInput { - login: form.login, - email: form.email, - pass, - facebook_id: form.facebook_id, - account_type: form.account_type, - }, - ) - .await; - let account = match res { - Ok(res) => { - id.remember(format!("{}", res.id)); - res - } - Err(queries::Error::AccountTaken { .. }) => { - return AccountTemplate::bad_request(&req, "Adres e-mail jest zajęty", Page::Register); - } - Err(e) => { - dbg!(e); - t.rollback().await.unwrap(); - return AccountTemplate::bad_request( - &req, - "Problem z utworzeniem konta", - Page::Register, - ); - } - }; - - if matches!(form.account_type, db::AccountType::Business) { - let name = form.name.as_deref().unwrap_or_default(); - let owner_id = account.id; - let description = form.description.as_deref().unwrap_or_default(); - - not_xss!(&req, name, t); - not_xss!(&req, description, t); - - let res = - queries::create_local_business(&mut t, name.into(), owner_id, description.into()).await; - let business = match res { - Ok(business) => business, - Err(e) => { - dbg!(e); - t.rollback().await.unwrap(); - return HttpResult::res( - &req, - StatusCode::BAD_REQUEST, - AccountTemplate { - error: Some("Problem z utworzeniem konta".into()), - page: Page::Register, - ..Default::default() - }, - ); - } - }; - - for (idx, item) in form.items.unwrap_or_default().into_iter().enumerate() { - not_xss!(&req, &item.name, t); - not_xss!(&req, &item.picture_url, t); - - let res: sqlx::Result = sqlx::query_as( - r#" -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()) - } else { - let name = item.picture_url.split('/').last().unwrap_or_default(); - let dir = utils::item_picture_write_dir(format!("{}", account.id)); - if let Err(e) = std::fs::create_dir_all(&dir) { - error!("{e} {:?}", dir); - dbg!(e); - t.rollback().await.unwrap(); - return AccountTemplate::bad_request( - &req, - "Problem z utworzeniem konta. Nie można zapisać zdjęcia.", - Page::Register, - ); - } - let path = dir.join(name); - if let Err(e) = std::fs::rename(format!(".{}", item.picture_url), &path) { - error!("{e} {:?}", item.picture_url); - dbg!(e); - t.rollback().await.unwrap(); - return AccountTemplate::bad_request( - &req, - "Problem z utworzeniem konta. Nie można zapisać zdjęcia.", - Page::Register, - ); - } - let path = path.to_str().map(String::from).unwrap_or_default(); - path.strip_prefix('.') - .map(String::from) - .unwrap_or_else(|| path) - }) - .fetch_one(&mut t) - .await; - match res { - Ok(_) => {} - Err(e) => { - tracing::error!("{e}"); - dbg!(e); - t.rollback().await.unwrap(); - return AccountTemplate::bad_request( - &req, - "Problem z utworzeniem konta", - Page::Register, - ); - } - } - } - } - - t.commit().await.unwrap(); - - HttpResult::goto(&req, "/account?success") -} - #[post("/logout")] #[tracing::instrument] async fn logout(id: Identity) -> HttpResponse { @@ -223,15 +32,38 @@ async fn logout(id: Identity) -> HttpResponse { ) } +#[derive(Debug, Default, Serialize, Template)] +#[template(path = "sign-in.html")] +struct SignInTemplate { + account: Option, + error: Option, + #[serde(skip)] + page: Page, + h: Helper, +} + #[derive(Debug, Deserialize)] struct LoginForm { email: String, password: String, } +#[get("/login")] +#[tracing::instrument] +async fn perform_sign_in(req: HttpRequest, id: Identity) -> HttpResult { + HttpResult::res( + &req, + StatusCode::OK, + SignInTemplate { + page: Page::Login, + ..Default::default() + }, + ) +} + #[post("/login")] #[tracing::instrument] -async fn login( +async fn display_sign_in_form( req: HttpRequest, form: web::Form, db: Data, @@ -273,33 +105,45 @@ async fn login( ) } -#[post("/upload")] -async fn upload( - req: HttpRequest, - payload: actix_multipart::Multipart, - db: Data, - id: Identity, -) -> HttpResult { - let pool = db.into_inner(); - let mut t = crate::ok_or_internal!(&req, pool.begin().await); - let id = match id.identity() { - Some(id) => queries::account_by_id(&mut t, id).await.map(|a| a.id), - _ => None, - }; - t.commit().await.ok(); - routes::uploads::hande_upload(&req, payload, id, "accounts").await +#[derive(Default, Serialize, Template)] +#[template(path = "account.html")] +struct AccountTemplate { + account: Option, + error: Option, + #[serde(skip)] + page: Page, + h: Helper, + register_page: RegisterPage, +} + +impl AccountTemplate { + pub fn error>(error: Error, page: Page) -> Self { + Self { + error: Some(error.into()), + page, + ..Default::default() + } + } + + pub fn bad_request>( + req: &HttpRequest, + error: Error, + page: Page, + ) -> HttpResult { + HttpResult::res(req, StatusCode::BAD_REQUEST, Self::error(error, page)) + } } #[get("/account")] #[tracing::instrument] -async fn account_page(req: HttpRequest, id: Identity, db: Data) -> HttpResult { +async fn display_account_info(req: HttpRequest, id: Identity, db: Data) -> HttpResult { 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, _ => { id.forget(); - None + return HttpResult::goto(&req, "/login"); } }; t.commit().await.ok(); @@ -314,31 +158,67 @@ async fn account_page(req: HttpRequest, id: Identity, db: Data) -> HttpR ) } -#[derive(Debug, Default, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -enum RegisterPage { - #[default] - UserType = 0, - Basic = 1, - Business = 2, - Items = 3, - Contact = 4, - Submit = 5, - User = 40, +#[derive(Default, Serialize, Template)] +#[template(path = "register.html")] +struct RegisterTemplate { + account: Option, + error: Option, + #[serde(skip)] + page: Page, + h: Helper, + register_page: RegisterPage, } -impl Iterator for RegisterPage { - type Item = Self; +impl RegisterTemplate { + pub fn error>(error: Error, page: Page) -> Self { + Self { + error: Some(error.into()), + page, + ..Default::default() + } + } + + pub fn bad_request>( + req: &HttpRequest, + error: Error, + page: Page, + ) -> HttpResult { + HttpResult::res(req, StatusCode::BAD_REQUEST, Self::error(error, page)) + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +enum RegisterPage { + #[default] + AccountType, + UserAccount, + BusinessAccount, + BusinessDetails, + BusinessItems, + BusinessContacts, + BusinessSubmit, +} + +struct RegisterIterator(RegisterPage, AccountType); + +impl Iterator for RegisterIterator { + type Item = RegisterPage; fn next(&mut self) -> Option { - match self { - RegisterPage::UserType => Some(RegisterPage::Basic), - RegisterPage::Basic => Some(RegisterPage::Business), - RegisterPage::Business => Some(RegisterPage::Items), - RegisterPage::Items => Some(RegisterPage::Contact), - RegisterPage::Contact => Some(RegisterPage::Submit), - RegisterPage::Submit => Some(RegisterPage::User), - RegisterPage::User => None, + match self.0 { + RegisterPage::AccountType if self.1 == AccountType::Business => { + Some(RegisterPage::BusinessAccount) + } + RegisterPage::AccountType => Some(RegisterPage::UserAccount), + + RegisterPage::UserAccount => None, + + RegisterPage::BusinessAccount => Some(RegisterPage::BusinessDetails), + RegisterPage::BusinessDetails => Some(RegisterPage::BusinessItems), + RegisterPage::BusinessItems => Some(RegisterPage::BusinessContacts), + RegisterPage::BusinessContacts => Some(RegisterPage::BusinessSubmit), + RegisterPage::BusinessSubmit => None, } } } @@ -346,20 +226,175 @@ impl Iterator for RegisterPage { impl RegisterPage { pub fn as_str(&self) -> &str { match self { - RegisterPage::UserType => "user-type", - RegisterPage::User => "user-account", - RegisterPage::Basic => "business-account", - RegisterPage::Business => "business-details", - RegisterPage::Items => "items", - RegisterPage::Submit => "submit", - RegisterPage::Contact => "contact", + RegisterPage::AccountType => "account-type", + RegisterPage::UserAccount => "user-account", + RegisterPage::BusinessAccount => "business-account", + RegisterPage::BusinessDetails => "business-details", + RegisterPage::BusinessItems => "business-items", + RegisterPage::BusinessContacts => "business-contacts", + RegisterPage::BusinessSubmit => "business-submit", } } } +#[post("/register")] +#[tracing::instrument] +async fn save_account_details( + req: HttpRequest, + form: web::Json, + db: Data, + id: Identity, +) -> HttpResult { + let mut form = form.into_inner(); + dbg!(&form); + process_items(form.items.get_or_insert_default(), form.names); + + let pool = db.into_inner(); + if form.account_type == db::AccountType::Admin { + return HttpResult::err(&req, routes::Error::XSS); + } + + let mut t = pool.begin().await.unwrap(); + + let pass = match utils::encrypt(&form.password) { + Ok(pass) => pass, + Err(e) => { + error!("{:?}", e); + dbg!(e); + t.rollback().await.unwrap(); + return RegisterTemplate::bad_request( + &req, + "Zapisanie hasła nie powiodło się", + Page::Register, + ); + } + }; + + let res = queries::create_account( + &mut t, + db::CreateAccountInput { + login: form.login, + email: form.email, + pass, + facebook_id: form.facebook_id, + account_type: form.account_type, + }, + ) + .await; + let account = match res { + Ok(res) => { + id.remember(format!("{}", res.id)); + res + } + Err(queries::Error::AccountTaken { .. }) => { + return RegisterTemplate::bad_request(&req, "Adres e-mail jest zajęty", Page::Register); + } + Err(e) => { + dbg!(e); + t.rollback().await.unwrap(); + return RegisterTemplate::bad_request( + &req, + "Problem z utworzeniem konta", + Page::Register, + ); + } + }; + + if matches!(form.account_type, db::AccountType::Business) { + let name = form.name.as_deref().unwrap_or_default(); + let owner_id = account.id; + let description = form.description.as_deref().unwrap_or_default(); + + not_xss!(&req, name, t); + not_xss!(&req, description, t); + + let res = + queries::create_local_business(&mut t, name.into(), owner_id, description.into()).await; + let business = match res { + Ok(business) => business, + Err(e) => { + dbg!(e); + t.rollback().await.unwrap(); + return RegisterTemplate::bad_request( + &req, + "Problem z utworzeniem konta", + Page::Register, + ); + } + }; + + for (idx, item) in form.items.unwrap_or_default().into_iter().enumerate() { + not_xss!(&req, &item.name, t); + not_xss!(&req, &item.picture_url, t); + + let res: sqlx::Result = sqlx::query_as( + r#" +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()) + } else { + let name = item.picture_url.split('/').last().unwrap_or_default(); + let dir = utils::item_picture_write_dir(format!("{}", account.id)); + if let Err(e) = std::fs::create_dir_all(&dir) { + error!("{e} {:?}", dir); + dbg!(e); + t.rollback().await.unwrap(); + return RegisterTemplate::bad_request( + &req, + "Problem z utworzeniem konta. Nie można zapisać zdjęcia.", + Page::Register, + ); + } + let path = dir.join(name); + if let Err(e) = std::fs::rename(format!(".{}", item.picture_url), &path) { + error!("{e} {:?}", item.picture_url); + dbg!(e); + t.rollback().await.unwrap(); + return RegisterTemplate::bad_request( + &req, + "Problem z utworzeniem konta. Nie można zapisać zdjęcia.", + Page::Register, + ); + } + let path = path.to_str().map(String::from).unwrap_or_default(); + path.strip_prefix('.') + .map(String::from) + .unwrap_or_else(|| path) + }) + .fetch_one(&mut t) + .await; + match res { + Ok(_) => {} + Err(e) => { + error!("{e}"); + dbg!(e); + t.rollback().await.unwrap(); + return RegisterTemplate::bad_request( + &req, + "Problem z utworzeniem konta", + Page::Register, + ); + } + } + } + } + + t.commit().await.unwrap(); + + HttpResult::goto(&req, "/account?success") +} + #[get("/register/{step}")] #[tracing::instrument] -async fn register_page( +async fn display_register_page( req: HttpRequest, id: Identity, db: Data, @@ -374,13 +409,16 @@ async fn register_page( None } }; + if account.is_some() { + return HttpResult::goto(&req, "/account"); + } let page = path.into_inner().0; t.commit().await.ok(); HttpResult::res( &req, StatusCode::OK, - AccountTemplate { + RegisterTemplate { account, page: Page::Account, register_page: page, @@ -451,14 +489,32 @@ fn process_items(items: &mut Vec, names: HashMap, + id: Identity, +) -> HttpResult { + let pool = db.into_inner(); + let mut t = crate::ok_or_internal!(&req, pool.begin().await); + let id = match id.identity() { + Some(id) => queries::account_by_id(&mut t, id).await.map(|a| a.id), + _ => None, + }; + t.commit().await.ok(); + routes::uploads::hande_upload(&req, payload, id, "accounts").await +} + pub fn configure(config: &mut ServiceConfig) { config - .service(register) - .service(logout) - .service(login) + .service(display_account_info) + .service(perform_sign_in) + .service(display_sign_in_form) + .service(save_account_details) + .service(display_register_page) .service(upload) - .service(account_page) - .service(register_page); + .service(logout); } #[cfg(test)]