diff --git a/assets/css/app.css b/assets/css/app.css index f209a38..f9b501e 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1,64 +1,69 @@ /* @media (min-width: 1200px) { - .bg { - display: block; - text-align: center; - width: 100%; - background: - radial-gradient(circle, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 45%, rgba(255,255,255,1) 100%), - no-repeat center image-set(url("/assets/images/background.webp"), url("/assets/images/background.jpeg")); - height: 200px; - } + .bg { + display: block; + text-align: center; + width: 100%; + background: + radial-gradient(circle, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 45%, rgba(255,255,255,1) 100%), + no-repeat center image-set(url("/assets/images/background.webp"), url("/assets/images/background.jpeg")); + height: 200px; + } - .bg h1 { - text-align: center; - font-weight: bold; - font-size: 50px; - text-shadow: 2px 2px 2px #c5d1d8; - color: #FFF; - padding-top: 60px; - } + .bg h1 { + text-align: center; + font-weight: bold; + font-size: 50px; + text-shadow: 2px 2px 2px #c5d1d8; + color: #FFF; + padding-top: 60px; + } }*/ @import url('http://fonts.cdnfonts.com/css/noto-sans'); +* { + --hover-color: #f18902; + --border-slim-color: #495057; +} + main { - font-family: 'Noto Sans', sans-serif; + font-family: 'Noto Sans', sans-serif; } * { - font-family: 'Noto Sans', sans-serif; + font-family: 'Noto Sans', sans-serif; } @media (min-width: 1200px) { - article { - width: 1280px; - margin: auto auto; - } - .bg { - height: 200px; - display: flex; - justify-content: space-between; - } - .bg::after { - display: block; - text-align: center; - width: 100%; - background: - linear-gradient(90deg, rgba(255,255,255,1) 0%, rgba(255,255,255,1) 1%, rgba(255,255,255,0) 100%), - no-repeat center image-set(url("/assets/images/background.webp"), url("/assets/images/background.jpeg")); - height: 200px; - width: calc(100% - 300px); - max-width: 1200px; - content: ' '; - } + article { + width: 1280px; + margin: auto auto; + } + .bg { + height: 200px; + display: flex; + justify-content: space-between; + } + .bg::after { + display: block; + text-align: center; + width: 100%; + background: + linear-gradient(90deg, rgba(255,255,255,1) 0%, rgba(255,255,255,1) 1%, rgba(255,255,255,0) 100%), + no-repeat center image-set(url("/assets/images/background.webp"), url("/assets/images/background.jpeg")); + height: 200px; + width: calc(100% - 300px); + max-width: 1200px; + content: ' '; + } - .bg h1 { - font-weight: bold; - font-size: 50px; - text-shadow: 2px 2px 2px #c5d1d8; - text-align: center; - display: block; - width: 300px; - line-height: 4; - } + .bg h1 { + font-weight: bold; + font-size: 50px; + text-shadow: 2px 2px 2px #c5d1d8; + text-align: center; + display: block; + width: 300px; + line-height: 4; + } } diff --git a/assets/js/app.js b/assets/js/app.js index 6aa6881..dcabbe3 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,207 +1,705 @@ const S = Symbol(); +customElements.define('ow-nav', class extends HTMLElement { + constructor() { + super(); + const shadow = this[S] = this.attachShadow({ mode: 'closed' }); + shadow.innerHTML = ` + +
+ +
+ `; + } +}); + +customElements.define('ow-path', class extends HTMLElement { + static get observedAttributes() { + return ['selected', 'path']; + } + + constructor() { + super(); + const shadow = this[S] = this.attachShadow({ mode: 'closed' }); + shadow.innerHTML = ` + + + `; + } + + connectedCallback() { + this.selected = this.getAttribute('selected'); + } + + attributeChangedCallback(name, oldV, newV) { + if (oldV === newV) return; + switch (name) { + case 'selected': + return this.selected = newV; + case 'path': + return this.path = newV; + } + } + + get selected() { + return this.getAttribute('selected') === 'selected'; + } + + set selected(value) { + if (value === 'selected') this.setAttribute('selected', 'selected'); + else this.removeAttribute('selected'); + } + + get path() { + return this.getAttribute('path') || '' + } + + set path(value) { + if (!value || value === '') { + this.removeAttribute('path'); + return; + } + this.setAttribute('path', value); + this[S].querySelector('a').setAttribute('href', value); + } +}); + customElements.define('local-services', class extends HTMLElement { - static get observedAttributes() { - return ['filter'] - } - constructor() { - super(); - const shadow = this[S] = this.attachShadow({ mode: 'closed' }); - shadow.innerHTML = ` - -
- -
-
- -
- `; - { - const filter = shadow.querySelector('#filter'); - let t = null; - filter.addEventListener('change', ev => { - ev.stopPropagation(); - this.filter = ev.target.value; - }); - filter.addEventListener('keyup', ev => { - ev.stopPropagation(); - if (t) clearTimeout(t); - t = setTimeout(() => { - this.filter = ev.target.value; - t = null; - }, 1000 / 3); - }); - } - } + static get observedAttributes() { + return ['filter'] + } - connectedCallback() { - this.filter = this.getAttribute('filter'); - } + constructor() { + super(); + const shadow = this[S] = this.attachShadow({ mode: 'closed' }); + shadow.innerHTML = ` + +
+ +
+
+ +
+ `; + { + const filter = shadow.querySelector('#filter'); + let t = null; + filter.addEventListener('change', ev => { + ev.stopPropagation(); + this.filter = ev.target.value; + }); + filter.addEventListener('keyup', ev => { + ev.stopPropagation(); + const value = ev.target.value; - attributeChangedCallback(name, oldV, newV) { - if (oldV == newV) return; - switch (name) { - case 'filter': return this.filter = newV; - } - } + if (t) clearTimeout(t); + t = setTimeout(() => { + this.filter = value; + t = null; + }, 1000 / 3); + }); + } + } - get filter() { - return this.getAttribute('filter'); - } + connectedCallback() { + this.filter = this.getAttribute('filter'); + } - set filter(value) { - this.setAttribute('filter', value); - for (const el of this.querySelectorAll('local-service')) { - if (!el.name) continue; - if (el.name.includes(value)) { - el.setAttribute('local-services-visible', 'visible'); - } else { - el.setAttribute('local-services-visible', 'invisible'); - } - } - } + attributeChangedCallback(name, oldV, newV) { + if (oldV === newV) return; + switch (name) { + case 'filter': + return this.filter = newV; + } + } + + get filter() { + return this.getAttribute('filter'); + } + + set filter(value) { + if (!value || value === '') { + this.removeAttribute('filter'); + for (const el of this.querySelectorAll('local-service')) { + el.removeAttribute('local-services-visible'); + } + } else { + this.setAttribute('filter', value); + for (const el of this.querySelectorAll('local-service')) { + if (!el.name) continue; + if (el.name.includes(value)) { + el.setAttribute('local-services-visible', 'visible'); + } else { + el.setAttribute('local-services-visible', 'invisible'); + } + } + } + } }); customElements.define('local-service', class extends HTMLElement { - static get observedAttributes() { - return ['name', 'service-id', 'state'] - } - constructor() { - super(); - const shadow = this[S] = this.attachShadow({ mode: 'closed' }); - shadow.innerHTML = ` - -

- -
- -
- `; - } + static get observedAttributes() { + return ['name', 'service-id', 'state'] + } - connectedCallback() { - this[S].querySelector('#name').textContent = this.getAttribute('name'); - } + constructor() { + super(); + const shadow = this[S] = this.attachShadow({ mode: 'closed' }); + shadow.innerHTML = ` + +

+ +
+ +
+ `; + } - attributeChangedCallback(name, oldV, newV) { - switch (name) { - case 'name': return this[S].querySelector('#name').textContent = newV; - } - } + connectedCallback() { + this[S].querySelector('#name').textContent = this.getAttribute('name'); + } - get name() { - return this.getAttribute('name') || '' - } + attributeChangedCallback(name, oldV, newV) { + if (oldV === newV) return; + switch (name) { + case 'name': + return this[S].querySelector('#name').textContent = newV; + } + } + + get name() { + return this.getAttribute('name') || '' + } }); customElements.define('local-service-item', class extends HTMLElement { - static get observedAttributes() { - return ['name', 'price'] - } - constructor() { - super(); - const shadow = this[S] = this.attachShadow({ mode: 'closed' }); - shadow.innerHTML = ` - -
-

- -
- `; - } + static get observedAttributes() { + return ['name', 'price'] + } - connectedCallback() { - this[S].querySelector('#name').textContent = this.getAttribute('name'); - this[S].querySelector('#price').value = this.price(); - } + constructor() { + super(); + const shadow = this[S] = this.attachShadow({ mode: 'closed' }); + shadow.innerHTML = ` + +
+

+ +
+ `; + } - attributeChangedCallback(name, oldV, newV) { - switch (name) { - case 'name': return this[S].querySelector('#name').textContent = newV; - case 'price': return this[S].querySelector('#price').value = newV; - } - } + connectedCallback() { + this[S].querySelector('#name').textContent = this.getAttribute('name'); + this[S].querySelector('#price').value = this.price(); + } - price(s) { - const n = parseInt(s || this.getAttribute('price')); - return isNaN(n) ? 0 : n; - } + attributeChangedCallback(name, oldV, newV) { + if (oldV === newV) return; + switch (name) { + case 'name': + return this[S].querySelector('#name').textContent = newV; + case 'price': + return this[S].querySelector('#price').value = newV; + } + } + + price(s) { + const n = parseInt(s || this.getAttribute('price')); + return isNaN(n) ? 0 : n; + } }); customElements.define('ow-price', class extends HTMLElement { - static get observedAttributes() { - return ['value', 'currency'] - } - constructor() { - super(); - const shadow = this[S] = this.attachShadow({ mode: 'closed' }); - shadow.innerHTML = ` - - - `; - } + static get observedAttributes() { + return ['value', 'currency'] + } - connectedCallback() { - this[S].querySelector('#price').textContent = this.formatted; - } + constructor() { + super(); + const shadow = this[S] = this.attachShadow({ mode: 'closed' }); + shadow.innerHTML = ` + + + `; + } - attributeChangedCallback(name, oldV, newV) { - switch (name) { - case 'price': { - this.value = newV; - this[S].querySelector('#price').textContent = this.formatted; - break; - } - } - } + connectedCallback() { + this[S].querySelector('#price').textContent = this.formatted; + } - get formatted() { - let v = this.value; - let major = Math.ceil(v / 100); - let minor = v % 100; - let formatted = `${major},${ minor < 10 ? `0${minor}` : minor }`; - return `${formatted}${this.currency}` - } + attributeChangedCallback(name, oldV, newV) { + if (oldV === newV) return; + switch (name) { + case 'price': { + this.value = newV; + this[S].querySelector('#price').textContent = this.formatted; + break; + } + } + } - get value() { - const n = parseInt(this.getAttribute('value')); - return isNaN(n) ? 0 : n; - } + get formatted() { + let v = this.value; + let major = Math.ceil(v / 100); + let minor = v % 100; + let formatted = `${ major },${ minor < 10 ? `0${ minor }` : minor }`; + return `${ formatted }${ this.currency }` + } - set value(v) { - this.setAttribute('value', v) - } + get value() { + const n = parseInt(this.getAttribute('value')); + return isNaN(n) ? 0 : n; + } - get currency() { - return this.getAttribute('currency') || 'PLN' - } + set value(v) { + this.setAttribute('value', v) + } + + get currency() { + return this.getAttribute('currency') || 'PLN' + } +}); + +customElements.define('ow-account', class extends HTMLElement { + static get observedAttributes() { + return ['mode'] + } + + constructor() { + super(); + const shadow = this[S] = this.attachShadow({ mode: 'closed' }); + shadow.innerHTML = ` + +
+ + +
+ Nie masz konta? Utwórz nowe +
+
+ Masz konta? Zaloguj się +
+
+ `; + shadow.querySelector('#switch-login > a').addEventListener('click', ev => { + ev.stopPropagation(); + ev.preventDefault(); + this.mode = 'login'; + }); + shadow.querySelector('#switch-register > a').addEventListener('click', ev => { + ev.stopPropagation(); + ev.preventDefault(); + this.mode = 'register'; + }); + } + + connectedCallback() { + this.mode = 'login'; + } + + attributeChangedCallback(name, oldV, newV) { + if (oldV === newV) return; + switch (name) { + } + } + + get mode() { + return this.getAttribute('mode'); + } + + set mode(value) { + value = value === 'login' || value === 'register' ? value : 'login'; + this.setAttribute('mode', value); + } +}); + +const FORM_STYLE = ` +form { + display: block; +} +form > div { + display: block; + margin-bottom: 1rem; +} +form > div > input, form > div > textarea { + font-size: 16px; + + border: none; + border-bottom-style: none; + border-bottom-width: medium; + border-bottom: 1px solid rgba(0,0,0,.1); + border-radius: 2px; + padding: 0; + + height: 36px; + background: #fff; + color: rgba(0,0,0,.8); + font-size: 14px; + + box-shadow: none !important; + + display: block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + padding: .375rem .75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-clip: padding-box; + transition: border-color .15s ease-in-out , -webkit-box-shadow .15s ease-in-out; + transition: border-color .15s ease-in-out , box-shadow .15s ease-in-out; + transition: border-color .15s ease-in-out , box-shadow .15s ease-in-out , -webkit-box-shadow .15s ease-in-out; +} +form > div > input[type="text"], +form > div > input[type="number"], +form > div > input[type="email"], +form > div > input[type="password"], +form > div > textarea { + width: calc(100% - 1.5rem - 2px); +} +form > div > label { + color: #000; + text-transform: uppercase; + font-size: 12px; + font-weight: 600; + + display: inline-block; + margin-bottom: .5rem; +} +form > div > input[type="button"], form > div > input[type="submit"] { + padding: 12px 16px; + cursor: pointer; + border: none; + border-width: 1px; + border-radius: 5px; + font-size: 14px; + font-weight: 400; + box-shadow: 0 10px 20px -6px rgba(0,0,0,.12); + position: relative; + margin-bottom: 20px; + transition: .3s; + + background: #46b5d1; + color: #fff; + + display: inline-block; + font-weight: 400; + text-align: center; + vertical-align: middle; + user-select: none; + padding: .375rem .75rem; + font-size: 1rem; + line-height: 1.5; + transition: color .15s ease-in-out, + background-color .15s ease-in-out, + border-color .15s ease-in-out, + box-shadow .15s ease-in-out, + -webkit-box-shadow .15s ease-in-out; + + width: auto; + height: calc(1.5em + 0.75rem + 2px); + padding: .375rem .75rem; +} +`; + +customElements.define('register-form', class extends HTMLElement { + static get observedAttributes() { + return ['step'] + } + + constructor() { + super(); + const shadow = this[S] = this.attachShadow({ mode: 'closed' }); + shadow.innerHTML = ` + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+
+ + +
+
+ + +
+
+
+ + + + + +
+ + +
+
+
+ `; + + const copyForm = (form) => { + for (const el of form.elements) { + if (el.name === '') continue; + finalForm.querySelector(`[id="hidden-${el.name}"]`).value = el.value; + } + }; + const finalForm = shadow.querySelector('#step-3'); + { + const el = shadow.querySelector('#step-1'); + el.addEventListener('submit', ev => { + ev.preventDefault(); + ev.stopPropagation(); + copyForm(el); + this.step = 2; + }); + } + { + const el = shadow.querySelector('#step-2'); + el.addEventListener('submit', ev => { + ev.preventDefault(); + ev.stopPropagation(); + copyForm(el); + this.step = 3; + }); + el.querySelector('.actions > input[type="button"]').addEventListener('click', ev => { + ev.preventDefault(); + ev.stopPropagation(); + this.step = 1; + }); + } + { + const el = finalForm; + el.addEventListener('submit', ev => { + ev.preventDefault(); + ev.stopPropagation(); + copyForm(el); + let content = [ + ...shadow.querySelector('#step-1').elements, + ...shadow.querySelector('#step-2').elements, + ...shadow.querySelector('#step-3').elements, + ] + .filter(el => el && el.name !== '') + .reduce((mem, el) => ({ ...mem, [el.name]: el.value }), {}); + console.info(content); + }); + el.querySelector('.actions > input[type="button"]').addEventListener('click', ev => { + ev.preventDefault(); + ev.stopPropagation(); + this.step = 2; + }); + } + } + + connectedCallback() { + this.step = 1; + } + + attributeChangedCallback(name, oldV, newV) { + if (oldV === newV) return; + switch (name) { + } + } + + get step() { + const step = parseInt(this.getAttribute('step')); + return isNaN(step) ? 1 : step; + } + + set step(n) { + this.setAttribute('step', n); + } +}); + +customElements.define('login-form', class extends HTMLElement { + static get observedAttributes() { + return [] + } + + constructor() { + super(); + const shadow = this[S] = this.attachShadow({ mode: 'closed' }); + shadow.innerHTML = ` + +
+
+ + +
+
+ + +
+
+ +
+
+ `; + } + + connectedCallback() { + } + + attributeChangedCallback(name, oldV, newV) { + if (oldV === newV) return; + switch (name) { + } + } }); diff --git a/assets/templates/account.html b/assets/templates/account.html new file mode 100644 index 0000000..470e9a9 --- /dev/null +++ b/assets/templates/account.html @@ -0,0 +1,29 @@ + + + + OS Wilno + + + + + + + +
+
+
+

OS Wilno

+
+
+
+ + Lokalne Usługi + Aktualności + Konto + + + +
+
+ + diff --git a/assets/templates/index.html b/assets/templates/index.html index 3f3b6bc..cce25f8 100644 --- a/assets/templates/index.html +++ b/assets/templates/index.html @@ -1,38 +1,47 @@ - - - OSWilno - - - - - - - -
-
-
-

OS Wilno

-
-
-
-

Lokalne usługi

- - {% for service in services %} - - {% for line in service.description.lines() %} -

{{line}}

- {% endfor %} + + + OS Wilno + + + + + + + +
+
+
+

OS Wilno

+
+
+
+ + Lokalne Usługi + Aktualności + Konto + + + {% for service in services %} + + {% for line in service.description.lines() %} +

{{line}}

+ {% endfor %} - {% for item in service.items %} - - - {% endfor %} + {% for item in service.items %} + + + {% endfor %} -
- {% endfor %} -
-
-
- +
+ {% endfor %} +
+
+
+ diff --git a/db/seed/001_init.psql b/db/seed/001_init.psql new file mode 100644 index 0000000..4222c1a --- /dev/null +++ b/db/seed/001_init.psql @@ -0,0 +1,18 @@ +INSERT INTO accounts (login, pass, email) +VALUES ('Foo', 'Bar', 'foo@example.com'); + +INSERT INTO local_services (name, description, owner_id) +VALUES ('Cheap Tees', 'Unlimited possiblities! You can move the water masks to create your own frame. It gives you an opportunity to fit the layers to your image. Create great final effect with seconds', 1), + ('Tema Model Agency Ltd', 'Special for website developers and app ui designers, to preview their apps in a professional way, showcasing details and focus on Responsive Design for Website and apps, Vol 09.', 1), + ('Neet Online Test Series - Nots', 'Advanced, easy to edit mockup. It contains everything you need to create a realistic look of your project. Guarantees the a good look for bright and dark designs and perfect fit to the shape. Easy to navigate, well described layers, friendly help file.', 1); + +INSERT INTO local_service_items (name, price, local_service_id, item_order) +VALUES ('Water Frame', 23423, 1, 1), + ('Macbook Laptop Display 2.0', 927, 1, 2), + ('Paper Band', 920, 1, 3), + ('Artbox', 2500, 2, 1), + ('School Backpacks', 2150, 2, 2), + ('Premium Paper Cup', 2090, 3, 1), + ('Beer Black Bottle', 5000, 3, 2), + ('Circle Photo Frame', 2300, 3, 3), + ('Grey T-Shirt', 3430, 3, 4); diff --git a/migrations/20220704123702_add_email.sql b/migrations/20220704123702_add_email.sql new file mode 100644 index 0000000..f6341dc --- /dev/null +++ b/migrations/20220704123702_add_email.sql @@ -0,0 +1 @@ +ALTER TABLE accounts ADD COLUMN email text not null default '' unique; diff --git a/src/model/db.rs b/src/model/db.rs index e95bb1e..a04a27c 100644 --- a/src/model/db.rs +++ b/src/model/db.rs @@ -8,6 +8,7 @@ use uuid::Uuid; pub struct Account { pub id: i32, pub login: String, + pub email: String, pub pass: String, } @@ -19,15 +20,15 @@ pub enum Role { #[derive(Debug, Copy, Clone, Serialize, Deserialize, Type)] pub enum LocalServiceState { - Pending, - Approved, - Banned, - Pinned, - Internal, + Pending, + Approved, + Banned, + Pinned, + Internal, } impl LocalServiceState { - pub fn to_str(&self) -> &str { + pub fn as_str(&self) -> &str { match self { Self::Pending => "Pending", Self::Approved => "Approved", diff --git a/src/routes/unrestricted.rs b/src/routes/unrestricted.rs index 187c8ef..907358a 100644 --- a/src/routes/unrestricted.rs +++ b/src/routes/unrestricted.rs @@ -66,10 +66,25 @@ FROM local_service_items HttpResponse::Ok().body(body) } +#[derive(Template)] +#[template(path = "account.html")] +struct AccountTemplate; + +#[get("/account")] +async fn account() -> HttpResponse { + HttpResponse::Ok().body(AccountTemplate.render().unwrap()) +} + pub fn configure(config: &mut ServiceConfig) { config .service(Files::new("/assets/images", "./assets/images")) .service(Files::new("/assets/css", "./assets/css")) - .service(Files::new("/assets/js", "./assets/js").use_etag(true).prefer_utf8(true).show_files_listing()) - .service(index); + .service( + Files::new("/assets/js", "./assets/js") + .use_etag(true) + .prefer_utf8(true) + .show_files_listing(), + ) + .service(index) + .service(account); }