Add register simple user and sessions
This commit is contained in:
parent
bae9f3415c
commit
7abf25345f
765
Cargo.lock
generated
765
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
26
Cargo.toml
@ -4,14 +4,13 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix = "*"
|
||||
actix-web = "*"
|
||||
actix-cors = "*"
|
||||
actix-rt = "*"
|
||||
actix-files = "*"
|
||||
# actix-web-4-validator = { version = "3.2.0", default-features = false }
|
||||
actix-4-jwt-auth = "0.4.2"
|
||||
# actix-web-security = "*"
|
||||
actix = { version = "*" }
|
||||
actix-web = { version = "*" }
|
||||
actix-cors = { version = "*" }
|
||||
actix-rt = { version = "*" }
|
||||
actix-files = { version = "*" }
|
||||
actix-redis = { version = "0.11.0" }
|
||||
actix-identity = { version = "0.4.0" }
|
||||
askama = { version = "*" }
|
||||
validator = { version = "0.14", features = ["derive"] }
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
@ -19,7 +18,10 @@ serde_json = { version = "*" }
|
||||
sqlx = { version = "*", features = ["runtime-actix-rustls", "postgres", "uuid", "chrono"] }
|
||||
uuid = { version = "*", features = ["serde"] }
|
||||
chrono = { version = "*", features = ["serde"] }
|
||||
gumdrop = "*"
|
||||
tracing = "*"
|
||||
tracing-subscriber = "*"
|
||||
tracing-actix-web = "*"
|
||||
gumdrop = { version = "*" }
|
||||
tracing = { version = "*" }
|
||||
tracing-subscriber = { version = "*" }
|
||||
tracing-actix-web = { version = "*" }
|
||||
argon2 = { version = "0.4.1" }
|
||||
password-hash = { version = "0.4.2" }
|
||||
rand = { version = "0.8.5", features = [] }
|
||||
|
@ -1,30 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<title>OS Wilno</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/assets/css/reset.css" rel="stylesheet"/>
|
||||
<link href="/assets/css/app.css" rel="stylesheet"/>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico" />
|
||||
<script type="module" src=/assets/js/app.js></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<div class="bg">
|
||||
<h1>OS Wilno</h1>
|
||||
</div>
|
||||
</header>
|
||||
<article>
|
||||
<ow-nav>
|
||||
<ow-path path="/">Lokalne Usługi</ow-path>
|
||||
<ow-path path="/">Aktualności</ow-path>
|
||||
<ow-path path="/account" selected="selected">Konto</ow-path>
|
||||
</ow-nav>
|
||||
<ow-account>
|
||||
</ow-account>
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% match account.as_ref() %}
|
||||
{% when Some with (a) %}
|
||||
<ow-account
|
||||
mode="display"
|
||||
id="{{a.id}}"
|
||||
name="{{a.login}}"
|
||||
email="{{a.email}}"
|
||||
facebook-id="{{a.facebook_id.as_deref().unwrap_or_default()}}"
|
||||
></ow-account>
|
||||
{% when None %}
|
||||
<ow-account></ow-account>
|
||||
{% endmatch %}
|
||||
{% endblock %}
|
||||
|
30
assets/templates/base.html
Normal file
30
assets/templates/base.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<title>OS Wilno</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/assets/css/reset.css" rel="stylesheet"/>
|
||||
<link href="/assets/css/app.css" rel="stylesheet"/>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico" />
|
||||
<script type="module" src=/assets/js/app.js></script>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<div class="bg">
|
||||
<h1>OS Wilno</h1>
|
||||
</div>
|
||||
</header>
|
||||
<article>
|
||||
<ow-nav>
|
||||
<ow-path path="/" selected="{{ page.select_index() }}">Lokalne Usługi</ow-path>
|
||||
<ow-path path="/news" selected="{{ page.select_news() }}">Aktualności</ow-path>
|
||||
<ow-path path="/account" selected="{{ page.select_account() }}">Konto</ow-path>
|
||||
</ow-nav>
|
||||
{% block content %}{% endblock %}
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
@ -1,26 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<title>OS Wilno</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/assets/css/reset.css" rel="stylesheet"/>
|
||||
<link href="/assets/css/app.css" rel="stylesheet"/>
|
||||
<script type="module" src=/assets/js/app.js></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<div class="bg">
|
||||
<h1>OS Wilno</h1>
|
||||
</div>
|
||||
</header>
|
||||
<article>
|
||||
<ow-nav>
|
||||
<ow-path path="/" selected="selected">Lokalne Usługi</ow-path>
|
||||
<ow-path path="/">Aktualności</ow-path>
|
||||
<ow-path path="/account">Konto</ow-path>
|
||||
</ow-nav>
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<local-services>
|
||||
{% for service in services %}
|
||||
<local-service
|
||||
@ -41,7 +20,4 @@
|
||||
</local-service>
|
||||
{% endfor %}
|
||||
</local-services>
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
282
client/dist/app.js
vendored
282
client/dist/app.js
vendored
@ -3,9 +3,10 @@ const FORM_STYLE = `
|
||||
form {
|
||||
display: block;
|
||||
}
|
||||
form.inline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
form legend {
|
||||
margin: 16px 0;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
form > div {
|
||||
display: block;
|
||||
@ -102,12 +103,9 @@ class PseudoForm extends HTMLElement {
|
||||
}
|
||||
}
|
||||
const fireFbReady = ()=>{
|
||||
for (let c of (b = !0, a))c();
|
||||
for (let c of (!0, a))c();
|
||||
};
|
||||
const runFbReady = (c)=>{
|
||||
b ? c() : a.push(c);
|
||||
};
|
||||
let a = [], b = !1;
|
||||
let a = [];
|
||||
customElements.define("form-navigation", class extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return [
|
||||
@ -133,7 +131,7 @@ customElements.define("form-navigation", class extends HTMLElement {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
<form class="inline">
|
||||
<form>
|
||||
<div class="actions">
|
||||
<input id="prev" type="button" value="Wróć" />
|
||||
<input id="next" type="submit" value="Następny" />
|
||||
@ -355,19 +353,23 @@ customElements.define("login-form", class extends HTMLElement {
|
||||
customElements.define("ow-account", class extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return [
|
||||
"mode"
|
||||
"mode",
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"facebook-id"
|
||||
];
|
||||
}
|
||||
constructor(){
|
||||
super();
|
||||
let b = this[S] = this.attachShadow({
|
||||
let c = this[S] = this.attachShadow({
|
||||
mode: "closed"
|
||||
});
|
||||
b.innerHTML = `
|
||||
c.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Noto Sans', sans-serif; }
|
||||
article > * {
|
||||
#form > * {
|
||||
display: none;
|
||||
}
|
||||
:host([mode="login"]) login-form, :host([mode="login"]) #switch-register {
|
||||
@ -376,6 +378,12 @@ customElements.define("ow-account", class extends HTMLElement {
|
||||
:host([mode="register"]) register-form, :host([mode="register"]) #switch-login {
|
||||
display: block;
|
||||
}
|
||||
#display {
|
||||
display: none;
|
||||
}
|
||||
:host([mode="display"]) #display { display: block; }
|
||||
:host([mode="display"]) #form { display: none; }
|
||||
|
||||
a{
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
@ -385,8 +393,10 @@ customElements.define("ow-account", class extends HTMLElement {
|
||||
border-bottom-color: var(--hover-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
${FORM_STYLE}
|
||||
</style>
|
||||
<article>
|
||||
<article id="form">
|
||||
<login-form></login-form>
|
||||
<register-form></register-form>
|
||||
<section id="switch-register">
|
||||
@ -396,23 +406,77 @@ customElements.define("ow-account", class extends HTMLElement {
|
||||
<a>Masz konta? Zaloguj się</a>
|
||||
</section>
|
||||
</article>
|
||||
`, b.querySelector("#switch-login > a").addEventListener("click", (a)=>{
|
||||
<article id="display">
|
||||
<div>
|
||||
<input id="id" name="id" readonly type="hidden" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Login</label>
|
||||
<input id="name" name="name" readonly />
|
||||
</div>
|
||||
<div>
|
||||
<label>E-Mail</label>
|
||||
<input id="email" name="email" readonly />
|
||||
</div>
|
||||
<div>
|
||||
<label>Powiązane konto Facebook</label>
|
||||
<input id="facebook_id" name="facebook_id" readonly />
|
||||
</div>
|
||||
</article>
|
||||
`, c.querySelector("#switch-login > a").addEventListener("click", (a)=>{
|
||||
a.stopPropagation(), a.preventDefault(), this.mode = "login";
|
||||
}), b.querySelector("#switch-register > a").addEventListener("click", (a)=>{
|
||||
}), c.querySelector("#switch-register > a").addEventListener("click", (a)=>{
|
||||
a.stopPropagation(), a.preventDefault(), this.mode = "register";
|
||||
});
|
||||
}
|
||||
connectedCallback() {
|
||||
this.mode = "login";
|
||||
"" === this.mode && (this.mode = "login");
|
||||
}
|
||||
attributeChangedCallback(a, b, c) {
|
||||
if (b === c) return;
|
||||
if (b !== c) switch(a){
|
||||
case "mode":
|
||||
this.mode = c;
|
||||
break;
|
||||
case "id":
|
||||
this.id = c;
|
||||
break;
|
||||
case "name":
|
||||
this.name = c;
|
||||
break;
|
||||
case "email":
|
||||
this.email = c;
|
||||
break;
|
||||
case "facebook-id":
|
||||
this.facebook_id = c;
|
||||
}
|
||||
}
|
||||
get mode() {
|
||||
return this.getAttribute("mode");
|
||||
return this.getAttribute("mode") || "";
|
||||
}
|
||||
set mode(a) {
|
||||
a = "login" === a || "register" === a ? a : "login", this.setAttribute("mode", a);
|
||||
a = [
|
||||
"login",
|
||||
"register",
|
||||
"display"
|
||||
].includes(a) ? a : "login", this.setAttribute("mode", a);
|
||||
}
|
||||
get name() {
|
||||
return this.getAttribute("name") || "";
|
||||
}
|
||||
set name(b) {
|
||||
this.setAttribute("name", b), this[S].querySelector("#display #name").value = b;
|
||||
}
|
||||
get email() {
|
||||
return this.getAttribute("email") || "";
|
||||
}
|
||||
set email(b) {
|
||||
this.setAttribute("email", b), this[S].querySelector("#display #email").value = b;
|
||||
}
|
||||
get facebook_id() {
|
||||
return this.getAttribute("facebook-id");
|
||||
}
|
||||
set facebook_id(b) {
|
||||
this.setAttribute("facebook-id", b), this[S].querySelector("#display #facebook_id").value = b;
|
||||
}
|
||||
});
|
||||
customElements.define("ow-nav", class extends HTMLElement {
|
||||
@ -667,7 +731,7 @@ customElements.define("register-basic-form", class extends PseudoForm {
|
||||
<label>Hasło</label>
|
||||
<input name="pass" placeholder="Hasło" type="password" required />
|
||||
</div>
|
||||
<form-navigation prev="hidden"></form-navigation>
|
||||
<form-navigation></form-navigation>
|
||||
</form>
|
||||
`;
|
||||
let d = c.querySelector("form");
|
||||
@ -737,8 +801,8 @@ customElements.define("register-item-form-row", class extends PseudoForm {
|
||||
}
|
||||
get inputs() {
|
||||
return [
|
||||
this[S].querySelector("input.item-name").cloneNode(!0),
|
||||
this[S].querySelector("input.item-price").cloneNode(!0),
|
||||
this[S].querySelector(".item-name").cloneNode(!0),
|
||||
this[S].querySelector(".item-price").cloneNode(!0),
|
||||
];
|
||||
}
|
||||
updateNames() {
|
||||
@ -870,6 +934,7 @@ customElements.define("register-submit-form", class extends PseudoForm {
|
||||
<label>Description</label>
|
||||
<input readonly id="preview-description">
|
||||
</div>
|
||||
<input type="hidden" name="account_type" id="account_type" />
|
||||
<div id="items">
|
||||
</div>
|
||||
|
||||
@ -890,6 +955,9 @@ customElements.define("register-submit-form", class extends PseudoForm {
|
||||
f.setAttribute("readonly", "readonly"), e.appendChild(f), e.appendChild(document.createElement("price-view")).value = g.value, g.setAttribute("readonly", "readonly"), g.setAttribute("type", "hidden"), e.appendChild(g);
|
||||
}
|
||||
}
|
||||
set accountType(a) {
|
||||
this[S].querySelector("#account_type").value = a;
|
||||
}
|
||||
});
|
||||
customElements.define("register-user-type", class extends HTMLElement {
|
||||
constructor(){
|
||||
@ -961,102 +1029,104 @@ customElements.define("register-user-type", class extends HTMLElement {
|
||||
});
|
||||
}
|
||||
});
|
||||
customElements.define("register-oauth2", class extends HTMLElement {
|
||||
customElements.define("register-user-form", class extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return [
|
||||
"fb-connected"
|
||||
"mode"
|
||||
];
|
||||
}
|
||||
constructor(){
|
||||
super();
|
||||
let a = this.attachShadow({
|
||||
let c = this[S] = this.attachShadow({
|
||||
mode: "closed"
|
||||
});
|
||||
a.innerHTML = `
|
||||
c.innerHTML = `
|
||||
<style>
|
||||
.loginBtn {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
/* width: 13em; - apply for fixed size */
|
||||
margin: 0.2em;
|
||||
padding: 0 15px 0 46px;
|
||||
border: none;
|
||||
text-align: left;
|
||||
line-height: 34px;
|
||||
white-space: nowrap;
|
||||
border-radius: 0.2em;
|
||||
font-size: 16px;
|
||||
color: #FFF;
|
||||
:host { display: block; }
|
||||
svg { width: 200px; }
|
||||
* { font-family: 'Noto Sans', sans-serif; }
|
||||
#icons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.loginBtn:before {
|
||||
content: "";
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 34px;
|
||||
height: 100%;
|
||||
#form {
|
||||
display: none;
|
||||
}
|
||||
.loginBtn:focus {
|
||||
outline: none;
|
||||
:host([mode="email"]) #form {
|
||||
display: block;
|
||||
}
|
||||
.loginBtn:active {
|
||||
box-shadow: inset 0 0 0 32px rgba(0,0,0,0.1);
|
||||
:host([mode="email"]) #email-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Facebook */
|
||||
:host([fb-connected]) .loginBtn--facebook {
|
||||
display: none !important;
|
||||
#icons .option {
|
||||
display: block;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.loginBtn--facebook {
|
||||
background-color: #4C69BA;
|
||||
background-image: linear-gradient(#4C69BA, #3B55A0);
|
||||
/*font-family: "Helvetica neue", Helvetica Neue, Helvetica, Arial, sans-serif;*/
|
||||
text-shadow: 0 -1px 0 #354C8C;
|
||||
}
|
||||
.loginBtn--facebook:before {
|
||||
border-right: #364e92 1px solid;
|
||||
background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/14082/icon_facebook.png') 6px 6px no-repeat;
|
||||
}
|
||||
.loginBtn--facebook:hover,
|
||||
.loginBtn--facebook:focus {
|
||||
background-color: #5B7BD5;
|
||||
background-image: linear-gradient(#5B7BD5, #4864B1);
|
||||
.option {
|
||||
min-width: 45%;
|
||||
}
|
||||
${FORM_STYLE}
|
||||
</style>
|
||||
<button class="loginBtn loginBtn--facebook">
|
||||
Login with Facebook
|
||||
</button>
|
||||
<section id="icons">
|
||||
<div class="option">
|
||||
<svg id="email-icon" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<path d="M123.25 24.192c0-.018 0-.034-.005-.052s-.007-.063-.009-.094a1.734 1.734 0 0 0-.083-.408c-.006-.018 0-.037-.011-.055s-.01-.015-.013-.023a1.734 1.734 0 0 0-.227-.407c-.021-.028-.043-.053-.066-.08a1.755 1.755 0 0 0-.31-.294c-.012-.009-.022-.02-.034-.028a1.744 1.744 0 0 0-.414-.2c-.034-.012-.068-.022-.1-.032a1.733 1.733 0 0 0-.474-.073H6.5a1.733 1.733 0 0 0-.474.073c-.035.01-.068.02-.1.032a1.744 1.744 0 0 0-.414.2c-.012.008-.022.019-.034.028a1.755 1.755 0 0 0-.31.294c-.022.027-.045.052-.066.08a1.734 1.734 0 0 0-.227.407c0 .008-.01.015-.013.023s-.005.037-.011.055a1.734 1.734 0 0 0-.083.408c0 .032-.009.063-.009.094s-.005.034-.005.052v79.615c0 .023.006.045.007.068a1.737 1.737 0 0 0 .019.188c.008.051.015.1.027.152a1.74 1.74 0 0 0 .056.179c.017.047.033.094.054.139a1.729 1.729 0 0 0 .093.172c.024.04.048.081.075.119a1.743 1.743 0 0 0 .125.152c.033.036.066.072.1.106.021.019.037.042.059.061s.036.017.052.03a1.736 1.736 0 0 0 .452.263c.035.014.071.022.107.033a1.732 1.732 0 0 0 .488.085c.012 0 .023.006.035.006H121.501c.012 0 .023-.006.034-.006a1.732 1.732 0 0 0 .489-.085c.035-.011.07-.019.1-.033a1.736 1.736 0 0 0 .453-.263c.016-.013.036-.017.052-.03s.038-.042.059-.061c.036-.034.069-.069.1-.106a1.743 1.743 0 0 0 .125-.152c.027-.038.051-.078.075-.119a1.729 1.729 0 0 0 .093-.172c.021-.045.037-.092.054-.139a1.74 1.74 0 0 0 .056-.179c.012-.05.019-.1.027-.152a1.737 1.737 0 0 0 .019-.188c0-.023.007-.045.007-.068zM45.8 60.316l17.058 14.677a1.751 1.751 0 0 0 2.283 0L82.2 60.316l35.512 41.741H10.289zM8.25 99.052V28.007l34.9 30.026zm76.6-41.019 34.9-30.026v71.045zm31.931-32.091L81.276 56.493c-.006.005-.014.008-.02.014l-.019.02L64 71.358 46.763 56.527l-.019-.02-.02-.014-35.507-30.551z"/>
|
||||
</svg>
|
||||
|
||||
<form>
|
||||
<legend>Rejestracja adresem e-mail</legend>
|
||||
<form id="form" method="post" action="/register">
|
||||
<input type="hidden" id="facebook_id" name="facebook_id">
|
||||
<div>
|
||||
<label>E-Mail</label>
|
||||
<input type="email" name="email">
|
||||
<input id="email" type="email" name="email">
|
||||
</div>
|
||||
<div>
|
||||
<label>Login</label>
|
||||
<input type="text" name="login">
|
||||
<input id="login" type="text" name="login">
|
||||
</div>
|
||||
<div>
|
||||
<label>Hasło</label>
|
||||
<input type="password" name="password">
|
||||
<input id="password" type="password" name="password">
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Zarejestruj" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="option">
|
||||
<svg id="fb-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#3B5998" d="M448 512h-87.999c-22.094 0-39.999-17.922-39.999-40V360.001c0-22.094 17.905-39.999 39.999-39.999h12.922c8.32 0 16.844-6.656 19.062-15.031l8.484-31.953c2.484-9.312-2.812-17.023-11.852-17.023H360c-22.094 0-39.999-17.902-39.999-39.988v-16C320.001 177.905 337.906 160 360 160h24.008c8.82 0 16-7.156 16-16v-32.004c0-8.828-7.18-16.004-16-16.004H336c-44.187 0-79.991 35.828-79.991 80.011v40.004c0 22.085-17.922 39.988-40.003 39.988h-7.625c-9.039 0-16.379 7.711-16.379 17.023v31.953c0 8.375 6.746 15.031 15.07 15.031h8.934c22.082 0 40.003 17.905 40.003 39.999V472c0 22.078-17.922 40-40.003 40h-8c-8.844 0-16.004-7.172-16.004-16 0-8.844 7.16-16 16.004-16 8.824 0 16-7.172 16-16v-95.999c0-8.844-7.175-16-16-16H197.51c-20.719 0-37.511-16.578-37.511-37.577v-47.922c0-23.156 18.371-42.512 41.027-42.512h6.98c8.824 0 16-7.16 16-15.984v-40.004c0-57.441 46.559-103.995 103.995-103.995h64.008c22.086 0 39.984 17.902 39.984 39.988v48.004c0 22.085-17.898 40.003-39.984 40.003H352.001v16.004c0 8.824 7.156 15.984 16 15.984h29.969c22.672 0 37.398 19.355 33.062 42.512l-8.992 47.922c-3.93 20.999-23.82 37.577-44.539 37.577h-9.5c-8.844 0-16 7.156-16 16V464c0 8.828 7.156 16 16 16H448c17.672 0 32-14.328 32-32V64.007c0-17.688-14.328-32.003-32-32.003H64.007c-17.672 0-32.003 14.316-32.003 32.003V448c0 17.672 14.332 32 32.003 32h16c8.828 0 16.003 7.156 16.003 16 0 8.828-7.175 16-16.003 16h-16C28.668 512 0 483.344 0 448V64.007C0 28.648 28.668 0 64.007 0H448c35.359 0 64 28.648 64 64.007V448c0 35.344-28.641 64-64 64zm-304.001-32c8.844 0 16 7.156 16 16 0 8.828-7.156 16-16 16-8.828 0-16.004-7.172-16.004-16 0-8.844 7.176-16 16.004-16z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
let d = c.querySelector("form");
|
||||
c.querySelector("#fb-icon").addEventListener("click", (a)=>{
|
||||
a.stopPropagation(), a.preventDefault(), this.mode = "facebook", FB.login((a)=>{
|
||||
"connected" === a.status && FB.api("/me?fields=id,name,email", ({ id: a , name: b , email: c , ...e })=>{
|
||||
console.log(a, b, c, e), d.querySelector("#email").value = c, d.querySelector("#login").value = b, d.querySelector("#password").value = crypto.randomUUID(), d.querySelector("#facebook_id").value = a, d.submit();
|
||||
});
|
||||
}, {
|
||||
scope: "public_profile,email",
|
||||
return_scopes: !0
|
||||
});
|
||||
}), c.querySelector("#email-icon").addEventListener("click", (a)=>{
|
||||
a.stopPropagation(), a.preventDefault(), this.mode = "email";
|
||||
});
|
||||
}
|
||||
connectedCallback() {
|
||||
runFbReady(()=>{
|
||||
FB.getLoginStatus(({ status: a })=>{
|
||||
"connected" !== a ? FB.login() : this.setAttribute("fb-connected", "1");
|
||||
});
|
||||
});
|
||||
this.mode = "";
|
||||
}
|
||||
attributeChangedCallback(a, b, c) {
|
||||
b !== c && "mode" === a && ("email" === c || "facebook" === c || "" === c) && (this.mode = c);
|
||||
}
|
||||
get mode() {
|
||||
return this.getAttribute("mode") || "";
|
||||
}
|
||||
set mode(a) {
|
||||
this.setAttribute("mode", a);
|
||||
}
|
||||
});
|
||||
let c = (a, b)=>{
|
||||
@ -1087,7 +1157,7 @@ customElements.define("register-form", class extends HTMLElement {
|
||||
:host([step="2"]) #step-2 { display: block; }
|
||||
:host([step="3"]) #step-3 { display: block; }
|
||||
:host([step="4"]) #step-4 { display: block; }
|
||||
:host([step="1000"]) #step-1000 { display: block; }
|
||||
:host([step="40"]) #step-40 { display: block; }
|
||||
|
||||
${FORM_STYLE}
|
||||
|
||||
@ -1105,31 +1175,22 @@ customElements.define("register-form", class extends HTMLElement {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<article id="host">
|
||||
</article>
|
||||
<article>
|
||||
<register-user-type id="step-0">
|
||||
</register-user-type>
|
||||
|
||||
<register-basic-form id="step-1">
|
||||
</register-basic-form>
|
||||
|
||||
<register-company-form id="step-2">
|
||||
</register-company-form>
|
||||
|
||||
<register-items-form id="step-3">
|
||||
</register-items-form>
|
||||
|
||||
<register-submit-form id="step-4">
|
||||
</register-submit-form>
|
||||
|
||||
<register-oauth2 id="step-1000">
|
||||
</register-oauth2>
|
||||
<register-user-type id="step-0"> </register-user-type>
|
||||
<register-basic-form id="step-1"></register-basic-form>
|
||||
<register-company-form id="step-2"></register-company-form>
|
||||
<register-items-form id="step-3"></register-items-form>
|
||||
<register-submit-form id="step-4"></register-submit-form>
|
||||
<register-user-form id="step-40"></register-user-form>
|
||||
</article>
|
||||
`;
|
||||
let e = d.querySelector("#step-4");
|
||||
this[S].addEventListener("account:type:user", (a)=>{
|
||||
a.stopPropagation(), this.step = 1000;
|
||||
a.stopPropagation(), e.accountType = "User", this.step = 40;
|
||||
}), this[S].addEventListener("account:type:local-service", (a)=>{
|
||||
a.stopPropagation(), this.step = 1;
|
||||
a.stopPropagation(), e.accountType = "Business", this.step = 1;
|
||||
}), this[S].addEventListener("form:next", (a)=>{
|
||||
a.stopPropagation();
|
||||
let b = d.querySelector(`#step-${this.step}`);
|
||||
@ -1154,14 +1215,15 @@ customElements.define("register-form", class extends HTMLElement {
|
||||
a < 0 || this.setAttribute("step", a);
|
||||
}
|
||||
});
|
||||
if (window.fbAsyncInit = ()=>{
|
||||
if (!document.querySelector("#facebook-jssdk")) {
|
||||
window.fbAsyncInit = ()=>{
|
||||
FB.init({
|
||||
appId: "1293538251053124",
|
||||
cookie: !0,
|
||||
xfbml: !0,
|
||||
version: "v14.0"
|
||||
}), FB.AppEvents.logPageView(), fireFbReady();
|
||||
}, !document.querySelector("#facebook-jssdk")) {
|
||||
let b1 = document.createElement("script");
|
||||
b1.id = "facebook-jssdk", b1.src = "https://connect.facebook.net/en_US/sdk.js", document.head.appendChild(b1);
|
||||
};
|
||||
let b = document.createElement("script");
|
||||
b.id = "facebook-jssdk", b.src = "https://connect.facebook.net/en_US/sdk.js", document.head.appendChild(b);
|
||||
}
|
||||
|
2
client/dist/app.js.map
vendored
2
client/dist/app.js.map
vendored
File diff suppressed because one or more lines are too long
@ -4,13 +4,14 @@ import "./local-service-item.js";
|
||||
import "./local-services.js";
|
||||
import "./login-form.js";
|
||||
import "./ow-account.js";
|
||||
import "./ow-nav.js";
|
||||
import "./ow-path.js";
|
||||
import "./nav/ow-nav.js";
|
||||
import "./nav/ow-path.js";
|
||||
import "./price/price-view";
|
||||
import "./price/price-input";
|
||||
import "./register-form.js";
|
||||
import { fireFbReady } from "./shared.js";
|
||||
|
||||
if (!document.querySelector('#facebook-jssdk')) {
|
||||
window.fbAsyncInit = () => {
|
||||
FB.init({
|
||||
appId: '1293538251053124',
|
||||
@ -22,11 +23,8 @@ window.fbAsyncInit = () => {
|
||||
fireFbReady();
|
||||
};
|
||||
|
||||
{
|
||||
if (!document.querySelector('#facebook-jssdk')) {
|
||||
const js = document.createElement('script');
|
||||
js.id = 'facebook-jssdk';
|
||||
js.src = "https://connect.facebook.net/en_US/sdk.js";
|
||||
document.head.appendChild(js);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ customElements.define('form-navigation', class extends HTMLElement {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
<form class="inline">
|
||||
<form>
|
||||
<div class="actions">
|
||||
<input id="prev" type="button" value="Wróć" />
|
||||
<input id="next" type="submit" value="Następny" />
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { S } from "./shared";
|
||||
import { S, FORM_STYLE } from "./shared";
|
||||
|
||||
customElements.define('ow-account', class extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['mode']
|
||||
return ['mode', "id", "name", 'email', "facebook-id"]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
@ -12,7 +12,7 @@ customElements.define('ow-account', class extends HTMLElement {
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Noto Sans', sans-serif; }
|
||||
article > * {
|
||||
#form > * {
|
||||
display: none;
|
||||
}
|
||||
:host([mode="login"]) login-form, :host([mode="login"]) #switch-register {
|
||||
@ -21,6 +21,12 @@ customElements.define('ow-account', class extends HTMLElement {
|
||||
:host([mode="register"]) register-form, :host([mode="register"]) #switch-login {
|
||||
display: block;
|
||||
}
|
||||
#display {
|
||||
display: none;
|
||||
}
|
||||
:host([mode="display"]) #display { display: block; }
|
||||
:host([mode="display"]) #form { display: none; }
|
||||
|
||||
a{
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
@ -30,8 +36,10 @@ customElements.define('ow-account', class extends HTMLElement {
|
||||
border-bottom-color: var(--hover-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
${FORM_STYLE}
|
||||
</style>
|
||||
<article>
|
||||
<article id="form">
|
||||
<login-form></login-form>
|
||||
<register-form></register-form>
|
||||
<section id="switch-register">
|
||||
@ -41,6 +49,23 @@ customElements.define('ow-account', class extends HTMLElement {
|
||||
<a>Masz konta? Zaloguj się</a>
|
||||
</section>
|
||||
</article>
|
||||
<article id="display">
|
||||
<div>
|
||||
<input id="id" name="id" readonly type="hidden" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Login</label>
|
||||
<input id="name" name="name" readonly />
|
||||
</div>
|
||||
<div>
|
||||
<label>E-Mail</label>
|
||||
<input id="email" name="email" readonly />
|
||||
</div>
|
||||
<div>
|
||||
<label>Powiązane konto Facebook</label>
|
||||
<input id="facebook_id" name="facebook_id" readonly />
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
shadow.querySelector('#switch-login > a').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
@ -55,21 +80,68 @@ customElements.define('ow-account', class extends HTMLElement {
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.mode = 'login';
|
||||
if (this.mode === '') this.mode = 'login';
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldV, newV) {
|
||||
if (oldV === newV) return;
|
||||
switch (name) {
|
||||
case 'mode': {
|
||||
this.mode = newV;
|
||||
break;
|
||||
}
|
||||
case 'id': {
|
||||
this.id = newV;
|
||||
break;
|
||||
}
|
||||
case 'name': {
|
||||
this.name = newV;
|
||||
break;
|
||||
}
|
||||
case 'email': {
|
||||
this.email = newV;
|
||||
break;
|
||||
}
|
||||
case 'facebook-id': {
|
||||
this.facebook_id = newV;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this.getAttribute('mode');
|
||||
return this.getAttribute('mode') || '';
|
||||
}
|
||||
|
||||
set mode(value) {
|
||||
value = value === 'login' || value === 'register' ? value : 'login';
|
||||
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[S].querySelector('#display #name').value = v;
|
||||
}
|
||||
|
||||
get email() {
|
||||
return this.getAttribute('email') || '';
|
||||
}
|
||||
|
||||
set email(v) {
|
||||
this.setAttribute('email', v);
|
||||
this[S].querySelector('#display #email').value = v;
|
||||
}
|
||||
|
||||
get facebook_id() {
|
||||
return this.getAttribute('facebook-id');
|
||||
}
|
||||
|
||||
set facebook_id(v) {
|
||||
this.setAttribute('facebook-id', v);
|
||||
this[S].querySelector('#display #facebook_id').value = v;
|
||||
}
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import "./register-form/register-items-form.js";
|
||||
import "./register-form/register-company-form";
|
||||
import "./register-form/register-submit-form";
|
||||
import "./register-form/register-user-type";
|
||||
import "./register-form/register-oauth2";
|
||||
import "./register-form/register-user-form";
|
||||
|
||||
const copyForm = (form, finalForm) => {
|
||||
form.reportValidity();
|
||||
@ -46,7 +46,7 @@ customElements.define('register-form', class extends HTMLElement {
|
||||
:host([step="2"]) #step-2 { display: block; }
|
||||
:host([step="3"]) #step-3 { display: block; }
|
||||
:host([step="4"]) #step-4 { display: block; }
|
||||
:host([step="1000"]) #step-1000 { display: block; }
|
||||
:host([step="40"]) #step-40 { display: block; }
|
||||
|
||||
${ FORM_STYLE }
|
||||
|
||||
@ -64,34 +64,27 @@ customElements.define('register-form', class extends HTMLElement {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<article id="host">
|
||||
</article>
|
||||
<article>
|
||||
<register-user-type id="step-0">
|
||||
</register-user-type>
|
||||
|
||||
<register-basic-form id="step-1">
|
||||
</register-basic-form>
|
||||
|
||||
<register-company-form id="step-2">
|
||||
</register-company-form>
|
||||
|
||||
<register-items-form id="step-3">
|
||||
</register-items-form>
|
||||
|
||||
<register-submit-form id="step-4">
|
||||
</register-submit-form>
|
||||
|
||||
<register-oauth2 id="step-1000">
|
||||
</register-oauth2>
|
||||
<register-user-type id="step-0"> </register-user-type>
|
||||
<register-basic-form id="step-1"></register-basic-form>
|
||||
<register-company-form id="step-2"></register-company-form>
|
||||
<register-items-form id="step-3"></register-items-form>
|
||||
<register-submit-form id="step-4"></register-submit-form>
|
||||
<register-user-form id="step-40"></register-user-form>
|
||||
</article>
|
||||
`;
|
||||
|
||||
const finalForm = shadow.querySelector('#step-4');
|
||||
this[S].addEventListener('account:type:user', ev => {
|
||||
ev.stopPropagation();
|
||||
this.step = 1000;
|
||||
finalForm.accountType = 'User';
|
||||
this.step = 40;
|
||||
});
|
||||
this[S].addEventListener('account:type:local-service', ev => {
|
||||
ev.stopPropagation();
|
||||
finalForm.accountType = 'Business';
|
||||
this.step = 1;
|
||||
});
|
||||
this[S].addEventListener('form:next', ev => {
|
||||
@ -106,8 +99,7 @@ customElements.define('register-form', class extends HTMLElement {
|
||||
this.step = this.step - 1;
|
||||
});
|
||||
{
|
||||
const el = finalForm;
|
||||
el.addEventListener('submit', ev => {
|
||||
finalForm.addEventListener('submit', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
|
@ -26,7 +26,7 @@ customElements.define('register-basic-form', class extends PseudoForm {
|
||||
<label>Hasło</label>
|
||||
<input name="pass" placeholder="Hasło" type="password" required />
|
||||
</div>
|
||||
<form-navigation prev="hidden"></form-navigation>
|
||||
<form-navigation></form-navigation>
|
||||
</form>
|
||||
`;
|
||||
|
||||
|
@ -68,8 +68,8 @@ customElements.define('register-item-form-row', class extends PseudoForm {
|
||||
|
||||
get inputs() {
|
||||
return [
|
||||
this[S].querySelector('input.item-name').cloneNode(true),
|
||||
this[S].querySelector('input.item-price').cloneNode(true),
|
||||
this[S].querySelector('.item-name').cloneNode(true),
|
||||
this[S].querySelector('.item-price').cloneNode(true),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -1,101 +0,0 @@
|
||||
import { runFbReady } from "../shared";
|
||||
|
||||
customElements.define('register-oauth2', class extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['fb-connected']
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const shadow = this.attachShadow({ mode: "closed" });
|
||||
shadow.innerHTML = `
|
||||
<style>
|
||||
.loginBtn {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
/* width: 13em; - apply for fixed size */
|
||||
margin: 0.2em;
|
||||
padding: 0 15px 0 46px;
|
||||
border: none;
|
||||
text-align: left;
|
||||
line-height: 34px;
|
||||
white-space: nowrap;
|
||||
border-radius: 0.2em;
|
||||
font-size: 16px;
|
||||
color: #FFF;
|
||||
}
|
||||
.loginBtn:before {
|
||||
content: "";
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 34px;
|
||||
height: 100%;
|
||||
}
|
||||
.loginBtn:focus {
|
||||
outline: none;
|
||||
}
|
||||
.loginBtn:active {
|
||||
box-shadow: inset 0 0 0 32px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
|
||||
/* Facebook */
|
||||
:host([fb-connected]) .loginBtn--facebook {
|
||||
display: none !important;
|
||||
}
|
||||
.loginBtn--facebook {
|
||||
background-color: #4C69BA;
|
||||
background-image: linear-gradient(#4C69BA, #3B55A0);
|
||||
/*font-family: "Helvetica neue", Helvetica Neue, Helvetica, Arial, sans-serif;*/
|
||||
text-shadow: 0 -1px 0 #354C8C;
|
||||
}
|
||||
.loginBtn--facebook:before {
|
||||
border-right: #364e92 1px solid;
|
||||
background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/14082/icon_facebook.png') 6px 6px no-repeat;
|
||||
}
|
||||
.loginBtn--facebook:hover,
|
||||
.loginBtn--facebook:focus {
|
||||
background-color: #5B7BD5;
|
||||
background-image: linear-gradient(#5B7BD5, #4864B1);
|
||||
}
|
||||
</style>
|
||||
<button class="loginBtn loginBtn--facebook">
|
||||
Login with Facebook
|
||||
</button>
|
||||
|
||||
<form>
|
||||
<legend>Rejestracja adresem e-mail</legend>
|
||||
<div>
|
||||
<label>E-Mail</label>
|
||||
<input type="email" name="email">
|
||||
</div>
|
||||
<div>
|
||||
<label>Login</label>
|
||||
<input type="text" name="login">
|
||||
</div>
|
||||
<div>
|
||||
<label>Hasło</label>
|
||||
<input type="password" name="password">
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Zarejestruj" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
runFbReady(() => {
|
||||
FB.getLoginStatus(({ status }) => {
|
||||
if (status !== 'connected')
|
||||
FB.login();
|
||||
else
|
||||
this.setAttribute('fb-connected', '1');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
@ -40,6 +40,7 @@ customElements.define('register-submit-form', class extends PseudoForm {
|
||||
<label>Description</label>
|
||||
<input readonly id="preview-description">
|
||||
</div>
|
||||
<input type="hidden" name="account_type" id="account_type" />
|
||||
<div id="items">
|
||||
</div>
|
||||
|
||||
@ -72,4 +73,8 @@ customElements.define('register-submit-form', class extends PseudoForm {
|
||||
el.appendChild(price);
|
||||
}
|
||||
}
|
||||
|
||||
set accountType(v) {
|
||||
this[S].querySelector('#account_type').value = v;
|
||||
}
|
||||
});
|
||||
|
127
client/src/register-form/register-user-form.js
Normal file
127
client/src/register-form/register-user-form.js
Normal file
@ -0,0 +1,127 @@
|
||||
import { S, FORM_STYLE } from "../shared";
|
||||
|
||||
customElements.define('register-user-form', class extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['mode']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const shadow = this[S] = this.attachShadow({ mode: "closed" });
|
||||
|
||||
shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; }
|
||||
svg { width: 200px; }
|
||||
* { font-family: 'Noto Sans', sans-serif; }
|
||||
#icons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#form {
|
||||
display: none;
|
||||
}
|
||||
:host([mode="email"]) #form {
|
||||
display: block;
|
||||
}
|
||||
:host([mode="email"]) #email-icon {
|
||||
display: none;
|
||||
}
|
||||
#icons .option {
|
||||
display: block;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.option {
|
||||
min-width: 45%;
|
||||
}
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<section id="icons">
|
||||
<div class="option">
|
||||
<svg id="email-icon" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<path d="M123.25 24.192c0-.018 0-.034-.005-.052s-.007-.063-.009-.094a1.734 1.734 0 0 0-.083-.408c-.006-.018 0-.037-.011-.055s-.01-.015-.013-.023a1.734 1.734 0 0 0-.227-.407c-.021-.028-.043-.053-.066-.08a1.755 1.755 0 0 0-.31-.294c-.012-.009-.022-.02-.034-.028a1.744 1.744 0 0 0-.414-.2c-.034-.012-.068-.022-.1-.032a1.733 1.733 0 0 0-.474-.073H6.5a1.733 1.733 0 0 0-.474.073c-.035.01-.068.02-.1.032a1.744 1.744 0 0 0-.414.2c-.012.008-.022.019-.034.028a1.755 1.755 0 0 0-.31.294c-.022.027-.045.052-.066.08a1.734 1.734 0 0 0-.227.407c0 .008-.01.015-.013.023s-.005.037-.011.055a1.734 1.734 0 0 0-.083.408c0 .032-.009.063-.009.094s-.005.034-.005.052v79.615c0 .023.006.045.007.068a1.737 1.737 0 0 0 .019.188c.008.051.015.1.027.152a1.74 1.74 0 0 0 .056.179c.017.047.033.094.054.139a1.729 1.729 0 0 0 .093.172c.024.04.048.081.075.119a1.743 1.743 0 0 0 .125.152c.033.036.066.072.1.106.021.019.037.042.059.061s.036.017.052.03a1.736 1.736 0 0 0 .452.263c.035.014.071.022.107.033a1.732 1.732 0 0 0 .488.085c.012 0 .023.006.035.006H121.501c.012 0 .023-.006.034-.006a1.732 1.732 0 0 0 .489-.085c.035-.011.07-.019.1-.033a1.736 1.736 0 0 0 .453-.263c.016-.013.036-.017.052-.03s.038-.042.059-.061c.036-.034.069-.069.1-.106a1.743 1.743 0 0 0 .125-.152c.027-.038.051-.078.075-.119a1.729 1.729 0 0 0 .093-.172c.021-.045.037-.092.054-.139a1.74 1.74 0 0 0 .056-.179c.012-.05.019-.1.027-.152a1.737 1.737 0 0 0 .019-.188c0-.023.007-.045.007-.068zM45.8 60.316l17.058 14.677a1.751 1.751 0 0 0 2.283 0L82.2 60.316l35.512 41.741H10.289zM8.25 99.052V28.007l34.9 30.026zm76.6-41.019 34.9-30.026v71.045zm31.931-32.091L81.276 56.493c-.006.005-.014.008-.02.014l-.019.02L64 71.358 46.763 56.527l-.019-.02-.02-.014-35.507-30.551z"/>
|
||||
</svg>
|
||||
|
||||
<form id="form" method="post" action="/register">
|
||||
<input type="hidden" id="facebook_id" name="facebook_id">
|
||||
<div>
|
||||
<label>E-Mail</label>
|
||||
<input id="email" type="email" name="email">
|
||||
</div>
|
||||
<div>
|
||||
<label>Login</label>
|
||||
<input id="login" type="text" name="login">
|
||||
</div>
|
||||
<div>
|
||||
<label>Hasło</label>
|
||||
<input id="password" type="password" name="password">
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Zarejestruj" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="option">
|
||||
<svg id="fb-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#3B5998" d="M448 512h-87.999c-22.094 0-39.999-17.922-39.999-40V360.001c0-22.094 17.905-39.999 39.999-39.999h12.922c8.32 0 16.844-6.656 19.062-15.031l8.484-31.953c2.484-9.312-2.812-17.023-11.852-17.023H360c-22.094 0-39.999-17.902-39.999-39.988v-16C320.001 177.905 337.906 160 360 160h24.008c8.82 0 16-7.156 16-16v-32.004c0-8.828-7.18-16.004-16-16.004H336c-44.187 0-79.991 35.828-79.991 80.011v40.004c0 22.085-17.922 39.988-40.003 39.988h-7.625c-9.039 0-16.379 7.711-16.379 17.023v31.953c0 8.375 6.746 15.031 15.07 15.031h8.934c22.082 0 40.003 17.905 40.003 39.999V472c0 22.078-17.922 40-40.003 40h-8c-8.844 0-16.004-7.172-16.004-16 0-8.844 7.16-16 16.004-16 8.824 0 16-7.172 16-16v-95.999c0-8.844-7.175-16-16-16H197.51c-20.719 0-37.511-16.578-37.511-37.577v-47.922c0-23.156 18.371-42.512 41.027-42.512h6.98c8.824 0 16-7.16 16-15.984v-40.004c0-57.441 46.559-103.995 103.995-103.995h64.008c22.086 0 39.984 17.902 39.984 39.988v48.004c0 22.085-17.898 40.003-39.984 40.003H352.001v16.004c0 8.824 7.156 15.984 16 15.984h29.969c22.672 0 37.398 19.355 33.062 42.512l-8.992 47.922c-3.93 20.999-23.82 37.577-44.539 37.577h-9.5c-8.844 0-16 7.156-16 16V464c0 8.828 7.156 16 16 16H448c17.672 0 32-14.328 32-32V64.007c0-17.688-14.328-32.003-32-32.003H64.007c-17.672 0-32.003 14.316-32.003 32.003V448c0 17.672 14.332 32 32.003 32h16c8.828 0 16.003 7.156 16.003 16 0 8.828-7.175 16-16.003 16h-16C28.668 512 0 483.344 0 448V64.007C0 28.648 28.668 0 64.007 0H448c35.359 0 64 28.648 64 64.007V448c0 35.344-28.641 64-64 64zm-304.001-32c8.844 0 16 7.156 16 16 0 8.828-7.156 16-16 16-8.828 0-16.004-7.172-16.004-16 0-8.844 7.176-16 16.004-16z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
const form = shadow.querySelector('form');
|
||||
|
||||
shadow.querySelector('#fb-icon').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.mode = 'facebook';
|
||||
FB.login((res) => {
|
||||
if (res.status === 'connected') {
|
||||
FB.api("/me?fields=id,name,email", ({ id, name, email, ...rest }) => {
|
||||
console.log(id, name, email, rest);
|
||||
form.querySelector('#email').value = email;
|
||||
form.querySelector('#login').value = name;
|
||||
form.querySelector('#password').value = crypto.randomUUID();
|
||||
form.querySelector('#facebook_id').value = id;
|
||||
form.submit();
|
||||
});
|
||||
}
|
||||
}, { scope: 'public_profile,email', return_scopes: true });
|
||||
});
|
||||
|
||||
shadow.querySelector('#email-icon').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
this.mode = 'email';
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.mode = '';
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldV, newV) {
|
||||
if (oldV === newV) return;
|
||||
switch (name) {
|
||||
case 'mode': {
|
||||
if (newV !== 'email' && newV !== 'facebook' && newV !== '')
|
||||
return;
|
||||
this.mode = newV;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this.getAttribute('mode') || ''
|
||||
}
|
||||
|
||||
set mode(v) {
|
||||
this.setAttribute('mode', v);
|
||||
}
|
||||
});
|
@ -4,9 +4,10 @@ export const FORM_STYLE = `
|
||||
form {
|
||||
display: block;
|
||||
}
|
||||
form.inline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
form legend {
|
||||
margin: 16px 0;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
form > div {
|
||||
display: block;
|
||||
|
1
migrations/20220706095417_add_facebook_id.sql
Normal file
1
migrations/20220706095417_add_facebook_id.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE accounts ADD COLUMN facebook_id TEXT DEFAULT NULL;
|
8
migrations/20220706130049_add_account_type.sql
Normal file
8
migrations/20220706130049_add_account_type.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TYPE "AccountType" AS ENUM (
|
||||
'User',
|
||||
'Business',
|
||||
'Admin'
|
||||
);
|
||||
|
||||
ALTER TABLE accounts
|
||||
ADD COLUMN account_type "AccountType" default 'User';
|
20
src/main.rs
20
src/main.rs
@ -1,14 +1,17 @@
|
||||
#![feature(drain_filter)]
|
||||
use actix_web::{App, HttpServer, web::Data};
|
||||
|
||||
use crate::routes::render_index;
|
||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||
use actix_web::{web, web::Data, App, HttpResponse, HttpServer};
|
||||
|
||||
mod auth;
|
||||
mod model;
|
||||
mod routes;
|
||||
mod utils;
|
||||
|
||||
#[actix_web::main] // or #[tokio::main]
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
use actix_web::middleware::Logger;
|
||||
use tracing::{Level};
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_max_level(Level::DEBUG)
|
||||
@ -23,10 +26,17 @@ async fn main() -> std::io::Result<()> {
|
||||
.unwrap();
|
||||
|
||||
HttpServer::new(move || {
|
||||
let policy = CookieIdentityPolicy::new(&[0; 32])
|
||||
.name("auth-cookie")
|
||||
.secure(false);
|
||||
|
||||
App::new()
|
||||
.wrap(Logger::default())
|
||||
.wrap(actix_web::middleware::Compress::default())
|
||||
.wrap(actix_web::middleware::Logger::default())
|
||||
.wrap(IdentityService::new(policy))
|
||||
.app_data(Data::new(pool.clone()))
|
||||
.configure(routes::configure)
|
||||
.default_service(web::to(render_index))
|
||||
})
|
||||
.bind(("0.0.0.0", 8080))?
|
||||
.run()
|
||||
|
@ -4,12 +4,21 @@ use sqlx::{FromRow, Type};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, PartialOrd, PartialEq, Serialize, Deserialize, Type)]
|
||||
pub enum AccountType {
|
||||
User,
|
||||
Business,
|
||||
Admin,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
||||
pub struct Account {
|
||||
pub id: i32,
|
||||
pub login: String,
|
||||
pub email: String,
|
||||
pub pass: String,
|
||||
pub facebook_id: Option<String>,
|
||||
pub account_type: AccountType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Type)]
|
||||
|
@ -1,6 +1,41 @@
|
||||
use crate::model::db;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Page {
|
||||
LocalServices,
|
||||
News,
|
||||
Account,
|
||||
Register,
|
||||
Login,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn select_index(&self) -> &str {
|
||||
if matches!(self, Page::LocalServices) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_news(&self) -> &str {
|
||||
if matches!(self, Page::News) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_account(&self) -> &str {
|
||||
if matches!(self, Page::Account) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LocalService {
|
||||
pub id: i32,
|
||||
|
@ -2,6 +2,8 @@ use actix_web::web::ServiceConfig;
|
||||
|
||||
mod unrestricted;
|
||||
|
||||
pub use unrestricted::render_index;
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
unrestricted::configure(config);
|
||||
}
|
||||
|
@ -1,17 +1,43 @@
|
||||
use crate::model::db;
|
||||
use crate::model::db::AccountType;
|
||||
use crate::model::view::Page;
|
||||
use crate::utils;
|
||||
use actix_files::Files;
|
||||
use actix_identity::Identity;
|
||||
use actix_web::web::{Data, ServiceConfig};
|
||||
use actix_web::*;
|
||||
use askama::Template;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
pub struct IndexTemplate {
|
||||
pub services: Vec<crate::model::view::LocalService>,
|
||||
services: Vec<crate::model::view::LocalService>,
|
||||
account: Option<db::Account>,
|
||||
error: Option<String>,
|
||||
page: Page,
|
||||
}
|
||||
|
||||
pub async fn render_index() -> HttpResponse {
|
||||
HttpResponse::NotFound().body(
|
||||
IndexTemplate {
|
||||
services: vec![],
|
||||
account: None,
|
||||
error: None,
|
||||
page: Page::LocalServices,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index(db: Data<sqlx::PgPool>) -> HttpResponse {
|
||||
pub async fn index(db: Data<sqlx::PgPool>, id: Identity) -> HttpResponse {
|
||||
let pool = db.into_inner();
|
||||
let record = match id.identity() {
|
||||
Some(id) => utils::user_by_id(id, &pool).await,
|
||||
_ => None,
|
||||
};
|
||||
let (services, mut items) = {
|
||||
use crate::model::db::{LocalService, LocalServiceItem};
|
||||
let services: Vec<LocalService> = sqlx::query_as(
|
||||
@ -62,17 +88,194 @@ FROM local_service_items
|
||||
.collect()
|
||||
};
|
||||
|
||||
let body = IndexTemplate { services }.render().unwrap();
|
||||
let body = IndexTemplate {
|
||||
services,
|
||||
account: record,
|
||||
error: None,
|
||||
page: Page::LocalServices,
|
||||
}
|
||||
.render()
|
||||
.unwrap();
|
||||
HttpResponse::Ok().body(body)
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "account.html")]
|
||||
struct AccountTemplate;
|
||||
struct AccountTemplate {
|
||||
account: Option<db::Account>,
|
||||
error: Option<String>,
|
||||
page: Page,
|
||||
}
|
||||
|
||||
#[get("/account")]
|
||||
async fn account() -> HttpResponse {
|
||||
HttpResponse::Ok().body(AccountTemplate.render().unwrap())
|
||||
async fn account_page(id: Identity, db: Data<sqlx::PgPool>) -> HttpResponse {
|
||||
let pool = db.into_inner();
|
||||
let record = match id.identity() {
|
||||
Some(id) => utils::user_by_id(id, &pool).await,
|
||||
_ => None,
|
||||
};
|
||||
HttpResponse::Ok().body(
|
||||
AccountTemplate {
|
||||
account: record,
|
||||
error: None,
|
||||
page: Page::Account,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RegisterForm {
|
||||
email: String,
|
||||
login: String,
|
||||
password: String,
|
||||
facebook_id: Option<String>,
|
||||
account_type: db::AccountType,
|
||||
}
|
||||
|
||||
#[post("/register")]
|
||||
async fn register(
|
||||
form: web::Form<RegisterForm>,
|
||||
db: Data<sqlx::PgPool>,
|
||||
id: Identity,
|
||||
) -> HttpResponse {
|
||||
let form = form.into_inner();
|
||||
let pool = db.into_inner();
|
||||
if form.account_type == AccountType::Admin {
|
||||
return HttpResponse::BadRequest().body("Breach attempt detected!");
|
||||
}
|
||||
|
||||
let pass = match utils::encrypt(&form.password) {
|
||||
Ok(pass) => pass,
|
||||
Err(e) => {
|
||||
tracing::error!("{:?}", e);
|
||||
return HttpResponse::BadRequest().body(
|
||||
AccountTemplate {
|
||||
account: None,
|
||||
error: Some("Zapisanie hasła nie powiodło się".into()),
|
||||
page: Page::Register,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let res: sqlx::Result<db::Account> = sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO accounts (login, email, pass, facebook_id, account_type)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, login, email, pass, facebook_id, account_type
|
||||
"#,
|
||||
)
|
||||
.bind(form.login)
|
||||
.bind(form.email)
|
||||
.bind(pass)
|
||||
.bind(form.facebook_id)
|
||||
.fetch_one(&*pool)
|
||||
.await;
|
||||
match res {
|
||||
Ok(res) => {
|
||||
id.remember(format!("{}", res.id));
|
||||
HttpResponse::Ok().body(
|
||||
AccountTemplate {
|
||||
account: Some(res),
|
||||
error: None,
|
||||
page: Page::Register,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
HttpResponse::BadRequest().body(
|
||||
AccountTemplate {
|
||||
account: None,
|
||||
error: Some("Problem z utworzeniem konta".into()),
|
||||
page: Page::Register,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/logout")]
|
||||
async fn logout(id: Identity) -> HttpResponse {
|
||||
id.forget();
|
||||
HttpResponse::SeeOther()
|
||||
.append_header(("Location", "/"))
|
||||
.body(
|
||||
IndexTemplate {
|
||||
services: vec![],
|
||||
account: None,
|
||||
error: None,
|
||||
page: Page::LocalServices,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LoginForm {
|
||||
email: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[post("/login")]
|
||||
async fn login(form: web::Form<LoginForm>, db: Data<sqlx::PgPool>, id: Identity) -> HttpResponse {
|
||||
let pool = db.into_inner();
|
||||
let form = form.into_inner();
|
||||
let record: db::Account = match sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, login, email, pass, facebook_id, account_type
|
||||
FROM accounts
|
||||
WHERE email = $1
|
||||
"#,
|
||||
)
|
||||
.bind(form.email)
|
||||
.fetch_one(&*pool)
|
||||
.await
|
||||
{
|
||||
Ok(record) => record,
|
||||
Err(e) => {
|
||||
tracing::error!("{e}");
|
||||
return HttpResponse::Ok().body(
|
||||
AccountTemplate {
|
||||
account: None,
|
||||
error: Some("Nie znaleziono konta".into()),
|
||||
page: Page::Login,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
};
|
||||
if utils::validate(&form.password, &record.pass).is_err() {
|
||||
return HttpResponse::BadRequest().body(
|
||||
AccountTemplate {
|
||||
account: None,
|
||||
error: Some("Hasło i/lub adres e-mail są nieprawidłowe".into()),
|
||||
page: Page::Login,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
id.remember(format!("{}", record.id));
|
||||
HttpResponse::Ok().body(
|
||||
AccountTemplate {
|
||||
account: Some(record),
|
||||
error: None,
|
||||
page: Page::Login,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
@ -86,5 +289,8 @@ pub fn configure(config: &mut ServiceConfig) {
|
||||
.show_files_listing(),
|
||||
)
|
||||
.service(index)
|
||||
.service(account);
|
||||
.service(account_page)
|
||||
.service(register)
|
||||
.service(logout)
|
||||
.service(login);
|
||||
}
|
||||
|
52
src/utils.rs
Normal file
52
src/utils.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use crate::model::db;
|
||||
use argon2::{Algorithm, Argon2, Params, Version};
|
||||
use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
|
||||
|
||||
pub fn encrypt(pass: &str) -> password_hash::Result<String> {
|
||||
tracing::debug!("Hashing password {:?}", pass);
|
||||
Ok(
|
||||
Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default())
|
||||
.hash_password(pass.as_bytes(), &SaltString::generate(rand::rngs::OsRng))?
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn validate(pass: &str, pass_hash: &str) -> password_hash::Result<()> {
|
||||
tracing::debug!("Validating password {:?} {:?}", pass, pass_hash);
|
||||
|
||||
Argon2::default().verify_password(
|
||||
pass.as_bytes(),
|
||||
&PasswordHash::new(pass_hash).expect("Invalid hashed password"),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn user_by_id(id: String, pool: &sqlx::PgPool) -> Option<db::Account> {
|
||||
match sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, login, email, pass, facebook_id, account_type
|
||||
FROM accounts
|
||||
WHERE id = $1 :: INT
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
tracing::error!("{e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::{encrypt, validate};
|
||||
|
||||
#[test]
|
||||
fn check_pass() {
|
||||
let pass = "foobar";
|
||||
validate(pass, &encrypt(pass).unwrap()).unwrap();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user