Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
//= require utility
//= require custom
//= require map
//= require controllers
//= require_tree ./controllers

$(function() {
$(function () {
Utility.disable();
});

$.fn.random = function() {
$.fn.random = function () {
return this.eq(Math.floor(Math.random() * this.length));
};
10 changes: 10 additions & 0 deletions app/assets/javascripts/controllers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Minimal controller registry — mirrors Stimulus conventions without the dependency.
// Files in controllers/ assign to Controllers['name'] = class { connect(el) {} }
const Controllers = {};

document.addEventListener('DOMContentLoaded', function() {
Comment thread
JoschkaSchulz marked this conversation as resolved.
document.querySelectorAll('[data-controller]').forEach(function(el) {
const name = el.dataset.controller;
if (Controllers[name]) new Controllers[name](el).connect();
});
});
36 changes: 36 additions & 0 deletions app/assets/javascripts/controllers/nav_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Reads the _on_ruby_user data cookie (set by UserHandling#sign_in) and swaps the
// anonymous login button for the profile dropdown without a server round-trip.
Controllers['nav'] = class {
constructor(el) { this.el = el; }

connect() {
const user = this.readUserCookie();
if (user) this.renderUserNav(user);
}

readUserCookie() {
const match = document.cookie.match(/(?:^|;\s*)_on_ruby_user=([^;]+)/);
if (!match) return null;
try { return JSON.parse(decodeURIComponent(match[1])); }
catch (e) { return null; }
}

renderUserNav(user) {
const template = this.el.querySelector('template');
if (!template) return;

const frag = template.content.cloneNode(true);
const avatar = frag.querySelector('[data-avatar]');
avatar.src = user.image_path;
avatar.alt = user.name;
frag.querySelector('[data-profile]').href = user.profile_path;
frag.querySelector('[data-edit]').href = user.edit_path;
frag.querySelector('[data-logout]').href = user.logout_path;

const show = (sel) => { const node = frag.querySelector(sel); if (node) node.hidden = false; };
Comment thread
JoschkaSchulz marked this conversation as resolved.
if (user.is_admin) show('[data-admin]');
if (user.is_super_admin) show('[data-super-admin]');

this.el.querySelector('[data-login]').replaceWith(frag);
}
};
4 changes: 4 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ body {
display: none;
}
#nav {
.navbar-nav {
align-items: center;
}

img.label {
max-height: 30px;
}
Expand Down
28 changes: 28 additions & 0 deletions app/controllers/concerns/user_handling.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,39 @@ def sign_in(user)
@current_user = user
session[:user_id] = user.id
cookies.permanent.signed[:remember_me] = [user.id, user.salt]
user_cookie(user)
end

def sign_out
session.clear
cookies.permanent.signed[:remember_me] = ['', '']
clear_user_cookie
end

def user_cookie(user)
data = {
slug: user.to_param,
name: user.name.to_s,
image_path: helpers.cache_image_path(user),
profile_path: user_path(user),
edit_path: edit_user_path(user),
logout_path: destroy_session_path,
hide_jobs: user.hide_jobs?,
missing_name: user.missing_name?,
is_admin: (true if user.admin?),
is_super_admin: (true if user.super_admin?),
}.compact

cookies.permanent['_on_ruby_user'] = {
value: data.to_json,
domain: request.domain,
httponly: false,
same_site: :lax,
}
end

def clear_user_cookie
cookies.delete('_on_ruby_user', domain: request.domain)
end

def find_by_session_or_cookies
Expand Down
1 change: 1 addition & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def calendar

def update
if current_user.update user_params
user_cookie(current_user)
redirect_back(notice: t('user.saved_successful'), fallback_location: root_url)
else
redirect_back(alert: current_user.errors.full_messages.join(' '), fallback_location: root_url)
Expand Down
52 changes: 27 additions & 25 deletions app/views/application/_nav.slim
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,39 @@ nav.navbar.sticky-top.navbar-expand-lg.navbar-light.bg-light#nav
= fa_icon(fa_icon_map[section], class: 'fa-fw', text: t("main.#{section}"))

ul.navbar-nav.ms-auto
li.nav-item.dropdown.pe-4
- if signed_in?
li.nav-item.dropdown.pe-4(data-controller="nav")
/ Anonymous default — nav_controller.js replaces with profile dropdown when cookie present
div(data-login="")
a.btn.btn-primary.dropdown-toggle(href="#" id="loginDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false")
= t('login.login')
.dropdown-menu.dropdown-menu-end(aria-labelledby="loginDropdown")
- login_providers.each do |provider|
= button_to(label_auth_url(provider), class: 'dropdown-item') do
= fa_icon(icon_for_provider(provider), class: 'fa-fw', text: t("login.#{provider}_login"))

/ Template for logged-in state — inert until cloned by nav_controller.js
template
.dropdown
a.btn.btn-light.dropdown-toggle(href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false")
= user_image(current_user)
img.user-image.user-image-small(src="" alt="" data-avatar="")
= t('login.profile')
ul.dropdown-menu.dropdown-menu-end(aria-labelledby="loginDropdown")
li= link_to(user_path(current_user), class: 'dropdown-item') do
= fa_icon('eye', class: 'fa-fw', text: t("login.show_profile"))

li= link_to(edit_user_path(current_user), class: 'dropdown-item') do
= fa_icon('edit', class: 'fa-fw', text: t("login.edit_profile"))

li= link_to(destroy_session_path(current_user), class: 'dropdown-item') do
= fa_icon('times', class: 'fa-fw', text: t("login.logout"))

- if current_user.admin?
li= link_to('/admin', class: 'dropdown-item') do
ul.dropdown-menu.dropdown-menu-end
li
a.dropdown-item(href="" data-profile="")
= fa_icon('eye', class: 'fa-fw', text: t("login.show_profile"))
li
a.dropdown-item(href="" data-edit="")
= fa_icon('edit', class: 'fa-fw', text: t("login.edit_profile"))
li
a.dropdown-item(href="" data-logout="")
= fa_icon('times', class: 'fa-fw', text: t("login.logout"))
li(hidden=true data-admin="")
a.dropdown-item(href="/admin")
= fa_icon('lock', class: 'fa-fw', text: 'Community-Admin')
- if current_user.super_admin?
li= link_to('/super_admin', class: 'dropdown-item') do
li(hidden=true data-super-admin="")
a.dropdown-item(href="/super_admin")
= fa_icon('lock', class: 'fa-fw', text: 'Super-Admin')

- else
a(class="btn btn-primary dropdown-toggle" href="#" id="loginDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false")
= t('login.login')
.dropdown-menu.dropdown-menu-end(aria-labelledby="loginDropdown")
- login_providers.each do |provider|
= button_to(label_auth_url(provider), class: 'dropdown-item') do
= fa_icon(icon_for_provider(provider), class: 'fa-fw', text: t("login.#{provider}_login"))


li.nav-item.dropdown.pe-4
a(class="nav-link btn btn-light dropdown-toggle" href="#" id="localeDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false")
Expand Down
Loading