-
@@ -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 {
-
-
-
+
+
Nie masz konta? Utwórz nowe
@@ -51,31 +40,40 @@ customElements.define('ow-account', class extends Component {
Posiadasz konto? Zaloguj się
-
`);
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 }
`);
- }
+ 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) %}
-
{% 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)]
-
+
-
+
{{e}}
+