Signup and Login
This beginner guide walks through building simple signup, login forms and a logout button.
In this guide, you will learn how to:
- Set up a basic CanJS application.
- Collect form data and post it to a service endpoint when the form is submitted.
The final widget looks like:
To use the widget:
- Click the "Sign up" link.
- Enter an email and password and click "SIGN UP". You will be logged in.
- Click the "Log out" link. You will be presented with the "Sign Up" form.
- Click the "Log in" link. Enter the same email and password you used to sign up. Click the "LOG IN" button. You will be logged in.
Start this tutorial by clicking the “Run in your browser” button below:
<script type="module">
import { ajax, fixture, StacheElement, type } from "//unpkg.com/can@pre/core.mjs";
fixture("POST /api/session", function(request, response) {
const userData = localStorage.getItem("user");
if (userData) {
const user = JSON.parse(userData);
const requestUser = request.data.user;
if (
user.email === requestUser.email &&
user.password === requestUser.password
) {
return request.data;
} else {
response(401, { message: "Unauthorized" }, {}, "unauthorized");
}
}
response(401, { message: "Unauthorized" }, {}, "unauthorized");
});
fixture("GET /api/session", function(request, response) {
const session = localStorage.getItem("session");
if (session) {
response(JSON.parse(session));
} else {
response(404, { message: "No session" }, {}, "unauthorized");
}
});
fixture("DELETE /api/session", function() {
localStorage.removeItem("session");
return {};
});
fixture("POST /api/users", function(request) {
const session = {
user: { email: request.data.email }
};
localStorage.setItem("user", JSON.stringify(request.data));
localStorage.setItem("session", JSON.stringify(session));
return session.user;
});
</script>
<style type="text/less">
@font-family: 'Raleway', "Helvetica Neue", Arial, sans-serif;
@font-size: 1em;
@color-dark: #54599c;
@color-light: #fff;
@color-light-gray: #d3d3d3;
@color-light-blue: #e2f5ff;
@color-error: #ff000e;
@color-error-light: #fde5ec;
@link-color: #2196F3;
body,
input,
button {
font-family: @font-family;
font-size: @font-size;
}
body {
background-color: @color-dark;
padding: 5%;
}
form {
background-color: @color-light;
padding: 30px 40px 0 40px;
border-radius: 6px;
}
input {
border: 1px solid @color-light-gray;
border-radius: 4px;
width: 93%;
padding: 3%;
margin-bottom: 20px;
&:focus {
background-color: @color-light-blue;
outline: 0;
border-color: #a1c7e8;
}
}
button {
background-color: #3ec967;
text-transform: uppercase;
letter-spacing: 2px;
border-radius: 20px;
border: 0;
color: White;
padding: 10px;
width:100%;
}
a {
color: @link-color;
}
h2 {
color: #b027a1;
text-align: center;
font-size: 2em;
}
aside {
background-color: #f1f0ff;
margin: 40px -40px;
padding: 15px;
border-radius: 0px 0px 6px 6px;
text-align: center;
color: @color-dark;
}
.welcome-message {
color: white;
text-align: center;
}
.error {
padding: 20px;
margin-top: 20px;
text-align: center;
color: @color-error;
background-color: @color-error-light;
}
</style>
This starter code includes:
- CanJS (
import { ajax, fixture, StacheElement, type } from "//unpkg.com/can@5/core.mjs"
imports can-ajax, can-fixture, can-stache-element, and can-type) - Pre-made styles so the app looks pretty 😍
- A mock service layer
The following sections are broken down into:
- The problem — A description of what the section is trying to accomplish.
- What you need to know — Information about CanJS that is useful for solving the problem.
- The solution — The solution to the problem.
Understanding the service API
The mock service layer provided by the starter code is implemented with can-fixture. The service layer supplies:
POST /api/session
for creating sessions (log in)GET /api/session
for checking if there is a sessionDELETE /api/session
for deleting a session (log out)POST /api/users
for creating users
To tell if the current client is logged in:
Request:
GET /api/session
Response:
STATUS: 200
{user: {email: "someone@email.com"}}
If someone is logged out:
Request:
GET /api/session
Response:
STATUS: 404
{message: "No session"}
To log someone in:
Request:
POST /api/session
{user: {email: "someone@email.com", password: "123"}}
Response:
STATUS: 200
{user: {email: "someone@email.com"}}
If someone logs in with invalid credentials:
Request:
POST /api/session
{user: {email: "WRONG", password: "WRONG"}}
Response:
STATUS: 401 unauthorized
{ message: "Unauthorized"}
To log someone out:
Request:
DELETE /api/session
Response:
STATUS: 200
{}
To create a user:
Request:
POST /api/users
{email: "someone@email.com", password: "123"}
Response:
STATUS: 200
{email: "someone@email.com"}
Setup
The problem
When someone adds <signup-login></signup-login>
to their HTML, we want the following HTML to show up:
<p class="welcome-message">
Welcome Someone.
<a href="javascript://">Log out</a>
</p>
<form>
<h2>Sign Up</h2>
<input placeholder="email" />
<input type="password"
placeholder="password" />
<button>Sign Up</button>
<aside>
Have an account?
<a href="javascript://">Log in</a>
</aside>
</form>
What you need to know
To set up a basic CanJS application, you define a custom element in JavaScript and use the custom element in your page’s HTML.
To define a custom element, create a class that extends can-stache-element. Then register your element via customElements.define
by calling it with the name of your custom element and the class.
For example, we will use <signup-login>
as our custom tag:
class SignupLogin extends StacheElement {}
customElements.define("signup-login", SignupLogin);
But this doesn’t do anything. Components created with can-stache-element add their own HTML through their view property like this:
class SignupLogin extends StacheElement {
static view = `
<h2>Sign Up or Log In</h2>
`
}
The solution
Update the JavaScript tab to:
import { ajax, fixture, StacheElement, type } from "//unpkg.com/can@pre/core.mjs";
fixture("POST /api/session", function(request, response) {
const userData = localStorage.getItem("user");
if (userData) {
const user = JSON.parse(userData);
const requestUser = request.data.user;
if (
user.email === requestUser.email &&
user.password === requestUser.password
) {
return request.data;
} else {
response(401, { message: "Unauthorized" }, {}, "unauthorized");
}
}
response(401, { message: "Unauthorized" }, {}, "unauthorized");
});
fixture("GET /api/session", function(request, response) {
const session = localStorage.getItem("session");
if (session) {
response(JSON.parse(session));
} else {
response(404, { message: "No session" }, {}, "unauthorized");
}
});
fixture("DELETE /api/session", function() {
localStorage.removeItem("session");
return {};
});
fixture("POST /api/users", function(request) {
const session = {
user: { email: request.data.email }
};
localStorage.setItem("user", JSON.stringify(request.data));
localStorage.setItem("session", JSON.stringify(session));
return session.user;
});
class SignupLogin extends StacheElement {
static view = `
<p class="welcome-message">
Welcome Someone.
<a href="javascript://">Log out</a>
</p>
<form>
<h2>Sign Up</h2>
<input placeholder="email" />
<input type="password"
placeholder="password" />
<button>Sign Up</button>
<aside>
Have an account?
<a href="javascript://">Log in</a>
</aside>
</form>
`;
}
customElements.define("signup-login", SignupLogin);
Update the HTML tab to:
<signup-login></signup-login>
Check if the user is logged in
The problem
Let’s make a request to GET /api/session
to know if there is an active user session. If there is a session, we will print out the user’s email address. If there is not a session, we will show the "Sign Up" form.
We’ll keep the session data within a Promise on the sessionPromise
property. The following simulates a logged in user:
signupLogin.sessionPromise = Promise.resolve({user: {email: "someone@email.com"}})
What you need to know
The props property on a StacheElement class specifies well-defined properties for each element instance via can-observable-object:
class SignupLogin extends StacheElement { static view = `{{ this.myProperty }}`; static props = { myProperty: String } }
The default property can return the initial value of a property:
class SignupLogin extends StacheElement { static view = `{{ this.myProperty }} <!-- renders “This string” -->`; static props = { myProperty: { get default() { return "This string"; } } } }
can-ajax can make requests to a URL like:
ajax({ url: "http://query.yahooapis.com/v1/public/yql", data: { format: "json", q: 'select * from geo.places where text="sunnyvale, ca"' } }) //-> Promise
Use {{# if(value) }} to do
if/else
branching in can-stache.{{# if(this.myProperty) }} Value is truth-y {{ else }} Value is false-y {{/ if }}
Promises are observable in can-stache. For the promise
myPromise
:myPromise.value
is the resolved value of the promisemyPromise.isPending
is true if the promise has not been resolved or rejectedmyPromise.isResolved
is true if the promise has resolvedmyPromise.isRejected
is true if the promise was rejectedmyPromise.reason
is the rejected value of the promise
The solution
Update the JavaScript tab to:
import { ajax, fixture, StacheElement, type } from "//unpkg.com/can@pre/core.mjs";
fixture("POST /api/session", function(request, response) {
const userData = localStorage.getItem("user");
if (userData) {
const user = JSON.parse(userData);
const requestUser = request.data.user;
if (
user.email === requestUser.email &&
user.password === requestUser.password
) {
return request.data;
} else {
response(401, { message: "Unauthorized" }, {}, "unauthorized");
}
}
response(401, { message: "Unauthorized" }, {}, "unauthorized");
});
fixture("GET /api/session", function(request, response) {
const session = localStorage.getItem("session");
if (session) {
response(JSON.parse(session));
} else {
response(404, { message: "No session" }, {}, "unauthorized");
}
});
fixture("DELETE /api/session", function() {
localStorage.removeItem("session");
return {};
});
fixture("POST /api/users", function(request) {
const session = {
user: { email: request.data.email }
};
localStorage.setItem("user", JSON.stringify(request.data));
localStorage.setItem("session", JSON.stringify(session));
return session.user;
});
class SignupLogin extends StacheElement {
static view = `
{{# if(this.sessionPromise.value) }}
<p class="welcome-message">
Welcome {{ this.sessionPromise.value.user.email }}.
<a href="javascript://">Log out</a>
</p>
{{ else }}
<form>
<h2>Sign Up</h2>
<input placeholder="email" />
<input type="password"
placeholder="password" />
<button>Sign Up</button>
<aside>
Have an account?
<a href="javascript://">Log in</a>
</aside>
</form>
{{/ if }}
`;
static props = {
sessionPromise: {
get default() {
return ajax({
url: "/api/session"
});
}
}
};
}
customElements.define("signup-login", SignupLogin);
Signup form
The problem
Let’s allow the user to enter an email and password and click "Sign Up". When this happens, we’ll POST
this data to /api/users
. Once the user is created, we’ll want to update the sessionPromise
property to have a promise with a session-like object.
A promise with a session-like object looks like:
{user: {email: "someone@email.com"}}
What you need to know
Component properties defined with can-observable-object allow you specify a type like so:
static props = { name: String, password: Number }
The key:to binding can set an input’s
value
to an element property like:<input value:to="this.name" />
Use on:event to listen to an event on an element and call a method in can-stache. For example, the following calls
doSomething()
when the<div>
is clicked:<div on:click="this.doSomething(scope.event)"> ... </div>
Notice that it also passed the event object with scope.event.
To prevent a form from submitting, call event.preventDefault().
can-ajax can make
POST
requests to a URL like:
ajax({
url: "http://query.yahooapis.com/v1/public/yql",
type: "post",
data: {
format: "json",
q: 'select * from geo.places where text="sunnyvale, ca"'
}
}) //-> Promise
Use the then() method on a Promise to map the source Promise to another Promise value.
const source = Promise.resolve({email: "justin@bitovi.com"}) const result = source.then(function(userData) { return {user: userData} });
The solution
Update the JavaScript tab to:
import { ajax, fixture, StacheElement, type } from "//unpkg.com/can@pre/core.mjs";
fixture("POST /api/session", function(request, response) {
const userData = localStorage.getItem("user");
if (userData) {
const user = JSON.parse(userData);
const requestUser = request.data.user;
if (
user.email === requestUser.email &&
user.password === requestUser.password
) {
return request.data;
} else {
response(401, { message: "Unauthorized" }, {}, "unauthorized");
}
}
response(401, { message: "Unauthorized" }, {}, "unauthorized");
});
fixture("GET /api/session", function(request, response) {
const session = localStorage.getItem("session");
if (session) {
response(JSON.parse(session));
} else {
response(404, { message: "No session" }, {}, "unauthorized");
}
});
fixture("DELETE /api/session", function() {
localStorage.removeItem("session");
return {};
});
fixture("POST /api/users", function(request) {
const session = {
user: { email: request.data.email }
};
localStorage.setItem("user", JSON.stringify(request.data));
localStorage.setItem("session", JSON.stringify(session));
return session.user;
});
class SignupLogin extends StacheElement {
static view = `
{{# if(this.sessionPromise.value) }}
<p class="welcome-message">
Welcome {{ this.sessionPromise.value.user.email }}.
<a href="javascript://">Log out</a>
</p>
{{ else }}
<form on:submit="this.signUp(scope.event)">
<h2>Sign Up</h2>
<input placeholder="email" value:to="this.email" />
<input type="password"
placeholder="password" value:to="this.password" />
<button>Sign Up</button>
<aside>
Have an account?
<a href="javascript://">Log in</a>
</aside>
</form>
{{/ if }}
`;
static props = {
email: String,
password: String,
sessionPromise: {
get default() {
return ajax({
url: "/api/session"
});
}
}
};
signUp(event) {
event.preventDefault();
this.sessionPromise = ajax({
url: "/api/users",
type: "post",
data: {
email: this.email,
password: this.password
}
}).then(function(user) {
return { user: user };
});
}
}
customElements.define("signup-login", SignupLogin);
Log out button
The problem
Let’s update the app to log the user out when the "Log out" button is clicked. We can do this by making a DELETE
request to /api/session
and updating the sessionPromise
property to have a rejected value.
What you need to know
Use then() and Promise.reject() to map a source promise to a rejected promise.
const source = Promise.resolve({}); const result = source.then(function(userData) { return Promise.reject({message: "Unauthorized"}); }); result.catch(function(reason) { reason.message //-> "Unauthorized"; });
The solution
Update the JavaScript tab to:
import { ajax, fixture, StacheElement, type } from "//unpkg.com/can@pre/core.mjs";
fixture("POST /api/session", function(request, response) {
const userData = localStorage.getItem("user");
if (userData) {
const user = JSON.parse(userData);
const requestUser = request.data.user;
if (
user.email === requestUser.email &&
user.password === requestUser.password
) {
return request.data;
} else {
response(401, { message: "Unauthorized" }, {}, "unauthorized");
}
}
response(401, { message: "Unauthorized" }, {}, "unauthorized");
});
fixture("GET /api/session", function(request, response) {
const session = localStorage.getItem("session");
if (session) {
response(JSON.parse(session));
} else {
response(404, { message: "No session" }, {}, "unauthorized");
}
});
fixture("DELETE /api/session", function() {
localStorage.removeItem("session");
return {};
});
fixture("POST /api/users", function(request) {
const session = {
user: { email: request.data.email }
};
localStorage.setItem("user", JSON.stringify(request.data));
localStorage.setItem("session", JSON.stringify(session));
return session.user;
});
class SignupLogin extends StacheElement {
static view = `
{{# if(this.sessionPromise.value) }}
<p class="welcome-message">
Welcome {{ this.sessionPromise.value.user.email }}.
<a href="javascript://" on:click="this.logOut()">Log out</a>
</p>
{{ else }}
<form on:submit="this.signUp(scope.event)">
<h2>Sign Up</h2>
<input placeholder="email" value:to="this.email" />
<input type="password"
placeholder="password" value:to="this.password" />
<button>Sign Up</button>
<aside>
Have an account?
<a href="javascript://">Log in</a>
</aside>
</form>
{{/ if }}
`;
static props = {
email: String,
password: String,
sessionPromise: {
get default() {
return ajax({
url: "/api/session"
});
}
}
};
signUp(event) {
event.preventDefault();
this.sessionPromise = ajax({
url: "/api/users",
type: "post",
data: {
email: this.email,
password: this.password
}
}).then(function(user) {
return { user: user };
});
}
logOut() {
this.sessionPromise = ajax({
url: "/api/session",
type: "delete"
}).then(function() {
return Promise.reject({ message: "Unauthorized" });
});
}
}
customElements.define("signup-login", SignupLogin);
Login form
The problem
Let’s add a "Log In" form for the user:
<form>
<h2>Log In</h2>
<input placeholder="email" />
<input type="password"
placeholder="password" />
<button>Log In</button>
<div class="error">error message</div>
<aside>
Don’t have an account?
<a href="javascript://">Sign up</a>
</aside>
</form>
The user should be able to go back and forth between the "Sign Up" page and the "Log In"
page. We’ll do this by changing a page
property to "signup"
or "login"
.
We’ll also implement the "Log In" form’s functionality. When a session is created, we’ll want to POST
session data to /api/session
and update this.sessionPromise
accordingly.
What you need to know
- Use {{# eq(value1, value2) }} to test equality in can-stache.
{{# eq(this.value1, "value 2") }} Values are equal {{ else }} Values are not equal {{/ if }}
- You can set a property value within an event binding like:
<a on:click="this.aProperty = 'a value'">Link</a>
The solution
Update the JavaScript tab to:
import { ajax, fixture, StacheElement, type } from "//unpkg.com/can@pre/core.mjs";
fixture("POST /api/session", function(request, response) {
const userData = localStorage.getItem("user");
if (userData) {
const user = JSON.parse(userData);
const requestUser = request.data.user;
if (
user.email === requestUser.email &&
user.password === requestUser.password
) {
return request.data;
} else {
response(401, { message: "Unauthorized" }, {}, "unauthorized");
}
}
response(401, { message: "Unauthorized" }, {}, "unauthorized");
});
fixture("GET /api/session", function(request, response) {
const session = localStorage.getItem("session");
if (session) {
response(JSON.parse(session));
} else {
response(404, { message: "No session" }, {}, "unauthorized");
}
});
fixture("DELETE /api/session", function() {
localStorage.removeItem("session");
return {};
});
fixture("POST /api/users", function(request) {
const session = {
user: { email: request.data.email }
};
localStorage.setItem("user", JSON.stringify(request.data));
localStorage.setItem("session", JSON.stringify(session));
return session.user;
});
class SignupLogin extends StacheElement {
static view = `
{{# if(this.sessionPromise.value) }}
<p class="welcome-message">
Welcome {{ this.sessionPromise.value.user.email }}.
<a href="javascript://" on:click="this.logOut()">Log out</a>
</p>
{{ else }}
{{# eq(this.page, "signup") }}
<form on:submit="this.signUp(scope.event)">
<h2>Sign Up</h2>
<input placeholder="email" value:to="this.email" />
<input type="password"
placeholder="password" value:to="this.password" />
<button>Sign Up</button>
<aside>
Have an account?
<a href="javascript://" on:click="this.page = 'login'">Log in</a>
</aside>
</form>
{{ else }}
<form on:submit="this.logIn(scope.event)">
<h2>Log In</h2>
<input placeholder="email" value:to="this.email" />
<input type="password"
placeholder="password" value:to="this.password" />
<button>Log In</button>
<aside>
Don’t have an account?
<a href="javascript://" on:click="this.page = 'signup'">Sign up</a>
</aside>
</form>
{{/ eq }}
{{/ if }}
`;
static props = {
email: String,
password: String,
page: { type: String, default: "login" },
sessionPromise: {
get default() {
return ajax({
url: "/api/session"
});
}
}
};
signUp(event) {
event.preventDefault();
this.sessionPromise = ajax({
url: "/api/users",
type: "post",
data: {
email: this.email,
password: this.password
}
}).then(function(user) {
return { user: user };
});
}
logOut() {
this.sessionPromise = ajax({
url: "/api/session",
type: "delete"
}).then(function() {
return Promise.reject({ message: "Unauthorized" });
});
}
logIn(event) {
event.preventDefault();
this.sessionPromise = ajax({
url: "/api/session",
type: "post",
data: {
user: {
email: this.email,
password: this.password
}
}
});
}
}
customElements.define("signup-login", SignupLogin);
Login errors
The problem
If the user tried to login, but the server responded with an error message, let’s display that error message.
<div class="error">An error message</div>
We’ll do this by catch
ing the create-session request. If the request fails, we will set a logInError
property with the server’s response data.
What you need to know
Use catch() to handle when a promise is rejected:
const source = Promise.reject({message: "foo"}) source.catch(function(reason) { reason //-> {message: "foo"} });
Use the Any type to define a property of indeterminate type:
class AppModel extends ObservableObject { static props = { myProperty: type.Any }; }; const appModel = new AppModel({}); appModel.myProperty = ANYTHING;
The solution
Update the JavaScript tab to:
import { ajax, fixture, StacheElement, type } from "//unpkg.com/can@pre/core.mjs";
fixture("POST /api/session", function(request, response) {
const userData = localStorage.getItem("user");
if (userData) {
const user = JSON.parse(userData);
const requestUser = request.data.user;
if (
user.email === requestUser.email &&
user.password === requestUser.password
) {
return request.data;
} else {
response(401, { message: "Unauthorized" }, {}, "unauthorized");
}
}
response(401, { message: "Unauthorized" }, {}, "unauthorized");
});
fixture("GET /api/session", function(request, response) {
const session = localStorage.getItem("session");
if (session) {
response(JSON.parse(session));
} else {
response(404, { message: "No session" }, {}, "unauthorized");
}
});
fixture("DELETE /api/session", function() {
localStorage.removeItem("session");
return {};
});
fixture("POST /api/users", function(request) {
const session = {
user: { email: request.data.email }
};
localStorage.setItem("user", JSON.stringify(request.data));
localStorage.setItem("session", JSON.stringify(session));
return session.user;
});
class SignupLogin extends StacheElement {
static view = `
{{# if(this.sessionPromise.value) }}
<p class="welcome-message">
Welcome {{ this.sessionPromise.value.user.email }}.
<a href="javascript://" on:click="this.logOut()">Log out</a>
</p>
{{ else }}
{{# eq(this.page, "signup") }}
<form on:submit="this.signUp(scope.event)">
<h2>Sign Up</h2>
<input placeholder="email" value:to="this.email" />
<input type="password"
placeholder="password" value:to="this.password" />
<button>Sign Up</button>
<aside>
Have an account?
<a href="javascript://" on:click="this.page = 'login'">Log in</a>
</aside>
</form>
{{ else }}
<form on:submit="this.logIn(scope.event)">
<h2>Log In</h2>
<input placeholder="email" value:to="this.email" />
<input type="password"
placeholder="password" value:to="this.password" />
<button>Log In</button>
{{# if(this.logInError) }}
<div class="error">{{ this.logInError.message }}</div>
{{/ if }}
<aside>
Don’t have an account?
<a href="javascript://" on:click="this.page = 'signup'">Sign up</a>
</aside>
</form>
{{/ eq }}
{{/ if }}
`;
static props = {
email: String,
password: String,
page: { type: String, default: "login" },
logInError: type.Any,
sessionPromise: {
get default() {
return ajax({
url: "/api/session"
});
}
}
};
signUp(event) {
event.preventDefault();
this.sessionPromise = ajax({
url: "/api/users",
type: "post",
data: {
email: this.email,
password: this.password
}
}).then(function(user) {
return { user: user };
});
}
logIn(event) {
event.preventDefault();
this.sessionPromise = ajax({
url: "/api/session",
type: "post",
data: {
user: {
email: this.email,
password: this.password
}
}
});
this.logInError = null;
this.sessionPromise.catch(
function(error) {
this.logInError = error;
}.bind(this)
);
}
logOut() {
this.sessionPromise = ajax({
url: "/api/session",
type: "delete"
}).then(function() {
return Promise.reject({ message: "Unauthorized" });
});
}
}
customElements.define("signup-login", SignupLogin);
Result
When finished, you should have something like the following code:
<signup-login></signup-login>
<script type="module">
import { ajax, fixture, StacheElement, type } from "//unpkg.com/can@pre/core.mjs";
fixture("POST /api/session", function(request, response) {
const userData = localStorage.getItem("user");
if (userData) {
const user = JSON.parse(userData);
const requestUser = request.data.user;
if (
user.email === requestUser.email &&
user.password === requestUser.password
) {
return request.data;
} else {
response(401, { message: "Unauthorized" }, {}, "unauthorized");
}
}
response(401, { message: "Unauthorized" }, {}, "unauthorized");
});
fixture("GET /api/session", function(request, response) {
const session = localStorage.getItem("session");
if (session) {
response(JSON.parse(session));
} else {
response(404, { message: "No session" }, {}, "unauthorized");
}
});
fixture("DELETE /api/session", function() {
localStorage.removeItem("session");
return {};
});
fixture("POST /api/users", function(request) {
const session = {
user: { email: request.data.email }
};
localStorage.setItem("user", JSON.stringify(request.data));
localStorage.setItem("session", JSON.stringify(session));
return session.user;
});
class SignupLogin extends StacheElement {
static view = `
{{# if(this.sessionPromise.value) }}
<p class="welcome-message">
Welcome {{ this.sessionPromise.value.user.email }}.
<a href="javascript://" on:click="this.logOut()">Log out</a>
</p>
{{ else }}
{{# eq(this.page, "signup") }}
<form on:submit="this.signUp(scope.event)">
<h2>Sign Up</h2>
<input placeholder="email" value:to="this.email" />
<input type="password"
placeholder="password" value:to="this.password" />
<button>Sign Up</button>
<aside>
Have an account?
<a href="javascript://" on:click="this.page = 'login'">Log in</a>
</aside>
</form>
{{ else }}
<form on:submit="this.logIn(scope.event)">
<h2>Log In</h2>
<input placeholder="email" value:to="this.email" />
<input type="password"
placeholder="password" value:to="this.password" />
<button>Log In</button>
{{# if(this.logInError) }}
<div class="error">{{ this.logInError.message }}</div>
{{/ if }}
<aside>
Don’t have an account?
<a href="javascript://" on:click="this.page = 'signup'">Sign up</a>
</aside>
</form>
{{/ eq }}
{{/ if }}
`;
static props = {
email: String,
password: String,
page: { type: String, default: "login" },
logInError: type.Any,
sessionPromise: {
get default() {
return ajax({
url: "/api/session"
});
}
}
};
signUp(event) {
event.preventDefault();
this.sessionPromise = ajax({
url: "/api/users",
type: "post",
data: {
email: this.email,
password: this.password
}
}).then(function(user) {
return { user: user };
});
}
logIn(event) {
event.preventDefault();
this.sessionPromise = ajax({
url: "/api/session",
type: "post",
data: {
user: {
email: this.email,
password: this.password
}
}
});
this.logInError = null;
this.sessionPromise.catch(
function(error) {
this.logInError = error;
}.bind(this)
);
}
logOut() {
this.sessionPromise = ajax({
url: "/api/session",
type: "delete"
}).then(function() {
return Promise.reject({ message: "Unauthorized" });
});
}
}
customElements.define("signup-login", SignupLogin);
</script>
<style type="text/less">
@font-family: 'Raleway', "Helvetica Neue", Arial, sans-serif;
@font-size: 1em;
@color-dark: #54599c;
@color-light: #fff;
@color-light-gray: #d3d3d3;
@color-light-blue: #e2f5ff;
@color-error: #ff000e;
@color-error-light: #fde5ec;
@link-color: #2196F3;
body,
input,
button {
font-family: @font-family;
font-size: @font-size;
}
body {
background-color: @color-dark;
padding: 5%;
}
form {
background-color: @color-light;
padding: 30px 40px 0 40px;
border-radius: 6px;
}
input {
border: 1px solid @color-light-gray;
border-radius: 4px;
width: 93%;
padding: 3%;
margin-bottom: 20px;
&:focus {
background-color: @color-light-blue;
outline: 0;
border-color: #a1c7e8;
}
}
button {
background-color: #3ec967;
text-transform: uppercase;
letter-spacing: 2px;
border-radius: 20px;
border: 0;
color: White;
padding: 10px;
width:100%;
}
a {
color: @link-color;
}
h2 {
color: #b027a1;
text-align: center;
font-size: 2em;
}
aside {
background-color: #f1f0ff;
margin: 40px -40px;
padding: 15px;
border-radius: 0px 0px 6px 6px;
text-align: center;
color: @color-dark;
}
.welcome-message {
color: white;
text-align: center;
}
.error {
padding: 20px;
margin-top: 20px;
text-align: center;
color: @color-error;
background-color: @color-error-light;
}
</style>