Skip to content

Commit 0a046b1

Browse files
committed
Allow guest access to the platform for 30 minutes when invitation code is used
1 parent fc83dd8 commit 0a046b1

File tree

5 files changed

+112
-10
lines changed

5 files changed

+112
-10
lines changed

app/controllers/better_together/application_controller.rb

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ class ApplicationController < ActionController::Base # rubocop:todo Metrics/Clas
1010
before_action :check_platform_setup
1111
before_action :set_locale
1212
before_action :store_user_location!, if: :storable_location?
13+
14+
before_action :set_platform_invitation
1315
before_action :check_platform_privacy
1416
# The callback which stores the current location must be added before you authenticate the user
1517
# as `authenticate_user!` (or whatever your resource is) will halt the filter chain and redirect
@@ -24,7 +26,7 @@ class ApplicationController < ActionController::Base # rubocop:todo Metrics/Clas
2426
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
2527
rescue_from StandardError, with: :handle_error
2628

27-
helper_method :default_url_options
29+
helper_method :default_url_options, :valid_platform_invitation_token_present?
2830

2931
def self.default_url_options
3032
super.merge(locale: I18n.locale)
@@ -56,15 +58,58 @@ def check_platform_setup
5658
redirect_to setup_wizard_path
5759
end
5860

61+
def set_platform_invitation
62+
# Only proceed if there's an invitation token in the URL or already in the session.
63+
return unless params[:invitation_code].present? || session[:platform_invitation_token].present?
64+
65+
# Check if the session token has expired.
66+
if session[:platform_invitation_expires_at].present? && Time.current > session[:platform_invitation_expires_at]
67+
session.delete(:platform_invitation_token)
68+
session.delete(:platform_invitation_expires_at)
69+
return
70+
end
71+
72+
if params[:invitation_code].present?
73+
# On first visit with the invitation code, update the session with the token and a new expiry.
74+
token = params[:invitation_code]
75+
session[:platform_invitation_token] = token
76+
session[:platform_invitation_expires_at] ||= Time.current + platform_invitation_expiry_time
77+
else
78+
# If no params, simply use the token stored in the session.
79+
token = session[:platform_invitation_token]
80+
end
81+
82+
return unless token.present?
83+
84+
@platform_invitation = ::BetterTogether::PlatformInvitation.pending.find_by(token: token)
85+
86+
unless @platform_invitation
87+
session.delete(:platform_invitation_token)
88+
session.delete(:platform_invitation_expires_at)
89+
end
90+
end
91+
5992
def check_platform_privacy
6093
return if helpers.host_platform.privacy_public?
6194
return if current_user
6295
return unless BetterTogether.user_class.any?
96+
return if valid_platform_invitation_token_present?
6397

6498
flash[:error] = I18n.t('globals.platform_not_public')
6599
redirect_to new_user_session_path(locale: I18n.locale)
66100
end
67101

102+
def valid_platform_invitation_token_present?
103+
token = session[:platform_invitation_token]
104+
return false unless token.present?
105+
106+
return false if session[:platform_invitation_expires_at].present? && Time.current > session[:platform_invitation_expires_at]
107+
108+
::BetterTogether::PlatformInvitation.pending.exists?(token: token)
109+
end
110+
111+
private
112+
68113
def handle404
69114
render_404
70115
end
@@ -171,8 +216,18 @@ def after_sign_in_path_for(resource)
171216
end
172217
end
173218

219+
def after_inactive_sign_up_path_for(resource)
220+
new_user_session_path if helpers.host_platform&.private?
221+
super
222+
end
223+
174224
def after_sign_out_path_for(_resource_or_scope)
175225
BetterTogether.base_path_with_locale
176226
end
227+
228+
# Configurable expiration time (e.g., 30 minutes)
229+
def platform_invitation_expiry_time
230+
30.minutes
231+
end
177232
end
178233
end

app/controllers/better_together/users/registrations_controller.rb

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ class RegistrationsController < ::Devise::RegistrationsController
77
include DeviseLocales
88
skip_before_action :check_platform_privacy
99

10-
before_action :set_platform_invitation, only: %i[new create]
11-
1210
def new
1311
super do |user|
1412
user.email = @platform_invitation.invitee_email if @platform_invitation && user.email.empty?
@@ -51,12 +49,6 @@ def create # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
5149

5250
protected
5351

54-
def set_platform_invitation
55-
return unless params[:invitation_code].present?
56-
57-
@platform_invitation = ::BetterTogether::PlatformInvitation.pending.find_by(token: params[:invitation_code])
58-
end
59-
6052
def person_params
6153
params.require(:user).require(:person_attributes).permit(%i[identifier name description])
6254
end

app/helpers/better_together/platforms_helper.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22

33
module BetterTogether
44
module PlatformsHelper # rubocop:todo Style/Documentation
5+
def invitation_token_expires_at
6+
session[:platform_invitation_expires_at].to_datetime.to_i
7+
end
58
end
69
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// app/javascript/controllers/invitation_timer_controller.js
2+
3+
import { Controller } from '@hotwired/stimulus'
4+
5+
export default class extends Controller {
6+
static values = {
7+
expiresAt: Number // Timestamp in seconds (epoch time)
8+
}
9+
10+
connect() {
11+
this.updateTimer()
12+
this.interval = setInterval(() => this.updateTimer(), 1000)
13+
}
14+
15+
disconnect() {
16+
clearInterval(this.interval)
17+
}
18+
19+
updateTimer() {
20+
const now = Math.floor(Date.now() / 1000)
21+
const remainingSeconds = this.expiresAtValue - now
22+
console.log("Timer:", { expiresAt: this.expiresAtValue, now, remainingSeconds })
23+
24+
if (remainingSeconds <= 0) {
25+
this.element.textContent = 'Expired'
26+
this.element.classList.remove('bg-info')
27+
this.element.classList.add('bg-danger')
28+
clearInterval(this.interval)
29+
return
30+
}
31+
32+
const minutes = Math.floor(remainingSeconds / 60)
33+
this.element.textContent = `${minutes} min`
34+
}
35+
}

app/views/layouts/better_together/_header.html.erb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,24 @@
5858
<% else %>
5959
<!-- If user is not signed in, show 'Sign In' -->
6060
<li class="nav-item">
61-
<%= link_to t('navbar.sign_in'), new_user_session_path, class: "nav-link" %>
61+
<% if valid_platform_invitation_token_present? %>
62+
<%= link_to new_user_registration_path(invitation_code: session[:platform_invitation_token]), class: "nav-link d-flex align-items-center gap-2" do %>
63+
<i class="fas fa-envelope-open-text"></i>
64+
<%= t('navbar.accept_invitation') %>
65+
<% expires_at_unix = invitation_token_expires_at %>
66+
67+
<span class="badge bg-info ms-2"
68+
data-controller="better_together--invitation-timer"
69+
data-better_together--invitation-timer-expires-at-value="<%= expires_at_unix %>">
70+
<%= t('invitations.calculating') %>
71+
</span>
72+
<% end %>
73+
<% else %>
74+
<%= link_to new_user_session_path, class: "nav-link d-flex align-items-center gap-2" do %>
75+
<i class="fas fa-sign-in-alt"></i>
76+
<%= t('navbar.sign_in') %>
77+
<% end %>
78+
<% end %>
6279
</li>
6380
<% end %>
6481
</ul>

0 commit comments

Comments
 (0)