Skip to content

Commit 3898c44

Browse files
authored
Enhancement/platform setup wizard (#793)
2 parents 6e25092 + 708065f commit 3898c44

File tree

4 files changed

+217
-112
lines changed

4 files changed

+217
-112
lines changed

app/controllers/better_together/pages_controller.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ module BetterTogether
44
# Responds to requests for pages
55
class PagesController < FriendlyResourceController # rubocop:todo Metrics/ClassLength
66
before_action :set_page, only: %i[show edit update destroy]
7+
8+
skip_before_action :check_platform_setup, unless: -> { ::BetterTogether::Platform.where(host: true).any? }
9+
710
before_action only: %i[new edit], if: -> { Rails.env.development? } do
811
# Make sure that all BLock subclasses are loaded in dev to generate new block buttons
912
BetterTogether::Content::Block.load_all_subclasses
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Controller } from "stimulus"
2+
3+
export default class extends Controller {
4+
static targets = [ "select" ]
5+
6+
connect() {
7+
// Called when the controller is initialized and the element is in the DOM
8+
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
9+
10+
if (this.hasSelectTarget) {
11+
const options = this.selectTarget.options;
12+
for (let i = 0; i < options.length; i++) {
13+
if (options[i].value === userTimeZone) {
14+
this.selectTarget.selectedIndex = i;
15+
break;
16+
}
17+
}
18+
}
19+
}
20+
}

app/views/better_together/wizard_step_definitions/host_setup/admin_creation.html.erb

Lines changed: 120 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,141 @@
1-
<%= form_for @form, url: setup_wizard_step_create_admin_path(wizard_id: 'host_setup', wizard_step_definition_id: :admin_creation), method: :post, html: { class: 'form-group' } do |f| %>
2-
<div class="container my-5">
1+
<% min_password_length = Devise.password_length.min %>
32

4-
<% if @form.errors.any? %>
5-
<div class="alert alert-danger">
6-
<h4>Please correct the following errors:</h4>
7-
<ul>
8-
<% @form.errors.full_messages.each do |msg| %>
9-
<li><%= msg %></li>
10-
<% end %>
11-
</ul>
3+
<%= form_for @form, url: setup_wizard_step_create_admin_path(wizard_id: 'host_setup', wizard_step_definition_id: :admin_creation), method: :post, class: 'needs-validation form', id: 'admin-creation-form', data: { turbo: false, controller: 'better_together--form-validation', action: 'submit->better_together--form-validation#validateBeforeSubmit' } do |f| %>
4+
5+
<!-- Hero Section -->
6+
<div class="container-fluid p-0">
7+
<div class="hero-section" style="background: url(<%= asset_path('better_together/unsplash-community-1.jpeg') %>) no-repeat center center; background-size: cover; height: 50vh;">
8+
<div class="overlay" style="background: rgba(0, 0, 0, 0.3); height: 100%; width: 100%;">
9+
<div class="container h-100 d-flex align-items-center justify-content-center">
10+
<div class="text-center text-white">
11+
<h1 class="display-4 fw-bold">Create Your Platform Manager Account</h1>
12+
<p class="lead">
13+
This is the <strong>first account</strong> on your platform and gives you full control to manage settings, content, and members. It’s important to choose secure credentials and create a clear, welcoming profile.
14+
</p>
15+
</div>
16+
</div>
1217
</div>
13-
<% end %>
18+
</div>
19+
</div>
1420

21+
<!-- Form Section -->
22+
<div class="container my-5">
1523
<div class="row justify-content-center">
16-
<div class="col-md-6">
17-
<h2 class="mb-4">Create Admin Account</h2>
24+
<div class="col-md-8 col-lg-6">
25+
<!-- Progress Indicator -->
26+
<div class="mb-4">
27+
<div class="progress">
28+
<div class="progress-bar" role="progressbar" style="width: 100%;" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
29+
Step 2 of 2
30+
</div>
31+
</div>
32+
</div>
33+
34+
<p class="text-center mb-4">
35+
Let’s set up your <strong>Platform Manager account</strong>. This will be your personal login and public profile as the community’s first manager.
36+
</p>
37+
38+
<% if @form.errors.any? %>
39+
<div class="alert alert-danger">
40+
<h4><%= pluralize(@form.errors.count, "error") %> prevented this account from being created:</h4>
41+
<ul>
42+
<% @form.errors.full_messages.each do |message| %>
43+
<li><%= message %></li>
44+
<% end %>
45+
</ul>
46+
</div>
47+
<% end %>
48+
49+
<!-- Email Field -->
50+
<div class="mb-3">
51+
<%= f.label :email, 'Email Address', class: 'form-label' %>
52+
<%= f.email_field :email, autofocus: true, required: true,
53+
class: "form-control#{' is-invalid' if @form.errors[:email].any?}",
54+
data: { action: 'blur->better_together--form-validation#validate' } %>
55+
<small class="form-text text-muted">This will be your primary login and how the platform contacts you.</small>
56+
<% if @form.errors[:email].any? %>
57+
<div class="invalid-feedback d-block"><%= @form.errors[:email].join(', ') %></div>
58+
<% end %>
59+
</div>
60+
61+
<!-- Password Field -->
62+
<div class="mb-3">
63+
<%= f.label :password, 'Password', class: 'form-label' %>
64+
<%= f.password_field :password, required: true, minlength: min_password_length,
65+
class: "form-control#{' is-invalid' if @form.errors[:password].any?}",
66+
data: { action: 'blur->better_together--form-validation#validate', 'better_together--form-validation-min-length-value': min_password_length } %>
67+
<small class="form-text text-muted">
68+
Your password must be at least <%= min_password_length %> characters long. Use a mix of upper and lowercase letters, numbers, and symbols for extra security.
69+
</small>
70+
<% if @form.errors[:password].any? %>
71+
<div class="invalid-feedback d-block"><%= @form.errors[:password].join(', ') %></div>
72+
<% end %>
73+
</div>
74+
75+
<!-- Password Confirmation Field -->
76+
<div class="mb-3">
77+
<%= f.label :password_confirmation, 'Confirm Password', class: 'form-label' %>
78+
<%= f.password_field :password_confirmation, required: true, minlength: min_password_length,
79+
class: "form-control#{' is-invalid' if @form.errors[:password_confirmation].any?}",
80+
data: { action: 'blur->better_together--form-validation#validate', 'better_together--form-validation-min-length-value': min_password_length } %>
81+
<% if @form.errors[:password_confirmation].any? %>
82+
<div class="invalid-feedback d-block"><%= @form.errors[:password_confirmation].join(', ') %></div>
83+
<% end %>
84+
</div>
1885

19-
<div id="login-details" class="mb-4">
20-
<h4>Login Details</h4>
21-
<!-- Email Field -->
86+
<!-- Profile Details -->
87+
<h4 class="mt-4 mb-3">Your Public Profile</h4>
88+
<p class="small text-muted">
89+
These details will be visible to other members of the platform. You can edit them later.
90+
</p>
91+
92+
<%= f.fields_for :person do |person_form| %>
93+
<!-- Name Field -->
2294
<div class="mb-3">
23-
<%= f.label :email, class: 'form-label' %>
24-
<%= f.email_field :email, autofocus: true, class: "form-control#{' is-invalid' if @form.errors[:email].any?}" %>
25-
<% if @form.errors[:email].any? %>
26-
<div class="invalid-feedback d-block">
27-
<%= @form.errors[:email].join(", ") %>
28-
</div>
95+
<%= person_form.label :name, 'Full Name', class: 'form-label' %>
96+
<%= person_form.text_field :name, required: true,
97+
class: "form-control#{' is-invalid' if @form.errors[:name].any?}",
98+
data: { action: 'blur->better_together--form-validation#validate' } %>
99+
<small class="form-text text-muted">
100+
Use your real name or the name you want your community to recognize you by.
101+
</small>
102+
<% if @form.errors[:name].any? %>
103+
<div class="invalid-feedback d-block"><%= @form.errors[:name].join(', ') %></div>
29104
<% end %>
30105
</div>
31106

32-
<!-- Password Field -->
107+
<!-- Username/Identifier Field -->
33108
<div class="mb-3">
34-
<%= f.label :password, class: 'form-label' %>
35-
<%= f.password_field :password, class: "form-control#{' is-invalid' if @form.errors[:password].any?}" %>
36-
<% if @form.errors[:password].any? %>
37-
<div class="invalid-feedback d-block">
38-
<%= @form.errors[:password].join(", ") %>
39-
</div>
109+
<%= person_form.label :identifier, 'Username', class: 'form-label' %>
110+
<%= person_form.text_field :identifier, required: true,
111+
class: "form-control#{' is-invalid' if @form.errors[:identifier].any?}",
112+
data: { action: 'blur->better_together--form-validation#validate' } %>
113+
<small class="form-text text-muted">
114+
Your unique handle (no spaces). This will appear in your profile URL and mentions.
115+
</small>
116+
<% if @form.errors[:identifier].any? %>
117+
<div class="invalid-feedback d-block"><%= @form.errors[:identifier].join(', ') %></div>
40118
<% end %>
41119
</div>
42120

43-
<!-- Password Confirmation Field -->
121+
<!-- Description Field -->
44122
<div class="mb-3">
45-
<%= f.label :password_confirmation, class: 'form-label' %>
46-
<%= f.password_field :password_confirmation, class: "form-control#{' is-invalid' if @form.errors[:password_confirmation].any?}" %>
47-
<% if @form.errors[:password_confirmation].any? %>
48-
<div class="invalid-feedback d-block">
49-
<%= @form.errors[:password_confirmation].join(", ") %>
50-
</div>
123+
<%= person_form.label :description, 'Short Bio', class: 'form-label' %>
124+
<%= person_form.text_area :description, required: true, rows: 3,
125+
class: "form-control#{' is-invalid' if @form.errors[:description].any?}",
126+
data: { action: 'blur->better_together--form-validation#validate' } %>
127+
<small class="form-text text-muted">
128+
Introduce yourself to the community. Why are you starting this platform? What’s your vision?
129+
</small>
130+
<% if @form.errors[:description].any? %>
131+
<div class="invalid-feedback d-block"><%= @form.errors[:description].join(', ') %></div>
51132
<% end %>
52133
</div>
53-
</div>
54-
55-
<div id="profile-details" class="mb-4">
56-
<h4>Profile Details</h4>
57-
<!-- Person Identification Fields -->
58-
<%= f.fields_for :person do |person_form| %>
59-
<!-- Name Field -->
60-
<div class="mb-3">
61-
<%= person_form.label :name, class: 'form-label' %>
62-
<%= person_form.text_field :name, class: "form-control#{' is-invalid' if @form.errors[:name].any?}" %>
63-
<% if @form.errors[:name].any? %>
64-
<div class="invalid-feedback d-block">
65-
<%= @form.errors[:name].join(", ") %>
66-
</div>
67-
<% end %>
68-
</div>
69-
70-
<!-- Username Field -->
71-
<div class="mb-3">
72-
<%= person_form.label :identifier, class: 'form-label' %>
73-
<%= person_form.text_field :identifier, class: "form-control#{' is-invalid' if @form.errors[:identifier].any?}" %>
74-
<!-- Hint text for the Handle -->
75-
<small class="form-text text-muted">Your identifier is a unique username that identifies your profile on the site.</small>
76-
<% if @form.errors[:identifier].any? %>
77-
<div class="invalid-feedback d-block">
78-
<%= @form.errors[:identifier].join(", ") %>
79-
</div>
80-
<% end %>
81-
</div>
82-
83-
<!-- Description Field -->
84-
<div class="mb-3">
85-
<%= person_form.label :description, class: 'form-label' %>
86-
<%= person_form.text_area :description, class: "form-control#{' is-invalid' if @form.errors[:description].any?}" %>
87-
<% if @form.errors[:description].any? %>
88-
<div class="invalid-feedback d-block">
89-
<%= @form.errors[:description].join(", ") %>
90-
</div>
91-
<% end %>
92-
</div>
93-
94-
<% end %>
95-
</div>
134+
<% end %>
96135

97136
<!-- Submit Button -->
98-
<div class="text-center">
99-
<%= f.submit 'Finish Setup', class: 'btn btn-primary' %>
137+
<div class="mb-3">
138+
<%= f.submit 'Finish Setup', class: 'btn btn-primary w-100' %>
100139
</div>
101140
</div>
102141
</div>

app/views/better_together/wizard_step_definitions/host_setup/platform_details.html.erb

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,44 @@
1-
<!-- app/views/better_together/setup_wizard_step_definitions/host_setup/platform_details.html.erb -->
1+
<!-- Hero Section -->
2+
<div class="container-fluid p-0">
3+
<div class="hero-section" style="background: url(<%= asset_path('better_together/unsplash-community-1.jpeg') %>) no-repeat center center; background-size: cover; height: 50vh;">
4+
<div class="overlay" style="background: rgba(0, 0, 0, 0.3); height: 100%; width: 100%;">
5+
<div class="container h-100 d-flex align-items-center justify-content-center">
6+
<div class="text-center text-white">
7+
<h1 class="display-4 fw-bold">Welcome to Your Community Space</h1>
8+
<p class="lead">
9+
Let’s create a welcoming home for your community to connect, share, and thrive.
10+
</p>
11+
</div>
12+
</div>
13+
</div>
14+
</div>
15+
</div>
216

17+
<!-- Form Section -->
318
<div class="container my-5">
419
<div class="row justify-content-center">
5-
<div class="col-md-6">
6-
<h1 class="text-center mb-4">Setup Your Better Together Community Platform</h1>
7-
<p class="text-center mb-4">Fill out the platform details below to set up your platform.</p>
20+
<div class="col-md-8 col-lg-6">
21+
<!-- Progress Indicator -->
22+
<div class="mb-4">
23+
<div class="progress">
24+
<div
25+
class="progress-bar"
26+
role="progressbar"
27+
style="width: 50%;"
28+
aria-valuenow="50"
29+
aria-valuemin="0"
30+
aria-valuemax="100"
31+
>
32+
Step 1 of 2
33+
</div>
34+
</div>
35+
</div>
36+
37+
<p class="text-center mb-4">
38+
A strong community starts with a solid foundation. Let’s begin by filling in
39+
a few details about your platform. Before you know it, you’ll have a home base
40+
where everyone can gather and collaborate.
41+
</p>
842

943
<% if @form.errors.any? %>
1044
<div class="alert alert-danger">
@@ -19,48 +53,57 @@
1953

2054
<%= form_for @form, url: setup_wizard_step_create_host_platform_path, method: :post, class: 'needs-validation', novalidate: true do |f| %>
2155
<div class="mb-3">
22-
<%= f.label :name, class: 'form-label' %>
23-
<%= f.text_field :name, autofocus: true, class: "form-control#{' is-invalid' if @form.errors[:name].any?}", required: true %>
56+
<%= f.label :name, 'What should we call your platform?', class: 'form-label' %>
57+
<%= f.text_field :name, autofocus: true, placeholder: 'Example: The Community Hall', class: "form-control#{' is-invalid' if @form.errors[:name].any?}", required: true %>
58+
<small class="form-text text-muted">
59+
This name will appear at the top of your platform and welcome everyone who arrives.
60+
</small>
2461
</div>
2562

2663
<div class="mb-3">
27-
<%= f.label :description, class: 'form-label' %>
28-
<%= f.text_area :description, class: "form-control#{' is-invalid' if @form.errors[:description].any?}", rows: 3, required: true %>
64+
<%= f.label :description, 'How would you describe your community space?', class: 'form-label' %>
65+
<%= f.text_area :description, placeholder: 'A place where neighbors and friends support each other.', class: "form-control#{' is-invalid' if @form.errors[:description].any?}", rows: 3, required: true %>
66+
<small class="form-text text-muted">
67+
This helps visitors understand what your community is all about.
68+
</small>
2969
</div>
3070

3171
<div class="mb-3">
32-
<%= f.label :url, class: 'form-label' %>
33-
<%= f.text_field :url, class: "form-control#{' is-invalid' if @form.errors[:url].any?}", required: true %>
72+
<%= f.label :url, 'Where can people find you online?', class: 'form-label' %>
73+
<%= f.text_field :url, placeholder: 'https://yourplatform.com', class: "form-control#{' is-invalid' if @form.errors[:url].any?}", required: true %>
74+
<small class="form-text text-muted">
75+
This will be your platform’s web address — its front door on the internet.
76+
</small>
3477
</div>
3578

3679
<div class="mb-3">
37-
<%= f.label :privacy, class: 'form-label' %>
80+
<%= f.label :privacy, 'Who can visit your space?', class: 'form-label' %>
3881
<%= f.select :privacy, BetterTogether::Platform.privacies.keys.map { |privacy| [privacy.humanize, privacy] }, {}, { class: 'form-select', required: true } %>
82+
<small class="form-text text-muted">
83+
Choose whether your space is open to the public or invitation-only.
84+
</small>
3985
</div>
4086

41-
<div class="mb-3">
42-
<%= f.label :time_zone, class: 'form-label' %>
43-
<%= f.time_zone_select :time_zone, ActiveSupport::TimeZone.all, {}, { class: 'form-select', id: 'time_zone_select', required: true } %>
87+
<!-- Time Zone Section with Stimulus Controller -->
88+
<div class="mb-3" data-controller="better-together--time-zone">
89+
<%= f.label :time_zone, 'Which time zone should we use?', class: 'form-label' %>
90+
<%= f.time_zone_select :time_zone, ActiveSupport::TimeZone.all, {},
91+
{
92+
class: 'form-select',
93+
data: { 'better-together--time-zone-target': 'select' },
94+
id: 'time_zone_select',
95+
required: true
96+
}
97+
%>
98+
<small class="form-text text-muted">
99+
This helps keep events and notifications in sync for everyone.
100+
</small>
44101
</div>
45102

46-
<%= f.submit 'Next Step', class: 'btn btn-primary' %>
103+
<div class="mb-3">
104+
<%= f.submit 'Next Step', class: 'btn btn-primary w-100' %>
105+
</div>
47106
<% end %>
48107
</div>
49108
</div>
50109
</div>
51-
52-
<script>
53-
document.addEventListener('DOMContentLoaded', function() {
54-
var userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
55-
var timeZoneSelect = document.getElementById('time_zone_select');
56-
57-
if (timeZoneSelect) {
58-
for (var i = 0; i < timeZoneSelect.options.length; i++) {
59-
if (timeZoneSelect.options[i].value === userTimeZone) {
60-
timeZoneSelect.selectedIndex = i;
61-
break;
62-
}
63-
}
64-
}
65-
});
66-
</script>

0 commit comments

Comments
 (0)