Compare commits

..

8 commits

Author SHA1 Message Date
38a37137d0 Redirect to previous page after login 2026-01-09 17:20:06 -06:00
d55dc53ade Set time zone on login
Should be more reliable.
2026-01-09 17:20:06 -06:00
d2e07630d5 Display vote title in page title and when sharing link 2026-01-09 17:20:06 -06:00
45c8e9b6b4 Display vote name even when unauthenticated
For example, when sharing the link in messaging apps.
2026-01-09 17:20:01 -06:00
33bc3e1446 Add 'Vote not found' 2026-01-09 16:05:03 -06:00
ec173f450d Add participant count 2026-01-09 15:53:57 -06:00
876d46bc67 Sort list of users alphabetically 2026-01-09 15:46:32 -06:00
da30b02405 Add feedback when voting 2026-01-09 15:35:33 -06:00
10 changed files with 88 additions and 40 deletions

View file

@ -133,17 +133,18 @@ post '/login' do
end
session.clear
session[:user_id] = user.id
session[:timezone] = params[:timezone]
if params[:r]
redirect params[:r]
else
redirect '/'
end
else
@error = _("Incorrect email or password.")
erb :login
end
end
post '/timezone' do
session[:timezone] = JSON.parse(request.body.read)['timezone']
end
get '/reset' do
erb :reset
end
@ -317,8 +318,13 @@ post '/votes/new' do
end
get '/votes/:id' do
require_login
find_vote
if not @vote
erb :votes_show_unknown
else
if not current_user
erb :votes_show_unauthenticated
else
case @vote.state
when 'draft'
if @vote.users.exists?(current_user.id)
@ -331,6 +337,8 @@ get '/votes/:id' do
when 'closed'
erb :votes_show_closed
end
end
end
end
get '/votes/:id/edit' do
@ -510,15 +518,15 @@ post '/votes/:id/ratings' do
end
if not @errors.empty?
@params = params
erb :votes_show_open
else
@vote.candidates.each do |candidate|
rating = Rating.find_or_initialize_by(vote: @vote, user: current_user, candidate: candidate)
rating.value = params[candidate.id.to_s]
rating.save
end
redirect '/votes/' + @vote.secure_id
@voted = true
end
erb :votes_show_open
end
get '/votes/:id/organizers' do
@ -594,10 +602,11 @@ helpers do
end
def require_login
redirect '/login' unless current_user
redirect "/login?r=#{request.path}" unless current_user
end
def require_admin
require_login
redirect '/' unless is_admin
end
@ -634,12 +643,7 @@ helpers do
end
def format_date(timestamp)
if session[:timezone]
"#{TZInfo::Timezone.get(session[:timezone]).to_local(timestamp).strftime('%F')}"
else
# Otherwise, format_date fails on first page after login. Not sure why...
"#{timestamp.strftime('%F')}"
end
end
def format_date_and_time(timestamp)

View file

@ -3,7 +3,7 @@
<h2 class="mb-4"><%= _("Users") %></h2>
<ul class="mb-5">
<% @users.reverse.each do |user| %>
<% @users.sort_by { |user| user.email }.each do |user| %>
<li>
<a href="/admin/users/<%= user.id %>"><%= user.email %></a>
</li>

View file

@ -40,7 +40,7 @@
<% unless @vote.ratings.blank? %>
<ul>
<% @vote.ratings.collect { |rating| rating.user }.uniq.each do |user| %>
<% @vote.ratings.collect { |rating| rating.user }.uniq.sort_by { |user| user.email }.each do |user| %>
<li>
<%= user.email %>
<a href="/admin/votes/<%= @vote.id %>/ratings/<%= user.id %>/delete"><%= _("Delete") %></a>

View file

@ -3,22 +3,18 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<% if @vote %>
<title>Vedia · <%= @vote.title %></title>
<meta property="og:title" content="<%= @vote.title %>"/>
<% else %>
<title>Vedia</title>
<% end %>
<script src="/chartkick.js"></script>
<script src="/Chart.bundle.js"></script>
<link rel="stylesheet" href="/bootstrap.css">
<link rel="stylesheet" href="/bootstrap-icons.css">
<link rel="stylesheet" href="/style.css">
</head>
<script>
fetch('/timezone', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone })
});
</script>
<body>
<div class="container py-4">
@ -46,6 +42,8 @@
<div class="d-flex flex-wrap justify-content-end pe-3 py-3 mb-4">
<% if current_user %>
<%= current_user.email %>
<% else %>
&nbsp;
<% end %>
</div>

View file

@ -4,7 +4,7 @@
<p class="alert alert-warning mb-4"><%= @error %></p>
<% end %>
<form action="/login" method="post" class="mb-5">
<form action="/login" name="login" method="post" class="mb-5">
<div class="mb-3">
<label for="email" class="form-label"><%= _("Email") %></label>
<input type="text" name="email" class="form-control">
@ -14,6 +14,12 @@
<input type="password" name="password" class="form-control">
</div>
<button type="submit" class="btn btn-primary"><%= _("Login") %></button>
<input type="hidden" name="r" value="<%= @params[:r] %>">
<input type="hidden" name="timezone" value="UTC">
</form>
<p><a href="/signup"><%= _("Create account") %></a></p>
<p><a href="/reset"><%= _("Reset password") %></a></p>
<script>
document.login.timezone.value = Intl.DateTimeFormat().resolvedOptions().timeZone;
</script>

View file

@ -14,6 +14,11 @@
<% when 'closed' %>
<SPAN class="badge bg-dark"><%= _("Closed") %></span>
<% end %>
<% if vote.ratings.find { |rating| rating.user == current_user } %>
<span class="badge bg-success"><%= _("Voted") %></span>
<% elsif vote.state == 'open' %>
<span class="badge bg-danger"><%= _("Not voted") %></span>
<% end %>
<% if vote.users.exists?(current_user.id) %>
<span class="badge bg-warning text-bg-warning"><%= _("Organizer") %></span>
<% end %>

View file

@ -97,7 +97,7 @@ end
<% end %>
</tr>
</thead>
<% @vote.ratings.collect { |rating| rating.user }.uniq.each do |user| %>
<% @vote.ratings.collect { |rating| rating.user }.uniq.sort_by { |user| user.email }.each do |user| %>
<tr>
<td><%= user.email %></td>
<% @vote.candidates.each do |candidate| %>

View file

@ -3,6 +3,11 @@
<ul class="list-unstyled">
<li>
<span class="badge bg-primary"><%= _("Open") %></span>
<% if @vote.ratings.find { |rating| rating.user == current_user } %>
<span class="badge bg-success"><%= _("Voted") %></span>
<% else %>
<span class="badge bg-danger"><%= _("Not voted") %></span>
<% end %>
</li>
<% if @vote.expire_on %>
<li><%= _("Closes on %{date}") % { date: format_date_and_time(@vote.expire_on) } %></li>
@ -26,6 +31,10 @@
<% end %>
<% end %>
<% if @voted %>
<p class="alert alert-success mb-4"><%= _("Gràcies per votar!") %></p>
<% end %>
<form action="/votes/<%= @vote.secure_id %>/ratings" method="post" class="mb-5">
<% @vote.candidates.each do |candidate| %>
@ -54,10 +63,10 @@
<button type="submit" class="btn btn-primary"><%= _("Vote") %></button>
</form>
<h2 class="mb-4"><%= _("Participants") %></h2>
<h2 class="mb-4"><%= _("Participants") + " (#{@vote.ratings.collect { |rating| rating.user }.uniq.count})" %></h2>
<ul class="mb-5">
<% @vote.ratings.collect { |rating| rating.user }.uniq.each do |user| %>
<% @vote.ratings.collect { |rating| rating.user }.uniq.sort_by { |user| user.email }.each do |user| %>
<li><%= user.email %></li>
<% end %>
</ul>

View file

@ -0,0 +1,23 @@
<div class="mb-5">
<h1><%= @vote.title %></h1>
</div>
<p class="alert alert-warning mb-4"><%= _("You need to log in to see the details of this vote.") %></p>
<form action="/login" method="post" class="mb-5">
<div class="mb-3">
<label for="email" class="form-label"><%= _("Email") %></label>
<input type="text" name="email" class="form-control">
</div>
<div class="mb-3">
<label for="password" class="form-label"><%= _("Password") %></label>
<input type="password" name="password" class="form-control">
</div>
<button type="submit" class="btn btn-primary"><%= _("Login") %></button>
<input type="hidden" name="r" value="votes/<%= @vote.secure_id %>">
<input type="hidden" name="timezone" value="UTC">
</form>
<script>
document.login.timezone.value = Intl.DateTimeFormat().resolvedOptions().timeZone;
</script>

View file

@ -0,0 +1,3 @@
<div class="mb-5">
<h1><%= _("Vote not found...") %></h1>
</div>