mirror of
https://github.com/boostorg/website-v2.git
synced 2026-01-19 04:42:17 +00:00
Track user last login option and fixes (#1312)
This also hides the pop up notifications after 6 seconds For review, a review and merge of #1311 on which this is built would make this easier to review.
This commit is contained in:
@@ -12,9 +12,7 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="py-0 px-3 mb-3 md:py-6 md:px-0"
|
||||
x-data="{ loginMethod: localStorage.getItem('_x_boostlogin') }"
|
||||
>
|
||||
<div class="py-0 px-3 mb-3 md:py-6 md:px-0">
|
||||
|
||||
<div class="md:pt-11 md:mt-11 w-full bg-white dark:bg-charcoal mx-auto rounded py-6 px-3">
|
||||
<div class="md:w-full">
|
||||
@@ -57,13 +55,9 @@
|
||||
|
||||
<div class="w-full md:w-1/2">
|
||||
<h2 class="text-xl text-center items-center">
|
||||
Or Log In with Email <span x-cloak class="text-xs bg-emerald-400 text-slate rounded px-1 ml-2" x-show='loginMethod === "\"email\""'>Last Log In</span>
|
||||
Or Log In with Email <span x-cloak class="text-xs bg-emerald-400 text-slate rounded px-1 ml-2" x-show="providerMatchesLastLogin('email')">Last Log In</span>
|
||||
</h2>
|
||||
<form class="login"
|
||||
method="POST"
|
||||
action="{% url 'account_login' %}"
|
||||
x-data="{ boostlogin: $persist('email') }"
|
||||
>
|
||||
<form class="login" method="POST" action="{% url 'account_login' %}">
|
||||
<div class="mx-auto space-y-4 w-2/3" id="signup_form">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -94,7 +88,6 @@
|
||||
<div class="flex justify-between mb-4">
|
||||
<a class="text-sky-600 dark:text-sky-300 hover:text-orange dark:hover:text-orange" href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
|
||||
<button type="submit"
|
||||
x-on:click="boostlogin = 'email'"
|
||||
class="py-3 px-8 text-sm text-base font-medium text-white uppercase rounded-md border md:py-1 md:px-4 md:text-lg bg-orange hover:bg-orange/80 border-orange dark:bg-slate dark:hover:bg-charcoal dark:text-white hover:drop-shadow-md">{% trans "Log in" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -74,6 +74,9 @@
|
||||
|
||||
<script>
|
||||
var r = document.querySelector(':root');
|
||||
const messageVisibilitySeconds = 6;
|
||||
const opacityTransitionTime = 0.15; // matches the tailwind transition-opacity time
|
||||
const delay = ms => new Promise(res => setTimeout(res, ms));
|
||||
|
||||
function modalSize(h,w) {
|
||||
var rs = getComputedStyle(r);
|
||||
@@ -96,6 +99,39 @@
|
||||
modal.classList.remove('show-modal');
|
||||
}
|
||||
|
||||
const hideMessage = async () => {
|
||||
const message = document.getElementById('messages');
|
||||
message.classList.add('opacity-0');
|
||||
await delay(opacityTransitionTime * 1000);
|
||||
const messageButton = message.querySelector('button');
|
||||
if (messageButton) {
|
||||
messageButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
const providerMatchesLastLogin = async (provider) => {
|
||||
const lastLoginProvider = localStorage.getItem('boostLoginMethod') || null;
|
||||
return provider === lastLoginProvider;
|
||||
}
|
||||
|
||||
const trackLoginUpdateCheck = async () => {
|
||||
const footer = document.querySelector('footer');
|
||||
const indicator = footer.dataset.trackLoginMethod; // intentionally not cast to boolean
|
||||
const loginMethod = footer.dataset.loginMethod;
|
||||
if (indicator) {
|
||||
if (indicator === 'True') {
|
||||
localStorage.setItem('boostLoginMethod', loginMethod);
|
||||
} else {
|
||||
localStorage.removeItem('boostLoginMethod');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await trackLoginUpdateCheck();
|
||||
await delay(messageVisibilitySeconds * 1000);
|
||||
await hideMessage();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<footer class="footer">
|
||||
<footer class="footer"{% if user.is_authenticated %} data-track-login-method="{{ request.user.indicate_last_login_method }}" data-login-method="{{ request.session.boost_login_method }}"{% endif %}>
|
||||
<div class="footer-container">
|
||||
<div class="links">
|
||||
<a href="https://lists.boost.org/mailman/listinfo.cgi/boost-users">Contact</a>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="messages" class="w-full text-center" x-data="{show: true}">
|
||||
<div id="messages" class="w-full text-center transition-opacity" x-data="{show: true}">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div x-show="show" class="w-2/3 mx-auto text-left items-center text-slate dark:text-white rounded text-base px-3 py-2 {% if 'error' in message.tags %}bg-red-500{% else %}bg-green/70{% endif %} fade show">
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
<p>{% blocktrans with provider.name as provider %}You are about to log in using a third party account from {{ provider }}.{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" x-data="{ boostlogin: $persist('{{ provider.name }}') }">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
x-on:click="boostlogin = '{{ provider.name }}'"
|
||||
class="py-3 px-8 text-sm text-base font-medium text-white uppercase rounded-md border md:py-1 md:px-4 md:text-lg bg-orange hover:bg-orange/80 border-orange dark:bg-slate dark:hover:bg-charcoal dark:text-white hover:drop-shadow-md">{% trans "Continue" %}</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="py-3 px-8 text-sm text-base font-medium text-white uppercase rounded-md border md:py-1 md:px-4 md:text-lg bg-orange hover:bg-orange/80 border-orange dark:bg-slate dark:hover:bg-charcoal dark:text-white hover:drop-shadow-md"
|
||||
>{% trans "Continue" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
class="relative w-2/3 mx-auto block px-8 py-3 text-base font-medium rounded-md border border-orange !text-white hover:!text-white bg-orange hover:bg-orange/80 dark:bg-slate dark:hover:bg-charcoal hover:drop-shadow-md md:py-4 md:text-lg md:px-10 {{provider.id}}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}"
|
||||
>
|
||||
<span x-cloak class="absolute right-1 top-1 text-xs bg-white text-slate rounded p-1" x-show='loginMethod === "\"GitHub\""'>Last Log in</span>
|
||||
<span x-cloak class="absolute right-1 top-1 text-xs bg-emerald-400 text-slate rounded px-1 ml-2" x-show="providerMatchesLastLogin('github')">Last Log in</span>
|
||||
<i class="fab fa-github"></i>
|
||||
Use {{provider.name}}
|
||||
</a>
|
||||
@@ -27,7 +27,7 @@
|
||||
class="relative w-2/3 mx-auto block px-8 py-3 text-base font-medium rounded-md border border-orange !text-white hover:!text-white bg-orange hover:bg-orange/80 dark:bg-slate dark:hover:bg-charcoal hover:drop-shadow-md md:py-4 md:text-lg md:px-10 {{provider.id}}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}"
|
||||
>
|
||||
<span x-cloak class="absolute right-1 top-1 text-xs bg-white text-slate rounded p-1" x-show='loginMethod === "\"Google\""'>Last Log in</span>
|
||||
<span x-cloak class="absolute right-1 top-1 text-xs bg-emerald-400 text-slate rounded px-1 ml-2" x-show="providerMatchesLastLogin('google')">Last Log in</span>
|
||||
<i class="fab fa-google"></i>
|
||||
Use {{provider.name}}
|
||||
</a>
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not social_accounts %}
|
||||
<div class="rounded bg-white dark:bg-charcoal p-4">
|
||||
<h3>{% trans "Set Password" %}</h3>
|
||||
<form method="POST" action="." class="password_set space-y-3">
|
||||
@@ -103,13 +104,15 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="rounded bg-white dark:bg-charcoal p-4">
|
||||
<h3>{% trans "Account Connections" %}</h3>
|
||||
<div class="mt-4">
|
||||
<a href="{% url 'socialaccount_connections' %}"><button class="py-2 px-3 text-sm text-white rounded bg-orange">{% trans 'Manage Account Connections' %}</button></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
1
users/constants.py
Normal file
1
users/constants.py
Normal file
@@ -0,0 +1 @@
|
||||
LOGIN_METHOD_SESSION_FIELD_NAME = "boost_login_method"
|
||||
@@ -79,7 +79,7 @@ class PreferencesForm(forms.ModelForm):
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["email", "first_name", "last_name"]
|
||||
fields = ["email", "first_name", "last_name", "indicate_last_login_method"]
|
||||
|
||||
|
||||
class CustomClearableFileInput(forms.ClearableFileInput):
|
||||
|
||||
21
users/migrations/0013_user_indicate_last_login_method.py
Normal file
21
users/migrations/0013_user_indicate_last_login_method.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.2.15 on 2024-10-02 18:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("users", "0012_user_can_update_image"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="indicate_last_login_method",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Indicate on the login page the last login method used.",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -242,6 +242,10 @@ class User(BaseUser):
|
||||
"a user's ability to update their own profile photo, uncheck this box."
|
||||
),
|
||||
)
|
||||
indicate_last_login_method = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Indicate on the login page the last login method used.",
|
||||
)
|
||||
|
||||
def save_image_from_github(self, avatar_url):
|
||||
response = requests.get(avatar_url)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
from allauth.account.signals import user_logged_in
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import post_save
|
||||
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
|
||||
from users.constants import LOGIN_METHOD_SESSION_FIELD_NAME
|
||||
|
||||
GITHUB = "github"
|
||||
|
||||
|
||||
@@ -26,3 +29,14 @@ def import_social_profile_data(sender, instance, created, **kwargs):
|
||||
|
||||
if avatar_url:
|
||||
instance.user.save_image_from_github(avatar_url)
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
def user_logged_in_handler(request, user, **kwargs):
|
||||
# We trigger this here as well as on the profile update in case there are two users
|
||||
# on one machine, we need to reflag for the cookie update
|
||||
try:
|
||||
method = request.session["account_authentication_methods"][0].get("provider")
|
||||
except (KeyError, IndexError):
|
||||
method = None
|
||||
request.session[LOGIN_METHOD_SESSION_FIELD_NAME] = method or "email"
|
||||
|
||||
@@ -15,6 +15,7 @@ def user(db):
|
||||
email="user@example.com",
|
||||
first_name="Regular",
|
||||
last_name="User",
|
||||
indicate_last_login_method=False,
|
||||
last_login=timezone.now(),
|
||||
image=None,
|
||||
)
|
||||
|
||||
@@ -146,11 +146,13 @@ def test_user_profile_form(user):
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"indicate_last_login_method",
|
||||
}
|
||||
assert form.initial == {
|
||||
"first_name": user.first_name,
|
||||
"last_name": user.last_name,
|
||||
"email": user.email,
|
||||
"indicate_last_login_method": user.indicate_last_login_method,
|
||||
}
|
||||
form = UserProfileForm(instance=user, data={"email": "test@example.com"})
|
||||
assert form.is_valid()
|
||||
|
||||
Reference in New Issue
Block a user