mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 06:21:23 +00:00
Add onboarding state selector for self-hosted signup (#251)
* Add onboarding modes to self-hosted signup * Style form consistently * Configure ONBOARDING_STATE via ENV
This commit is contained in:
@@ -10,6 +10,9 @@
|
||||
# Enables self hosting features (should be set to true unless you know what you're doing)
|
||||
SELF_HOSTED=true
|
||||
|
||||
# Controls onboarding flow (valid: open, closed, invite_only)
|
||||
ONBOARDING_STATE=open
|
||||
|
||||
# Secret key used to encrypt credentials (https://api.rubyonrails.org/v7.1.3.2/classes/Rails/Application.html#method-i-secret_key_base)
|
||||
# Has to be a random string, generated eg. by running `openssl rand -hex 64`
|
||||
SECRET_KEY_BASE=secret-value
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# To enable / disable self-hosting features.
|
||||
SELF_HOSTED = true
|
||||
|
||||
# Controls onboarding flow (valid: open, closed, invite_only)
|
||||
ONBOARDING_STATE = open
|
||||
|
||||
# Enable Twelve market data (careful, this will use your API credits)
|
||||
TWELVE_DATA_API_KEY =
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
SELF_HOSTED=false
|
||||
|
||||
# Controls onboarding flow (valid: open, closed, invite_only)
|
||||
ONBOARDING_STATE=open
|
||||
|
||||
# OpenID Connect for tests
|
||||
OIDC_ISSUER=
|
||||
OIDC_CLIENT_ID=
|
||||
@@ -21,4 +24,4 @@ OIDC_REDIRECT_URI=http://localhost:3000/auth/openid_connect/callback
|
||||
COVERAGE=false
|
||||
|
||||
# Set to true to run test suite serially
|
||||
DISABLE_PARALLELIZATION=false
|
||||
DISABLE_PARALLELIZATION=false
|
||||
|
||||
@@ -8,7 +8,11 @@ module Invitable
|
||||
private
|
||||
def invite_code_required?
|
||||
return false if @invitation.present?
|
||||
self_hosted? ? Setting.require_invite_for_signup : ENV["REQUIRE_INVITE_CODE"] == "true"
|
||||
if self_hosted?
|
||||
Setting.onboarding_state == "invite_only"
|
||||
else
|
||||
ENV["REQUIRE_INVITE_CODE"] == "true"
|
||||
end
|
||||
end
|
||||
|
||||
def self_hosted?
|
||||
|
||||
@@ -3,6 +3,7 @@ class RegistrationsController < ApplicationController
|
||||
|
||||
layout "auth"
|
||||
|
||||
before_action :ensure_signup_open, if: :self_hosted?
|
||||
before_action :set_user, only: :create
|
||||
before_action :set_invitation
|
||||
before_action :claim_invite_code, only: :create, if: :invite_code_required?
|
||||
@@ -79,4 +80,10 @@ class RegistrationsController < ApplicationController
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_signup_open
|
||||
return unless Setting.onboarding_state == "closed"
|
||||
|
||||
redirect_to new_session_path, alert: t("registrations.closed")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,8 +15,9 @@ class Settings::HostingsController < ApplicationController
|
||||
end
|
||||
|
||||
def update
|
||||
if hosting_params.key?(:require_invite_for_signup)
|
||||
Setting.require_invite_for_signup = hosting_params[:require_invite_for_signup]
|
||||
if hosting_params.key?(:onboarding_state)
|
||||
onboarding_state = hosting_params[:onboarding_state].to_s
|
||||
Setting.onboarding_state = onboarding_state
|
||||
end
|
||||
|
||||
if hosting_params.key?(:require_email_confirmation)
|
||||
@@ -68,7 +69,7 @@ class Settings::HostingsController < ApplicationController
|
||||
|
||||
private
|
||||
def hosting_params
|
||||
params.require(:setting).permit(:require_invite_for_signup, :require_email_confirmation, :brand_fetch_client_id, :twelve_data_api_key, :openai_access_token, :openai_uri_base, :openai_model)
|
||||
params.require(:setting).permit(:onboarding_state, :require_email_confirmation, :brand_fetch_client_id, :twelve_data_api_key, :openai_access_token, :openai_uri_base, :openai_model)
|
||||
end
|
||||
|
||||
def ensure_admin
|
||||
|
||||
@@ -10,9 +10,40 @@ class Setting < RailsSettings::Base
|
||||
field :openai_model, type: :string, default: ENV["OPENAI_MODEL"]
|
||||
field :brand_fetch_client_id, type: :string, default: ENV["BRAND_FETCH_CLIENT_ID"]
|
||||
|
||||
ONBOARDING_STATES = %w[open closed invite_only].freeze
|
||||
DEFAULT_ONBOARDING_STATE = begin
|
||||
env_value = ENV["ONBOARDING_STATE"].to_s.presence || "open"
|
||||
ONBOARDING_STATES.include?(env_value) ? env_value : "open"
|
||||
end
|
||||
|
||||
field :onboarding_state, type: :string, default: DEFAULT_ONBOARDING_STATE
|
||||
field :require_invite_for_signup, type: :boolean, default: false
|
||||
field :require_email_confirmation, type: :boolean, default: ENV.fetch("REQUIRE_EMAIL_CONFIRMATION", "true") == "true"
|
||||
|
||||
def self.validate_onboarding_state!(state)
|
||||
return if ONBOARDING_STATES.include?(state)
|
||||
|
||||
raise ValidationError, I18n.t("settings.hostings.update.invalid_onboarding_state")
|
||||
end
|
||||
|
||||
class << self
|
||||
alias_method :raw_onboarding_state, :onboarding_state
|
||||
alias_method :raw_onboarding_state=, :onboarding_state=
|
||||
|
||||
def onboarding_state
|
||||
value = raw_onboarding_state
|
||||
return "invite_only" if value.blank? && require_invite_for_signup
|
||||
|
||||
value.presence || DEFAULT_ONBOARDING_STATE
|
||||
end
|
||||
|
||||
def onboarding_state=(state)
|
||||
validate_onboarding_state!(state)
|
||||
self.require_invite_for_signup = state == "invite_only"
|
||||
self.raw_onboarding_state = state
|
||||
end
|
||||
end
|
||||
|
||||
# Validates OpenAI configuration requires model when custom URI base is set
|
||||
def self.validate_openai_config!(uri_base: nil, model: nil)
|
||||
# Use provided values or current settings
|
||||
|
||||
@@ -9,7 +9,19 @@
|
||||
url: settings_hosting_path,
|
||||
method: :patch,
|
||||
data: { controller: "auto-submit-form", auto_submit_form_trigger_event_value: "change" } do |form| %>
|
||||
<%= form.toggle :require_invite_for_signup, { data: { auto_submit_form_target: "auto" } } %>
|
||||
<div class="form-field w-fit">
|
||||
<%= form.select :onboarding_state,
|
||||
options_for_select(
|
||||
[
|
||||
[ t(".states.open"), "open" ],
|
||||
[ t(".states.closed"), "closed" ],
|
||||
[ t(".states.invite_only"), "invite_only" ]
|
||||
],
|
||||
Setting.onboarding_state
|
||||
),
|
||||
{ label: false },
|
||||
{ data: { auto_submit_form_target: "auto" } } %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +39,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if Setting.require_invite_for_signup %>
|
||||
<% if Setting.onboarding_state == "invite_only" %>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<span class="text-primary text-base font-medium"><%= t(".generated_tokens") %></span>
|
||||
|
||||
@@ -8,6 +8,7 @@ en:
|
||||
user:
|
||||
create: Continue
|
||||
registrations:
|
||||
closed: Signups are currently closed.
|
||||
create:
|
||||
failure: There was a problem signing up.
|
||||
invalid_invite_code: Invalid invite code, please try again.
|
||||
|
||||
@@ -8,6 +8,7 @@ nb:
|
||||
user:
|
||||
create: Fortsett
|
||||
registrations:
|
||||
closed: Registrering er midlertidig stengt.
|
||||
create:
|
||||
failure: Det oppsto et problem med registreringen.
|
||||
invalid_invite_code: Ugyldig invitasjonskode, vennligst prøv igjen.
|
||||
@@ -22,4 +23,4 @@ nb:
|
||||
welcome_body: For å komme i gang må du registrere deg for en ny konto. Du vil
|
||||
da kunne konfigurere flere innstillinger i appen.
|
||||
welcome_title: Velkommen til Self Hosted %{product_name}!
|
||||
password_placeholder: Angi passordet ditt
|
||||
password_placeholder: Angi passordet ditt
|
||||
|
||||
@@ -8,6 +8,7 @@ tr:
|
||||
user:
|
||||
create: Devam Et
|
||||
registrations:
|
||||
closed: Kayıtlar şu anda kapalı.
|
||||
create:
|
||||
failure: Kayıt olurken bir sorun oluştu.
|
||||
invalid_invite_code: Geçersiz davet kodu, lütfen tekrar deneyin.
|
||||
@@ -21,4 +22,4 @@ tr:
|
||||
title: Hesabınızı oluşturun
|
||||
welcome_body: Başlamak için yeni bir hesap oluşturmalısınız. Daha sonra uygulama içinde ek ayarları yapılandırabileceksiniz.
|
||||
welcome_title: Self Hosted Maybe'ye Hoş Geldiniz!
|
||||
password_placeholder: Şifrenizi girin
|
||||
password_placeholder: Şifrenizi girin
|
||||
|
||||
@@ -3,17 +3,20 @@ en:
|
||||
settings:
|
||||
hostings:
|
||||
invite_code_settings:
|
||||
description: Every new user that joins your instance of %{product} can only do
|
||||
so via an invite code
|
||||
description: Control how new people sign up for your instance of %{product}.
|
||||
email_confirmation_description: When enabled, users must confirm their email
|
||||
address when changing it.
|
||||
email_confirmation_title: Require email confirmation
|
||||
generate_tokens: Generate new code
|
||||
generated_tokens: Generated codes
|
||||
title: Require invite code for signup
|
||||
title: Onboarding
|
||||
states:
|
||||
open: Open
|
||||
closed: Closed
|
||||
invite_only: Invite-only
|
||||
show:
|
||||
general: External Services
|
||||
invites: Invite Codes
|
||||
invites: Onboarding
|
||||
title: Self-Hosting
|
||||
danger_zone: Danger Zone
|
||||
clear_cache: Clear data cache
|
||||
@@ -47,6 +50,7 @@ en:
|
||||
update:
|
||||
failure: Invalid setting value
|
||||
success: Settings updated
|
||||
invalid_onboarding_state: Invalid onboarding state
|
||||
clear_cache:
|
||||
cache_cleared: Data cache has been cleared. This may take a few moments to complete.
|
||||
not_authorized: You are not authorized to perform this action
|
||||
|
||||
@@ -3,16 +3,20 @@ nb:
|
||||
settings:
|
||||
hostings:
|
||||
invite_code_settings:
|
||||
description: Hver ny bruker som blir med i din instans av %{product_name} kan bare bli med via en invitasjonskode
|
||||
description: Kontroller hvordan nye personer registrerer seg for din instans av %{product}.
|
||||
email_confirmation_description: Når aktivert, må brukere bekrefte e-postadressen
|
||||
sin når de endrer den.
|
||||
email_confirmation_title: Krev e-postbekreftelse
|
||||
generate_tokens: Generer ny kode
|
||||
generated_tokens: Genererte koder
|
||||
title: Krev invitasjonskode for registrering
|
||||
title: Onboarding
|
||||
states:
|
||||
open: Åpen
|
||||
closed: Stengt
|
||||
invite_only: Kun invitasjon
|
||||
show:
|
||||
general: Generelle innstillinger
|
||||
invites: Invitasjonskoder
|
||||
invites: Onboarding
|
||||
title: Selvhosting
|
||||
danger_zone: Fareområde
|
||||
clear_cache: Tøm cache
|
||||
@@ -23,6 +27,7 @@ nb:
|
||||
update:
|
||||
failure: Ugyldig innstillingsverdi
|
||||
success: Innstillinger oppdatert
|
||||
invalid_onboarding_state: Ugyldig onboarding-modus
|
||||
clear_cache:
|
||||
cache_cleared: Cachen er tømt. Dette kan ta noen øyeblikk å fullføre.
|
||||
not_authorized: Du er ikke autorisert til å utføre denne handlingen
|
||||
not_authorized: Du er ikke autorisert til å utføre denne handlingen
|
||||
|
||||
@@ -3,15 +3,19 @@ tr:
|
||||
settings:
|
||||
hostings:
|
||||
invite_code_settings:
|
||||
description: Maybe uygulamanıza katılan her yeni kullanıcı yalnızca bir davet kodu ile katılabilir
|
||||
description: Yeni kullanıcıların %{product} örneğinize nasıl kaydolacağını kontrol edin.
|
||||
email_confirmation_description: Etkinleştirildiğinde, kullanıcılar e-posta adreslerini değiştirirken e-posta onayı yapmak zorundadır.
|
||||
email_confirmation_title: E-posta onayı gerektir
|
||||
generate_tokens: Yeni kod oluştur
|
||||
generated_tokens: Oluşturulan kodlar
|
||||
title: Kayıt için davet kodu gerektir
|
||||
title: Onboarding
|
||||
states:
|
||||
open: Açık
|
||||
closed: Kapalı
|
||||
invite_only: Davet ile
|
||||
show:
|
||||
general: Genel Ayarlar
|
||||
invites: Davet Kodları
|
||||
invites: Onboarding
|
||||
title: Kendi Sunucunda Barındırma
|
||||
danger_zone: Tehlikeli Bölge
|
||||
clear_cache: Veri önbelleğini temizle
|
||||
@@ -29,6 +33,7 @@ tr:
|
||||
update:
|
||||
failure: Geçersiz ayar değeri
|
||||
success: Ayarlar güncellendi
|
||||
invalid_onboarding_state: Geçersiz onboarding durumu
|
||||
clear_cache:
|
||||
cache_cleared: Veri önbelleği temizlendi. Bu işlemin tamamlanması birkaç dakika sürebilir.
|
||||
not_authorized: Bu işlemi gerçekleştirmek için yetkiniz yok
|
||||
not_authorized: Bu işlemi gerçekleştirmek için yetkiniz yok
|
||||
|
||||
@@ -26,7 +26,7 @@ class Settings::HostingsControllerTest < ActionDispatch::IntegrationTest
|
||||
get settings_hosting_url
|
||||
assert_response :forbidden
|
||||
|
||||
patch settings_hosting_url, params: { setting: { require_invite_for_signup: true } }
|
||||
patch settings_hosting_url, params: { setting: { onboarding_state: "invite_only" } }
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
@@ -48,6 +48,20 @@ class Settings::HostingsControllerTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
test "can update onboarding state when self hosting is enabled" do
|
||||
with_self_hosting do
|
||||
patch settings_hosting_url, params: { setting: { onboarding_state: "invite_only" } }
|
||||
|
||||
assert_equal "invite_only", Setting.onboarding_state
|
||||
assert Setting.require_invite_for_signup
|
||||
|
||||
patch settings_hosting_url, params: { setting: { onboarding_state: "closed" } }
|
||||
|
||||
assert_equal "closed", Setting.onboarding_state
|
||||
refute Setting.require_invite_for_signup
|
||||
end
|
||||
end
|
||||
|
||||
test "can update openai access token when self hosting is enabled" do
|
||||
with_self_hosting do
|
||||
patch settings_hosting_url, params: { setting: { openai_access_token: "token" } }
|
||||
|
||||
@@ -44,7 +44,10 @@ class SettingsTest < ApplicationSystemTestCase
|
||||
click_link "Self-Hosting"
|
||||
assert_current_path settings_hosting_path
|
||||
assert_selector "h1", text: "Self-Hosting"
|
||||
check "setting[require_invite_for_signup]", allow_label_click: true
|
||||
find("select#setting_onboarding_state").select("Invite-only")
|
||||
within("select#setting_onboarding_state") do
|
||||
assert_selector "option[selected]", text: "Invite-only"
|
||||
end
|
||||
click_button "Generate new code"
|
||||
assert_selector 'span[data-clipboard-target="source"]', visible: true, count: 1 # invite code copy widget
|
||||
copy_button = find('button[data-action="clipboard#copy"]', match: :first) # Find the first copy button (adjust if needed)
|
||||
|
||||
Reference in New Issue
Block a user