Improved layout & style, fixed hamburger menu in login page.

This commit is contained in:
Pēteris Caune 2018-10-09 16:12:02 +03:00
parent 160aa191c8
commit a58ce791c0
No known key found for this signature in database
GPG Key ID: E28D7679E9A9EDE2
8 changed files with 205 additions and 103 deletions

View File

@ -6,9 +6,11 @@ All notable changes to this project will be documented in this file.
### Improvements ### Improvements
- Content updates in the "Welcome" page. - Content updates in the "Welcome" page.
- Added "Docs > Third-Party Resources" page. - Added "Docs > Third-Party Resources" page.
- Improved layout and styling in "Login" page.
### Bug Fixes ### Bug Fixes
- Timezones were missing in the "Change Schedule" dialog, fixed - Timezones were missing in the "Change Schedule" dialog, fixed.
- Fix hamburger menu button in "Login" page.
## 1.1.0 - 2018-08-20 ## 1.1.0 - 2018-08-20

View File

@ -1,5 +1,7 @@
from datetime import timedelta as td from datetime import timedelta as td
from django import forms from django import forms
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -10,9 +12,37 @@ class LowercaseEmailField(forms.EmailField):
return value.lower() return value.lower()
class EmailForm(forms.Form):
email = LowercaseEmailField()
def clean_email(self):
v = self.cleaned_data["email"]
# If registration is not open then validate if an user
# account with this address exists-
if not settings.REGISTRATION_OPEN:
if not User.objects.filter(email=v).exists():
raise forms.ValidationError("Incorrect email address.")
return v
class EmailPasswordForm(forms.Form): class EmailPasswordForm(forms.Form):
identity = LowercaseEmailField() email = LowercaseEmailField()
password = forms.CharField(required=False) password = forms.CharField()
def clean(self):
username = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
if username and password:
self.user = authenticate(username=username, password=password)
if self.user is None:
raise forms.ValidationError("Incorrect email or password")
if not self.user.is_active:
raise forms.ValidationError("Account is inactive")
return self.cleaned_data
class ReportSettingsForm(forms.Form): class ReportSettingsForm(forms.Form):

View File

@ -10,7 +10,7 @@ from django.conf import settings
class LoginTestCase(TestCase): class LoginTestCase(TestCase):
def test_it_sends_link(self): def test_it_sends_link(self):
form = {"identity": "alice@example.org"} form = {"email": "alice@example.org"}
r = self.client.post("/accounts/login/", form) r = self.client.post("/accounts/login/", form)
assert r.status_code == 302 assert r.status_code == 302
@ -32,10 +32,9 @@ class LoginTestCase(TestCase):
self.client.get("/accounts/login/") self.client.get("/accounts/login/")
assert "bad_link" not in self.client.session assert "bad_link" not in self.client.session
@override_settings(REGISTRATION_OPEN=False) @override_settings(REGISTRATION_OPEN=False)
def test_it_obeys_registration_open(self): def test_it_obeys_registration_open(self):
form = {"identity": "dan@example.org"} form = {"email": "dan@example.org"}
r = self.client.post("/accounts/login/", form) r = self.client.post("/accounts/login/", form)
assert r.status_code == 200 assert r.status_code == 200
@ -45,7 +44,7 @@ class LoginTestCase(TestCase):
alice = User(username="alice", email="alice@example.org") alice = User(username="alice", email="alice@example.org")
alice.save() alice.save()
form = {"identity": "ALICE@EXAMPLE.ORG"} form = {"email": "ALICE@EXAMPLE.ORG"}
r = self.client.post("/accounts/login/", form) r = self.client.post("/accounts/login/", form)
assert r.status_code == 302 assert r.status_code == 302

View File

@ -17,7 +17,7 @@ from django.views.decorators.http import require_POST
from hc.accounts.forms import (ChangeEmailForm, EmailPasswordForm, from hc.accounts.forms import (ChangeEmailForm, EmailPasswordForm,
InviteTeamMemberForm, RemoveTeamMemberForm, InviteTeamMemberForm, RemoveTeamMemberForm,
ReportSettingsForm, SetPasswordForm, ReportSettingsForm, SetPasswordForm,
TeamNameForm) TeamNameForm, EmailForm)
from hc.accounts.models import Profile, Member from hc.accounts.models import Profile, Member
from hc.api.models import Channel, Check from hc.api.models import Channel, Check
from hc.lib.badges import get_badge_url from hc.lib.badges import get_badge_url
@ -57,44 +57,38 @@ def _ensure_own_team(request):
request.profile.save() request.profile.save()
def login(request, show_password=False): def login(request):
bad_credentials = False form = EmailPasswordForm()
magic_form = EmailForm()
if request.method == 'POST': if request.method == 'POST':
if request.POST.get("action") == "login":
form = EmailPasswordForm(request.POST) form = EmailPasswordForm(request.POST)
if form.is_valid(): if form.is_valid():
email = form.cleaned_data["identity"] auth_login(request, form.user)
password = form.cleaned_data["password"]
if len(password):
user = authenticate(username=email, password=password)
if user is not None and user.is_active:
auth_login(request, user)
return redirect("hc-checks") return redirect("hc-checks")
bad_credentials = True
show_password = True
else: else:
magic_form = EmailForm(request.POST)
if magic_form.is_valid():
email = magic_form.cleaned_data["email"]
user = None user = None
try: try:
user = User.objects.get(email=email) user = User.objects.get(email=email)
except User.DoesNotExist: except User.DoesNotExist:
if settings.REGISTRATION_OPEN: if settings.REGISTRATION_OPEN:
user = _make_user(email) user = _make_user(email)
else:
bad_credentials = True
if user: if user:
profile = Profile.objects.for_user(user) profile = Profile.objects.for_user(user)
profile.send_instant_login_link() profile.send_instant_login_link()
return redirect("hc-login-link-sent") return redirect("hc-login-link-sent")
else:
form = EmailPasswordForm()
bad_link = request.session.pop("bad_link", None) bad_link = request.session.pop("bad_link", None)
ctx = { ctx = {
"page": "login",
"form": form, "form": form,
"bad_credentials": bad_credentials, "magic_form": magic_form,
"bad_link": bad_link, "bad_link": bad_link
"show_password": show_password
} }
return render(request, "accounts/login.html", ctx) return render(request, "accounts/login.html", ctx)

View File

@ -4,7 +4,7 @@ from django.urls import include, path
from hc.accounts.views import login as hc_login from hc.accounts.views import login as hc_login
urlpatterns = [ urlpatterns = [
path('admin/login/', hc_login, {"show_password": True}), path('admin/login/', hc_login),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('accounts/', include('hc.accounts.urls')), path('accounts/', include('hc.accounts.urls')),
path('', include('hc.api.urls')), path('', include('hc.api.urls')),

74
static/css/login.css Normal file
View File

@ -0,0 +1,74 @@
.page-login h1 {
text-align: center;
margin: 40px 0;
}
.page-login .alert {
margin-bottom: 40px;
}
.page-login form p {
margin-bottom: 20px;
text-align: center;
}
.page-login form input {
margin-bottom: 20px;
}
@media (min-width: 768px) {
#magic-link-form {
margin-right: 50px;
}
#login-form {
margin-left: 50px;
}
}
#link-instruction {
color: #999;
font-style: italic;
padding: 12px 16px;
}
#login-sep {
background:#ddd;
position: absolute;
top: 10%;
right: -1px;
height: 80%;
width: 1px;
}
#login-sep div {
position: absolute;
top: 40%;
width: 40px;
left: -20px;
text-align: center;
background: #fff;
font-style: italic;
color: #666;
font-size: 12px;
padding: 8px 0;
}
#xs-login-sep {
text-align: center;
margin: 40px;
font-style: italic;
color: #666;
font-size: 12px;
height: 1px;
background: #ddd;
}
#xs-login-sep div {
position: relative;
margin: 0 auto;
width: 30px;
top: -9px;
background: #fff;
}

View File

@ -3,85 +3,91 @@
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-sm-6 col-sm-offset-3"> <div class="col-xs-10 col-xs-offset-1 col-sm-12 col-sm-offset-0 col-lg-8 col-lg-offset-2">
<div class="hc-dialog">
<h1>Sign In to {% site_name %}</h1>
{% if bad_link %} {% if bad_link %}
<h1>Incorrect Login Link</h1> <div class="alert alert-warning">
<div class="dialog-body">
<p>The login link you just used is either incorrect or expired.</p> <p>The login link you just used is either incorrect or expired.</p>
<p>Please use the form below to request a fresh login link:</p> <p>Please use the form below to request a fresh login link.</p>
</div>
{% else %}
<h1>{% site_name %}</h1>
<div class="dialog-body">
<p>
{% if show_password %}
Please enter your email address and password.
{% else %}
Please enter your email address.
Next, we'll send you an email with log-in instructions!
{% endif %}
</p>
</div> </div>
{% endif %} {% endif %}
{% if bad_credentials %} <div class="row">
<p class="alert alert-danger">Incorrect email or password.</p> <div class="col-sm-6">
{% endif %} <form id="magic-link-form" method="post">
<form method="post">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> {% if magic_form.email.errors %}
<div class="input-group input-group-lg"> <p class="text-danger">Incorrect email address.</p>
<div class="input-group-addon"> {% else %}
<span class="icon-mail"></span> <p>Enter your <strong>email address</strong>.</p>
</div>
<input
type="text"
class="form-control"
name="identity"
value="{{ form.identity.value|default:"" }}"
placeholder="you@example.org">
</div>
</div>
{% if not show_password %}
<div class="checkbox" id="password-toggle">
<label>
<input type="checkbox"> I want to use a password
</label>
</div>
{% endif %} {% endif %}
<input
type="text"
class="form-control input-lg"
name="email"
value="{{ magic_form.email.value|default:"" }}"
placeholder="you@example.org">
<div id="password-block" class="form-group {% if not show_password %} hide {% endif %}"> <p id="link-instruction">
<div class="input-group input-group-lg"> We will email you a magic sign in link.
<div class="input-group-addon"> </p>
<span class="icon-dots"></span>
<button type="submit" class="btn btn-lg btn-primary btn-block">
Email Me a Link
</button>
</form>
<div id="login-sep" class="hidden-xs"><div>or</div></div>
</div> </div>
<div class="col-xs-12 visible-xs-block">
<div id="xs-login-sep">
<div>or</div>
</div>
</div>
<div class="col-sm-6">
<form id="login-form" method="post">
{% csrf_token %}
<input type="hidden" name="action" value="login" />
{% if form.non_field_errors %}
<p class="text-danger">Incorrect email or password.</p>
{% else %}
<p>
Enter your <strong>email address</strong> and <strong>password</strong>.
</p>
{% endif %}
<input
type="text"
class="form-control input-lg"
name="email"
value="{{ form.email.value|default:"" }}"
placeholder="you@example.org">
<input <input
type="password" type="password"
class="form-control" class="form-control input-lg"
name="password" name="password"
placeholder="password"> placeholder="your password">
</div>
</div>
<div class="clearfix"> <button type="submit" class="btn btn-lg btn-primary btn-block">
<button type="submit" class="btn btn-lg btn-primary pull-right"> Sign In
Log In
</button> </button>
</div>
</form> </form>
</div> </div>
</div> </div>
</div>
</div> </div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
{% compress js %} {% compress js %}
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script> <script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/login.js' %}"></script> <script src="{% static 'js/login.js' %}"></script>
{% endcompress %} {% endcompress %}
{% endblock %} {% endblock %}

View File

@ -42,6 +42,7 @@
<link rel="stylesheet" href="{% static 'css/checkbox.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/checkbox.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/billing.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/billing.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/login.css' %}" type="text/css">
{% endcompress %} {% endcompress %}
</head> </head>
<body class="page-{{ page }}"> <body class="page-{{ page }}">
@ -109,14 +110,6 @@
</ul> </ul>
{% if request.user.is_authenticated %}
{% else %}
<ul class="nav navbar-nav navbar-right">
<li><a href="{% url 'hc-login' %}">Log In</a></li>
</ul>
{% endif %}
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li class="dropdown"> <li class="dropdown">
@ -148,6 +141,10 @@
</ul> </ul>
</li> </li>
</ul> </ul>
{% elif page != "login" %}
<ul class="nav navbar-nav navbar-right">
<li><a href="{% url 'hc-login' %}">Log In</a></li>
</ul>
{% endif %} {% endif %}
</div> </div>