bible270 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +181 -0
- data/MIT-LICENSE +18 -0
- data/README.md +373 -0
- data/Rakefile +11 -0
- data/app/controllers/bible270/application_controller.rb +48 -0
- data/app/controllers/bible270/checkoffs_controller.rb +32 -0
- data/app/controllers/bible270/comments_controller.rb +46 -0
- data/app/controllers/bible270/days_controller.rb +42 -0
- data/app/controllers/bible270/readers_controller.rb +46 -0
- data/app/controllers/bible270/sessions_controller.rb +126 -0
- data/app/helpers/bible270/plan_helper.rb +45 -0
- data/app/mailers/bible270/application_mailer.rb +7 -0
- data/app/mailers/bible270/sign_in_mailer.rb +14 -0
- data/app/models/bible270/application_record.rb +6 -0
- data/app/models/bible270/checkoff.rb +23 -0
- data/app/models/bible270/comment.rb +16 -0
- data/app/models/bible270/reader.rb +211 -0
- data/app/models/bible270/sign_in_token.rb +74 -0
- data/app/views/bible270/checkoffs/toggle.turbo_stream.erb +4 -0
- data/app/views/bible270/comments/_comment.html.erb +15 -0
- data/app/views/bible270/comments/_form.html.erb +13 -0
- data/app/views/bible270/comments/create.turbo_stream.erb +7 -0
- data/app/views/bible270/comments/destroy.turbo_stream.erb +1 -0
- data/app/views/bible270/days/_reading.html.erb +25 -0
- data/app/views/bible270/days/_start_date.html.erb +43 -0
- data/app/views/bible270/days/index.html.erb +72 -0
- data/app/views/bible270/days/show.html.erb +50 -0
- data/app/views/bible270/readers/index.html.erb +24 -0
- data/app/views/bible270/readers/show.html.erb +41 -0
- data/app/views/bible270/sessions/new.html.erb +52 -0
- data/app/views/bible270/shared/_header.html.erb +20 -0
- data/app/views/bible270/shared/_styles.html.erb +147 -0
- data/app/views/bible270/sign_in_mailer/magic_link.html.erb +20 -0
- data/app/views/bible270/sign_in_mailer/magic_link.text.erb +7 -0
- data/app/views/layouts/bible270/application.html.erb +28 -0
- data/config/routes.rb +30 -0
- data/db/migrate/20260101000001_create_bible270_readers.rb +17 -0
- data/db/migrate/20260101000002_create_bible270_checkoffs.rb +15 -0
- data/db/migrate/20260101000003_create_bible270_comments.rb +14 -0
- data/db/migrate/20260101000004_create_bible270_sign_in_tokens.rb +18 -0
- data/lib/bible270/configuration.rb +177 -0
- data/lib/bible270/email_sign_in.rb +68 -0
- data/lib/bible270/engine.rb +21 -0
- data/lib/bible270/plan.rb +352 -0
- data/lib/bible270/versification.rb +83 -0
- data/lib/bible270/version.rb +4 -0
- data/lib/bible270.rb +9 -0
- data/lib/generators/bible270/install/install_generator.rb +66 -0
- data/lib/generators/bible270/install/templates/bible270.rb.tt +41 -0
- data/lib/generators/bible270/install/templates/omniauth.rb.tt +25 -0
- metadata +158 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= form_with model: comment, url: day_comments_path(day), method: :post,
|
|
2
|
+
id: "new_comment_form", class: "b270-form", data: { turbo_stream: true } do |f| %>
|
|
3
|
+
<% if comment.errors.any? %>
|
|
4
|
+
<p style="color:var(--oxblood);font-size:13px"><%= comment.errors.full_messages.to_sentence %></p>
|
|
5
|
+
<% end %>
|
|
6
|
+
<%= f.text_area :body, placeholder: "Share a reflection on today's reading…" %>
|
|
7
|
+
<div class="row">
|
|
8
|
+
<%= f.select :track,
|
|
9
|
+
options_for_select([["The whole day", ""]] + Bible270::Plan::TRACKS.map { |k, v| [v[:label], k] }),
|
|
10
|
+
{}, style: "font:inherit;padding:8px;border:1px solid var(--line);border-radius:8px;background:#fff" %>
|
|
11
|
+
<%= f.submit "Post reflection", class: "b270-btn" %>
|
|
12
|
+
</div>
|
|
13
|
+
<% end %>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<%= turbo_stream.remove "no_comments" %>
|
|
2
|
+
<%= turbo_stream.append "comments" do %>
|
|
3
|
+
<%= render partial: "bible270/comments/comment", locals: { comment: @comment } %>
|
|
4
|
+
<% end %>
|
|
5
|
+
<%= turbo_stream.replace "new_comment_form" do %>
|
|
6
|
+
<%= render "bible270/comments/form", day: @day, comment: Bible270::Comment.new(day: @day) %>
|
|
7
|
+
<% end %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= turbo_stream.remove "comment-#{@comment.id}" %>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<% t = b270_track(track); ref = readings[track]; done = reader_tracks.include?(track) %>
|
|
2
|
+
<% if ref %>
|
|
3
|
+
<div class="b270-reading <%= track %> <%= 'done' if done %>" id="reading_<%= day %>_<%= track %>">
|
|
4
|
+
<% if signed_in? %>
|
|
5
|
+
<%= button_to toggle_checkoff_path(day, track), class: "b270-toggle", form: { data: { turbo_stream: true } },
|
|
6
|
+
aria: { label: (done ? "Mark #{t[:label]} unread" : "Mark #{t[:label]} read") } do %>
|
|
7
|
+
<span class="b270-check"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg></span>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% else %>
|
|
10
|
+
<span class="b270-check"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg></span>
|
|
11
|
+
<% end %>
|
|
12
|
+
<div class="b270-rbody">
|
|
13
|
+
<div class="b270-cat"><%= t[:label] %></div>
|
|
14
|
+
<div class="b270-ref"><%= link_to ref, b270_passage_url(ref), target: "_blank", rel: "noopener" %></div>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<% else %>
|
|
18
|
+
<div class="b270-reading <%= track %> rest" id="reading_<%= day %>_<%= track %>">
|
|
19
|
+
<span class="b270-check" style="visibility:hidden"></span>
|
|
20
|
+
<div class="b270-rbody">
|
|
21
|
+
<div class="b270-cat"><%= t[:label] %></div>
|
|
22
|
+
<div class="b270-ref">Rest · revisit or reflect</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<% end %>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<div class="b270-startdate">
|
|
2
|
+
<% if reader.dated? %>
|
|
3
|
+
<p class="b270-startline">
|
|
4
|
+
<% if reader.own_start_date? %>
|
|
5
|
+
Started <strong><%= reader.start_date.strftime("%B %-d, %Y") %></strong>
|
|
6
|
+
<% else %>
|
|
7
|
+
Community plan began <strong><%= Bible270.config.start_date.strftime("%B %-d, %Y") %></strong>
|
|
8
|
+
<% end %>
|
|
9
|
+
· finishes <%= reader.plan_end_date.strftime("%B %-d, %Y") %>
|
|
10
|
+
<% if reader.not_started_yet? %>
|
|
11
|
+
· <em>begins in <%= (reader.effective_start_date - Date.current).to_i %> days</em>
|
|
12
|
+
<% elsif reader.past_end_date? %>
|
|
13
|
+
· <em>the 270 days have elapsed</em>
|
|
14
|
+
<% elsif (off = reader.days_off_pace) %>
|
|
15
|
+
<% if off > 0 %>
|
|
16
|
+
· <em><%= off %> day<%= "s" unless off == 1 %> behind</em>
|
|
17
|
+
<% elsif off < 0 %>
|
|
18
|
+
· <em><%= -off %> day<%= "s" unless off == -1 %> ahead</em>
|
|
19
|
+
<% else %>
|
|
20
|
+
· <em>right on pace</em>
|
|
21
|
+
<% end %>
|
|
22
|
+
<% end %>
|
|
23
|
+
</p>
|
|
24
|
+
<% else %>
|
|
25
|
+
<p class="b270-startline">This plan is undated — pick a start date to see it on a calendar.</p>
|
|
26
|
+
<% end %>
|
|
27
|
+
|
|
28
|
+
<% if Bible270.config.allow_reader_start_date %>
|
|
29
|
+
<details class="b270-startedit">
|
|
30
|
+
<summary><%= reader.own_start_date? ? "Change start date" : "Set a start date" %></summary>
|
|
31
|
+
<%= form_with url: start_date_path, method: :patch, class: "b270-startform" do |f| %>
|
|
32
|
+
<%= f.date_field :start_date, value: reader.start_date, required: true %>
|
|
33
|
+
<%= f.submit "Save", class: "b270-btn" %>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% if reader.own_start_date? %>
|
|
36
|
+
<%= button_to "Clear my start date", clear_start_date_path, method: :delete,
|
|
37
|
+
class: "b270-linkbtn", form: { style: "display:inline" } %>
|
|
38
|
+
<% end %>
|
|
39
|
+
<p class="b270-hint">Setting a start date only changes how days map onto the calendar.
|
|
40
|
+
Your check-offs and reflections are untouched.</p>
|
|
41
|
+
</details>
|
|
42
|
+
<% end %>
|
|
43
|
+
</div>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<% content_for :title, Bible270.config.app_name %>
|
|
2
|
+
<p class="b270-eyebrow"><%= Bible270.config.tagline %></p>
|
|
3
|
+
<h1><%= Bible270.config.app_name %> <em>— read the story straight through</em></h1>
|
|
4
|
+
<p class="lede">Each day: a passage from the Old Testament, a couple of chapters of the New
|
|
5
|
+
Testament, and a Psalm or Proverb — portions sized to take about the same time to read.
|
|
6
|
+
Check off what you read; your place is kept for you, and you can see how the rest of the
|
|
7
|
+
community is journeying too.</p>
|
|
8
|
+
|
|
9
|
+
<div class="b270-panel">
|
|
10
|
+
<% if signed_in? %>
|
|
11
|
+
<div class="b270-bignum">
|
|
12
|
+
<span class="n"><%= current_reader.days_completed %></span>
|
|
13
|
+
<span class="of">of <%= Plan::DAYS %> days complete</span>
|
|
14
|
+
<span class="pct"><%= current_reader.completion_percent %>%</span>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="b270-tracks">
|
|
17
|
+
<% [["ot", current_reader.days_read_in("ot"), Plan::DAYS],
|
|
18
|
+
["nt", current_reader.days_read_in("nt"), Plan.nt_content_days],
|
|
19
|
+
["pp", current_reader.days_read_in("pp"), Plan::DAYS]].each do |track, read, total| %>
|
|
20
|
+
<% t = b270_track(track) %>
|
|
21
|
+
<div class="b270-tm">
|
|
22
|
+
<span class="lab"><span class="b270-rib" style="background:<%= t[:color] %>"></span><%= t[:label] %></span>
|
|
23
|
+
<span class="b270-bar"><i style="width:<%= total.zero? ? 0 : (read.to_f/total*100).round %>%;background:<%= t[:color] %>"></i></span>
|
|
24
|
+
<span class="val"><%= read %>/<%= total %></span>
|
|
25
|
+
</div>
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
28
|
+
<p style="margin:16px 0 0">
|
|
29
|
+
<%= link_to "Continue reading — Day #{@start_day} →", day_path(@start_day), class: "b270-btn" %>
|
|
30
|
+
<% if @today_day && @today_day != @start_day %>
|
|
31
|
+
<%= link_to "Go to today (Day #{@today_day})", day_path(@today_day), style: "margin-left:12px" %>
|
|
32
|
+
<% end %>
|
|
33
|
+
</p>
|
|
34
|
+
<%= render "bible270/days/start_date", reader: current_reader %>
|
|
35
|
+
<% else %>
|
|
36
|
+
<p class="b270-signedout"><%= link_to "Sign in", sign_in_path %> to track your progress and join
|
|
37
|
+
the conversation. You can browse the whole plan and read others' reflections without an account.</p>
|
|
38
|
+
<% if @community_start_date %>
|
|
39
|
+
<p class="b270-startline">The community began this plan on
|
|
40
|
+
<strong><%= @community_start_date.strftime("%B %-d, %Y") %></strong>
|
|
41
|
+
and finishes <%= Plan.end_date_for(@community_start_date).strftime("%B %-d, %Y") %>.</p>
|
|
42
|
+
<% end %>
|
|
43
|
+
<p style="margin:14px 0 0"><%= link_to "Start at Day 1 →", day_path(1), class: "b270-btn" %></p>
|
|
44
|
+
<% end %>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<% if @readers.any? %>
|
|
48
|
+
<h2 class="b270-section-h">The community</h2>
|
|
49
|
+
<div class="b270-list">
|
|
50
|
+
<% @readers.first(5).each_with_index do |r, i| %>
|
|
51
|
+
<div class="b270-li">
|
|
52
|
+
<span class="rank"><%= i + 1 %></span>
|
|
53
|
+
<%= b270_avatar(r) %>
|
|
54
|
+
<div>
|
|
55
|
+
<div class="name"><%= link_to r.display_name, reader_path(r), style: "text-decoration:none" %></div>
|
|
56
|
+
<div class="sub"><%= @days_completed[r.id] %> days read</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="stat"><span class="big"><%= (@days_completed[r.id].to_f / Plan::DAYS * 100).round %>%</span></div>
|
|
59
|
+
</div>
|
|
60
|
+
<% end %>
|
|
61
|
+
</div>
|
|
62
|
+
<p style="margin:12px 0 0"><%= link_to "See everyone →", community_path %></p>
|
|
63
|
+
<% end %>
|
|
64
|
+
|
|
65
|
+
<details class="b270-index">
|
|
66
|
+
<summary>All <%= Plan::DAYS %> days</summary>
|
|
67
|
+
<div class="b270-grid">
|
|
68
|
+
<% (1..Plan::DAYS).each do |d| %>
|
|
69
|
+
<%= link_to d, day_path(d), class: "b270-cell #{b270_day_status_class(current_reader, d)} #{'now' if signed_in? && d == @start_day}" %>
|
|
70
|
+
<% end %>
|
|
71
|
+
</div>
|
|
72
|
+
</details>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<% content_for :title, "Day #{@day} — #{Bible270.config.app_name}" %>
|
|
2
|
+
<div class="b270-daynav">
|
|
3
|
+
<%= link_to "‹", (@prev_day ? day_path(@prev_day) : "#"), class: "b270-arrow #{'disabled' unless @prev_day}", "aria-label": "Previous day" %>
|
|
4
|
+
<div class="b270-dayhead">
|
|
5
|
+
<div class="k">Day <%= @day %><% if signed_in? && current_reader.day_complete?(@day) %><span class="b270-badge">Complete</span><% end %></div>
|
|
6
|
+
<% if signed_in? && current_reader.dated? %>
|
|
7
|
+
<div class="date">
|
|
8
|
+
<%= current_reader.date_for_day(@day).strftime("%A, %B %-d, %Y") %>
|
|
9
|
+
<% if current_reader.calendar_day == @day %><span class="b270-badge">Today</span><% end %>
|
|
10
|
+
</div>
|
|
11
|
+
<% end %>
|
|
12
|
+
</div>
|
|
13
|
+
<%= link_to "›", (@next_day ? day_path(@next_day) : "#"), class: "b270-arrow #{'disabled' unless @next_day}", "aria-label": "Next day" %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div id="readings" class="b270-readings">
|
|
17
|
+
<%= render partial: "bible270/days/reading", collection: %w[ot nt pp],
|
|
18
|
+
as: :track, locals: { day: @day, readings: @readings, reader_tracks: @reader_tracks } %>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<% unless signed_in? %>
|
|
22
|
+
<p class="b270-signin-hint" style="margin-top:12px"><%= link_to "Sign in", sign_in_path(origin: request.fullpath) %> to check off these readings.</p>
|
|
23
|
+
<% end %>
|
|
24
|
+
|
|
25
|
+
<div class="b270-completers" id="completers_<%= @day %>">
|
|
26
|
+
<% if @completers.any? %>
|
|
27
|
+
<span>Finished this day:</span>
|
|
28
|
+
<% @completers.first(12).each { |r| %><%= link_to b270_avatar(r), reader_path(r), title: r.display_name %><% } %>
|
|
29
|
+
<% if @completers.size > 12 %><span><%= @completers.size - 12 %> more</span><% end %>
|
|
30
|
+
<% else %>
|
|
31
|
+
<span>No one has finished this day yet — be the first.</span>
|
|
32
|
+
<% end %>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<h2 class="b270-section-h">Reflections</h2>
|
|
36
|
+
<div id="comments">
|
|
37
|
+
<% if @comments.any? %>
|
|
38
|
+
<%= render partial: "bible270/comments/comment", collection: @comments, as: :comment %>
|
|
39
|
+
<% else %>
|
|
40
|
+
<p style="color:var(--faint)" id="no_comments">No reflections yet. Share the first.</p>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<% if signed_in? %>
|
|
45
|
+
<div style="margin-top:18px">
|
|
46
|
+
<%= render "bible270/comments/form", day: @day, comment: @new_comment %>
|
|
47
|
+
</div>
|
|
48
|
+
<% else %>
|
|
49
|
+
<p class="b270-signin-hint" style="margin-top:14px"><%= link_to "Sign in", sign_in_path(origin: request.fullpath) %> to leave a reflection.</p>
|
|
50
|
+
<% end %>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<% content_for :title, "Community — #{Bible270.config.app_name}" %>
|
|
2
|
+
<p class="b270-eyebrow">Reading together</p>
|
|
3
|
+
<h1>The community</h1>
|
|
4
|
+
<p class="lede">Everyone journeying through the plan, and how far each has come.</p>
|
|
5
|
+
|
|
6
|
+
<% if @readers.any? %>
|
|
7
|
+
<div class="b270-list">
|
|
8
|
+
<% @readers.each_with_index do |r, i| %>
|
|
9
|
+
<div class="b270-li">
|
|
10
|
+
<span class="rank"><%= i + 1 %></span>
|
|
11
|
+
<%= b270_avatar(r, size: 40) %>
|
|
12
|
+
<div>
|
|
13
|
+
<div class="name"><%= link_to r.display_name, reader_path(r), style: "text-decoration:none" %></div>
|
|
14
|
+
<div class="sub"><%= @days_completed[r.id] %> of <%= Plan::DAYS %> days · joined <%= r.created_at.strftime("%b %Y") %></div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="stat">
|
|
17
|
+
<span class="big"><%= (@days_completed[r.id].to_f / Plan::DAYS * 100).round %>%</span>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
<% else %>
|
|
23
|
+
<p style="color:var(--faint)">No readers yet. Be the first to begin.</p>
|
|
24
|
+
<% end %>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<% content_for :title, "#{@reader.display_name} — #{Bible270.config.app_name}" %>
|
|
2
|
+
<div style="display:flex;align-items:center;gap:16px;margin-bottom:8px">
|
|
3
|
+
<%= b270_avatar(@reader, size: 56) %>
|
|
4
|
+
<div>
|
|
5
|
+
<h1 style="margin:0"><%= @reader.display_name %></h1>
|
|
6
|
+
<p style="margin:2px 0 0;color:var(--faint);font-size:14px"><%= @days_completed %> of <%= Plan::DAYS %> days complete · <%= @reader.completion_percent %>%</p>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="b270-panel" style="margin-top:18px">
|
|
11
|
+
<div class="b270-tracks">
|
|
12
|
+
<% [["ot", @reader.days_read_in("ot"), Plan::DAYS],
|
|
13
|
+
["nt", @reader.days_read_in("nt"), Plan.nt_content_days],
|
|
14
|
+
["pp", @reader.days_read_in("pp"), Plan::DAYS]].each do |track, read, total| %>
|
|
15
|
+
<% t = b270_track(track) %>
|
|
16
|
+
<div class="b270-tm">
|
|
17
|
+
<span class="lab"><span class="b270-rib" style="background:<%= t[:color] %>"></span><%= t[:label] %></span>
|
|
18
|
+
<span class="b270-bar"><i style="width:<%= total.zero? ? 0 : (read.to_f/total*100).round %>%;background:<%= t[:color] %>"></i></span>
|
|
19
|
+
<span class="val"><%= read %>/<%= total %></span>
|
|
20
|
+
</div>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="b270-grid">
|
|
26
|
+
<% (1..Plan::DAYS).each do |d| %>
|
|
27
|
+
<%= link_to d, day_path(d), class: "b270-cell #{b270_day_status_class(@reader, d)}" %>
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<% if @recent_comments.any? %>
|
|
32
|
+
<h2 class="b270-section-h">Recent reflections</h2>
|
|
33
|
+
<% @recent_comments.each do |c| %>
|
|
34
|
+
<div class="b270-comment">
|
|
35
|
+
<div style="flex:1">
|
|
36
|
+
<div class="b270-cmeta"><%= link_to "Day #{c.day}", day_path(c.day), style: "text-decoration:none;color:var(--gold)" %> · <%= c.created_at.strftime("%b %-d, %Y") %></div>
|
|
37
|
+
<p class="b270-cbody"><%= c.body %></p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<% content_for :title, "Sign in — #{Bible270.config.app_name}" %>
|
|
2
|
+
<p class="b270-eyebrow">Sign in</p>
|
|
3
|
+
<h1>Join the reading</h1>
|
|
4
|
+
<p class="lede">Signing in keeps your place in the plan, records what you've read, and lets you
|
|
5
|
+
leave reflections alongside everyone else. Browsing the plan and reading others' reflections
|
|
6
|
+
never requires an account.</p>
|
|
7
|
+
|
|
8
|
+
<div class="b270-panel">
|
|
9
|
+
<% if Bible270.config.email_sign_in? %>
|
|
10
|
+
<h2 class="b270-subhead">Sign in with your email</h2>
|
|
11
|
+
<p class="b270-hint" style="margin-top:0">No password and no account needed — we'll email you
|
|
12
|
+
a link that signs you in.</p>
|
|
13
|
+
<%= form_with url: email_sign_in_link_path, method: :post, class: "b270-emailform" do |f| %>
|
|
14
|
+
<%= hidden_field_tag :origin, @origin if @origin %>
|
|
15
|
+
<% if Bible270.config.email_sign_in_ask_name %>
|
|
16
|
+
<label class="b270-field">
|
|
17
|
+
<span>Name shown beside your reflections</span>
|
|
18
|
+
<%= text_field_tag :display_name, nil, placeholder: "e.g. Andrew", autocomplete: "name" %>
|
|
19
|
+
</label>
|
|
20
|
+
<% end %>
|
|
21
|
+
<label class="b270-field">
|
|
22
|
+
<span>Email address</span>
|
|
23
|
+
<%= email_field_tag :email, nil, required: true, placeholder: "you@example.com",
|
|
24
|
+
autocomplete: "email", inputmode: "email" %>
|
|
25
|
+
</label>
|
|
26
|
+
<%= f.submit "Email me a sign-in link", class: "b270-btn" %>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% end %>
|
|
29
|
+
|
|
30
|
+
<% if @providers.any? %>
|
|
31
|
+
<% if Bible270.config.email_sign_in? %>
|
|
32
|
+
<div class="b270-or"><span>or</span></div>
|
|
33
|
+
<% end %>
|
|
34
|
+
<h2 class="b270-subhead">Use an existing account</h2>
|
|
35
|
+
<div class="b270-providers">
|
|
36
|
+
<% @providers.each do |key, label| %>
|
|
37
|
+
<%= button_to b270_omniauth_path(key), method: :post, class: "b270-provider",
|
|
38
|
+
params: (@origin ? { origin: @origin } : {}), data: { turbo: false } do %>
|
|
39
|
+
Continue with <%= label %>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
<% end %>
|
|
44
|
+
|
|
45
|
+
<% unless Bible270.config.any_sign_in_method? %>
|
|
46
|
+
<p class="b270-signedout">No sign-in method is configured yet. Set
|
|
47
|
+
<code>config.email_sign_in</code> or <code>config.omniauth_providers</code>
|
|
48
|
+
in the host application.</p>
|
|
49
|
+
<% end %>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<p style="margin-top:18px"><%= link_to "← Back to the plan", root_path %></p>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<header class="b270-topbar">
|
|
2
|
+
<div class="b270-brand">
|
|
3
|
+
<%= link_to Bible270.config.app_name, root_path, class: "b270-title" %>
|
|
4
|
+
<span class="b270-tagline"><%= Bible270.config.tagline %></span>
|
|
5
|
+
</div>
|
|
6
|
+
<nav class="b270-nav">
|
|
7
|
+
<%= link_to "Plan", root_path %>
|
|
8
|
+
<%= link_to "Community", community_path %>
|
|
9
|
+
<% if signed_in? %>
|
|
10
|
+
<%= link_to current_reader.display_name, reader_path(current_reader), class: "b270-me" %>
|
|
11
|
+
<%= button_to "Sign out", sign_out_path, method: :delete, class: "b270-linkbtn",
|
|
12
|
+
form: { style: "display:inline" } %>
|
|
13
|
+
<% elsif Bible270.config.any_sign_in_method? %>
|
|
14
|
+
<%# Always route to the sign-in page: it offers email as well as any
|
|
15
|
+
social providers, so nobody is shut out. OmniAuth's own request phase
|
|
16
|
+
rejects GET, so its buttons live on that page as POST forms. %>
|
|
17
|
+
<%= link_to "Sign in", sign_in_path(origin: request.fullpath), class: "b270-signin" %>
|
|
18
|
+
<% end %>
|
|
19
|
+
</nav>
|
|
20
|
+
</header>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.b270{
|
|
3
|
+
--paper:#ece6da; --card:#f4efe6; --ink:#231f18; --muted:#6d6558; --faint:#9a9082;
|
|
4
|
+
--line:#d7cfbf; --line-soft:#e2dbcd; --teal:#2f6a67; --oxblood:#8a2f3b; --gold:#a5812c;
|
|
5
|
+
--gold-lite:#c6a24a; --shadow:0 1px 0 rgba(35,31,24,.04),0 10px 30px -18px rgba(35,31,24,.35);
|
|
6
|
+
margin:0; background:radial-gradient(120% 80% at 100% 0%,rgba(165,129,44,.06),transparent 55%),var(--paper);
|
|
7
|
+
color:var(--ink); font-family:"Inter",system-ui,sans-serif; line-height:1.5; -webkit-font-smoothing:antialiased;
|
|
8
|
+
}
|
|
9
|
+
.b270 *{box-sizing:border-box}
|
|
10
|
+
.b270 a{color:inherit}
|
|
11
|
+
.b270-wrap{max-width:820px;margin:0 auto;padding:clamp(14px,3.5vw,36px) clamp(14px,4vw,40px) 64px}
|
|
12
|
+
|
|
13
|
+
.b270-topbar{display:flex;align-items:center;gap:16px;flex-wrap:wrap;padding-bottom:20px;border-bottom:1px solid var(--line-soft);margin-bottom:26px}
|
|
14
|
+
.b270-brand{display:flex;flex-direction:column;margin-right:auto}
|
|
15
|
+
.b270-title{font-family:"Spectral",Georgia,serif;font-weight:600;font-size:22px;text-decoration:none}
|
|
16
|
+
.b270-tagline{font-size:12px;color:var(--faint);letter-spacing:.04em}
|
|
17
|
+
.b270-nav{display:flex;align-items:center;gap:16px;font-size:14px}
|
|
18
|
+
.b270-nav a{color:var(--muted);text-decoration:none}
|
|
19
|
+
.b270-nav a:hover{color:var(--gold)}
|
|
20
|
+
.b270-signin,.b270-me{color:var(--ink)!important;font-weight:600}
|
|
21
|
+
|
|
22
|
+
.b270-subhead{font-family:"Spectral",serif;font-size:17px;font-weight:600;margin:0 0 4px}
|
|
23
|
+
.b270-emailform{display:grid;gap:12px;max-width:340px;margin:12px 0 0}
|
|
24
|
+
.b270-field{display:grid;gap:5px;font-size:12.5px;color:var(--muted)}
|
|
25
|
+
.b270-field input{font:inherit;font-size:15px;padding:10px 12px;border:1px solid var(--line);border-radius:9px;background:#fff;color:var(--ink);width:100%}
|
|
26
|
+
.b270-field input:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px rgba(165,129,44,.13)}
|
|
27
|
+
.b270-emailform input[type=submit]{justify-self:start}
|
|
28
|
+
.b270-or{display:flex;align-items:center;gap:12px;margin:22px 0 18px;color:var(--faint);font-size:12px;letter-spacing:.1em;text-transform:uppercase}
|
|
29
|
+
.b270-or::before,.b270-or::after{content:"";flex:1;height:1px;background:var(--line-soft)}
|
|
30
|
+
.b270-providers{display:grid;gap:10px;max-width:340px}
|
|
31
|
+
.b270-provider{display:block;width:100%;text-align:center;border:1px solid var(--line);background:#fff;color:var(--ink);border-radius:10px;padding:12px 16px;font:inherit;font-weight:600;font-size:14.5px;cursor:pointer;transition:border-color .15s,transform .05s}
|
|
32
|
+
.b270-provider:hover{border-color:var(--gold);color:var(--gold)}
|
|
33
|
+
.b270-provider:active{transform:translateY(1px)}
|
|
34
|
+
.b270-signinbtn{border:1px solid var(--line);background:var(--card);color:var(--ink);border-radius:8px;padding:7px 13px;font:inherit;font-weight:600;font-size:13px;cursor:pointer}
|
|
35
|
+
.b270-signinbtn:hover{border-color:var(--gold);color:var(--gold)}
|
|
36
|
+
.b270-signin-hint a{color:var(--gold);font-weight:600}
|
|
37
|
+
.b270-flash{border-radius:10px;padding:11px 15px;margin-bottom:18px;font-size:14px}
|
|
38
|
+
.b270-flash.alert{background:#f6e4e2;color:#7a2b28;border:1px solid #e6c3bf}
|
|
39
|
+
.b270-flash.notice{background:#e7efe6;color:#2f5d34;border:1px solid #c6dcc6}
|
|
40
|
+
|
|
41
|
+
.b270-eyebrow{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);font-weight:600;margin:0 0 10px}
|
|
42
|
+
.b270 h1{font-family:"Spectral",serif;font-weight:600;font-size:clamp(28px,6vw,42px);line-height:1.03;letter-spacing:-.01em;margin:0 0 6px}
|
|
43
|
+
.b270 h1 em{font-style:italic;color:var(--muted);font-weight:400}
|
|
44
|
+
.b270 .lede{color:var(--muted);max-width:54ch;margin:0 0 24px;font-size:15px}
|
|
45
|
+
|
|
46
|
+
.b270-panel{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:20px 22px;box-shadow:var(--shadow);margin-bottom:24px}
|
|
47
|
+
.b270-bignum{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
|
|
48
|
+
.b270-bignum .n{font-family:"Spectral",serif;font-size:38px;font-weight:600;line-height:1}
|
|
49
|
+
.b270-bignum .of{color:var(--muted);font-size:15px}
|
|
50
|
+
.b270-bignum .pct{margin-left:auto;color:var(--gold);font-weight:600;font-variant-numeric:tabular-nums}
|
|
51
|
+
.b270-signedout{color:var(--muted);font-size:14px;margin:4px 0 0}
|
|
52
|
+
|
|
53
|
+
.b270-startdate{margin-top:18px;padding-top:16px;border-top:1px solid var(--line-soft)}
|
|
54
|
+
.b270-startline{margin:0;font-size:13.5px;color:var(--muted)}
|
|
55
|
+
.b270-startline strong{color:var(--ink)}
|
|
56
|
+
.b270-startline em{font-style:normal;color:var(--gold);font-weight:600}
|
|
57
|
+
.b270-startedit{margin-top:10px}
|
|
58
|
+
.b270-startedit summary{cursor:pointer;font-size:13px;color:var(--muted);list-style:none}
|
|
59
|
+
.b270-startedit summary::-webkit-details-marker{display:none}
|
|
60
|
+
.b270-startedit summary:hover{color:var(--gold)}
|
|
61
|
+
.b270-startform{display:flex;align-items:center;gap:10px;margin-top:10px;flex-wrap:wrap}
|
|
62
|
+
.b270-startform input[type=date]{font:inherit;font-size:14px;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:#fff;color:var(--ink)}
|
|
63
|
+
.b270-linkbtn{background:none;border:none;padding:0;margin-top:8px;font:inherit;font-size:12.5px;color:var(--oxblood);cursor:pointer;text-decoration:underline}
|
|
64
|
+
.b270-hint{margin:8px 0 0;font-size:12px;color:var(--faint);max-width:52ch}
|
|
65
|
+
.b270-tracks{margin-top:16px;display:grid;gap:11px}
|
|
66
|
+
.b270-tm{display:grid;grid-template-columns:130px 1fr auto;align-items:center;gap:12px;font-size:12.5px}
|
|
67
|
+
.b270-tm .lab{color:var(--muted);display:flex;align-items:center;gap:8px}
|
|
68
|
+
.b270-rib{width:11px;height:11px;border-radius:2px;flex:none}
|
|
69
|
+
.b270-bar{height:6px;border-radius:4px;background:var(--line-soft);overflow:hidden}
|
|
70
|
+
.b270-bar>i{display:block;height:100%;border-radius:4px}
|
|
71
|
+
.b270-tm .val{color:var(--faint);font-variant-numeric:tabular-nums;min-width:56px;text-align:right}
|
|
72
|
+
|
|
73
|
+
/* day nav */
|
|
74
|
+
.b270-daynav{display:flex;align-items:center;gap:10px;margin-bottom:12px}
|
|
75
|
+
.b270-arrow{width:38px;height:38px;border-radius:10px;border:1px solid var(--line);background:var(--card);cursor:pointer;font-size:17px;color:var(--muted);display:grid;place-items:center;text-decoration:none}
|
|
76
|
+
.b270-arrow:hover{border-color:var(--gold);color:var(--gold)}
|
|
77
|
+
.b270-arrow.disabled{opacity:.35;pointer-events:none}
|
|
78
|
+
.b270-dayhead{flex:1}
|
|
79
|
+
.b270-dayhead .k{font-family:"Spectral",serif;font-size:24px;font-weight:600;line-height:1}
|
|
80
|
+
.b270-badge{font-size:10.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:#fff;background:var(--gold);padding:3px 8px;border-radius:20px;margin-left:9px}
|
|
81
|
+
.b270-dayhead .date{color:var(--faint);font-size:13px;margin-top:2px}
|
|
82
|
+
|
|
83
|
+
/* readings */
|
|
84
|
+
.b270-readings{background:var(--card);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow);overflow:hidden}
|
|
85
|
+
.b270-reading{display:flex;gap:15px;align-items:center;padding:16px 20px;border-left:4px solid transparent}
|
|
86
|
+
.b270-reading+.b270-reading{border-top:1px solid var(--line-soft)}
|
|
87
|
+
.b270-reading.ot{border-left-color:var(--teal)} .b270-reading.nt{border-left-color:var(--oxblood)} .b270-reading.pp{border-left-color:var(--gold)}
|
|
88
|
+
.b270-reading.rest{opacity:.65}
|
|
89
|
+
.b270-toggle{border:none;background:none;padding:0;cursor:pointer}
|
|
90
|
+
.b270-toggle form{margin:0}
|
|
91
|
+
.b270-check{width:26px;height:26px;border-radius:8px;border:2px solid var(--line);flex:none;display:grid;place-items:center;background:#fff;transition:all .18s}
|
|
92
|
+
.b270-check svg{width:15px;height:15px;stroke:#fff;stroke-width:3;fill:none;stroke-linecap:round;stroke-linejoin:round;opacity:0}
|
|
93
|
+
.b270-reading.done.ot .b270-check{background:var(--teal);border-color:var(--teal)}
|
|
94
|
+
.b270-reading.done.nt .b270-check{background:var(--oxblood);border-color:var(--oxblood)}
|
|
95
|
+
.b270-reading.done.pp .b270-check{background:var(--gold);border-color:var(--gold)}
|
|
96
|
+
.b270-reading.done .b270-check svg{opacity:1}
|
|
97
|
+
.b270-rbody{flex:1;min-width:0}
|
|
98
|
+
.b270-cat{font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--faint);font-weight:600}
|
|
99
|
+
.b270-ref{font-family:"Spectral",serif;font-size:19px;font-weight:500;margin-top:1px}
|
|
100
|
+
.b270-ref a{text-decoration:none;border-bottom:1px solid transparent}
|
|
101
|
+
.b270-ref a:hover{border-bottom-color:var(--gold)}
|
|
102
|
+
.b270-reading.done .b270-ref{color:var(--muted)}
|
|
103
|
+
.b270-reading.rest .b270-ref{font-style:italic;font-size:16px;color:var(--faint)}
|
|
104
|
+
.b270-signin-hint{font-size:12px;color:var(--faint)}
|
|
105
|
+
|
|
106
|
+
/* completers */
|
|
107
|
+
.b270-completers{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:16px;font-size:13px;color:var(--muted)}
|
|
108
|
+
.b270-avatar{border-radius:50%;object-fit:cover;display:inline-block;text-align:center;background:var(--teal);color:#fff;font-size:12px;font-weight:600}
|
|
109
|
+
.b270-avatar-fallback{border-radius:50%}
|
|
110
|
+
|
|
111
|
+
/* comments */
|
|
112
|
+
.b270-section-h{font-family:"Spectral",serif;font-size:20px;font-weight:600;margin:34px 0 14px}
|
|
113
|
+
.b270-comment{display:flex;gap:12px;padding:14px 0;border-top:1px solid var(--line-soft)}
|
|
114
|
+
.b270-comment:first-child{border-top:none}
|
|
115
|
+
.b270-cmeta{font-size:13px;color:var(--muted)}
|
|
116
|
+
.b270-cmeta strong{color:var(--ink)}
|
|
117
|
+
.b270-cbody{margin:3px 0 0;white-space:pre-wrap}
|
|
118
|
+
.b270-cdel{font-size:12px;color:var(--oxblood);background:none;border:none;cursor:pointer;padding:0;margin-left:8px}
|
|
119
|
+
.b270-form textarea{width:100%;font:inherit;font-size:15px;border:1px solid var(--line);border-radius:10px;padding:11px 13px;background:#fff;resize:vertical;min-height:76px}
|
|
120
|
+
.b270-form .row{display:flex;align-items:center;gap:10px;margin-top:10px}
|
|
121
|
+
.b270-btn{border:none;border-radius:10px;padding:10px 16px;font:inherit;font-weight:600;font-size:14px;cursor:pointer;background:var(--ink);color:var(--paper);text-decoration:none;display:inline-block}
|
|
122
|
+
.b270-btn:hover{background:#3a352b}
|
|
123
|
+
|
|
124
|
+
/* community */
|
|
125
|
+
.b270-list{background:var(--card);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow);overflow:hidden}
|
|
126
|
+
.b270-li{display:flex;align-items:center;gap:14px;padding:14px 18px}
|
|
127
|
+
.b270-li+.b270-li{border-top:1px solid var(--line-soft)}
|
|
128
|
+
.b270-li .name{font-weight:600}
|
|
129
|
+
.b270-li .sub{font-size:12.5px;color:var(--faint)}
|
|
130
|
+
.b270-li .stat{margin-left:auto;text-align:right}
|
|
131
|
+
.b270-li .stat .big{font-family:"Spectral",serif;font-size:20px;font-weight:600}
|
|
132
|
+
.b270-li .rank{width:26px;color:var(--faint);font-variant-numeric:tabular-nums;text-align:center}
|
|
133
|
+
|
|
134
|
+
/* calendar grid */
|
|
135
|
+
.b270-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(30px,1fr));gap:6px;margin-top:16px}
|
|
136
|
+
.b270-cell{aspect-ratio:1;border-radius:6px;border:1px solid var(--line);background:var(--card);font-size:10.5px;color:var(--faint);display:grid;place-items:center;text-decoration:none;font-variant-numeric:tabular-nums}
|
|
137
|
+
.b270-cell:hover{border-color:var(--gold);color:var(--gold)}
|
|
138
|
+
.b270-cell.partial{background:linear-gradient(135deg,var(--card) 50%,rgba(165,129,44,.35) 50%);color:var(--muted)}
|
|
139
|
+
.b270-cell.complete{background:var(--teal);border-color:var(--teal);color:#eef3f2;font-weight:600}
|
|
140
|
+
.b270-cell.now{box-shadow:0 0 0 2px var(--gold)}
|
|
141
|
+
|
|
142
|
+
.b270-footer{margin-top:40px;padding-top:22px;border-top:1px solid var(--line-soft);color:var(--faint);font-size:13px}
|
|
143
|
+
.b270-footer p{margin:0;line-height:1.5}
|
|
144
|
+
details.b270-index summary{cursor:pointer;font-family:"Spectral",serif;font-size:18px;font-weight:600;list-style:none;margin-top:30px}
|
|
145
|
+
details.b270-index summary::-webkit-details-marker{display:none}
|
|
146
|
+
@media (max-width:480px){ .b270-tm{grid-template-columns:104px 1fr auto} .b270-reading{padding:14px 15px;gap:12px} }
|
|
147
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<body style="margin:0;padding:24px;background:#ece6da;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#231f18">
|
|
4
|
+
<div style="max-width:520px;margin:0 auto;background:#f4efe6;border:1px solid #d7cfbf;border-radius:14px;padding:28px">
|
|
5
|
+
<h1 style="margin:0 0 6px;font-family:Georgia,serif;font-size:22px;font-weight:600"><%= @app_name %></h1>
|
|
6
|
+
<p style="margin:0 0 20px;font-size:14px;color:#6d6558">
|
|
7
|
+
Click the button below to sign in. The link works once and expires in
|
|
8
|
+
<%= @expires_in_minutes %> minutes.
|
|
9
|
+
</p>
|
|
10
|
+
<p style="margin:0 0 22px">
|
|
11
|
+
<a href="<%= @url %>" style="display:inline-block;background:#231f18;color:#ece6da;text-decoration:none;padding:12px 20px;border-radius:10px;font-weight:600;font-size:15px">Sign in</a>
|
|
12
|
+
</p>
|
|
13
|
+
<p style="margin:0 0 6px;font-size:12px;color:#9a9082">Or paste this address into your browser:</p>
|
|
14
|
+
<p style="margin:0 0 20px;font-size:12px;word-break:break-all;color:#6d6558"><%= @url %></p>
|
|
15
|
+
<p style="margin:0;font-size:12px;color:#9a9082;border-top:1px solid #e2dbcd;padding-top:14px">
|
|
16
|
+
If you didn't ask to sign in, you can ignore this email — nothing has changed.
|
|
17
|
+
</p>
|
|
18
|
+
</div>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title><%= content_for?(:title) ? yield(:title) : Bible270.config.app_name %></title>
|
|
7
|
+
<%= csrf_meta_tags %>
|
|
8
|
+
<%= csp_meta_tag if respond_to?(:csp_meta_tag) %>
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,400;0,500;0,600;1,400&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
12
|
+
<%= render "bible270/shared/styles" %>
|
|
13
|
+
<%= yield :head %>
|
|
14
|
+
</head>
|
|
15
|
+
<body class="b270">
|
|
16
|
+
<div class="b270-wrap">
|
|
17
|
+
<%= render "bible270/shared/header" %>
|
|
18
|
+
<% if flash[:alert] %><div class="b270-flash alert"><%= flash[:alert] %></div><% end %>
|
|
19
|
+
<% if flash[:notice] %><div class="b270-flash notice"><%= flash[:notice] %></div><% end %>
|
|
20
|
+
<%= yield %>
|
|
21
|
+
<footer class="b270-footer">
|
|
22
|
+
<p>The Old Testament is read once, cover to cover. The New Testament is read twice —
|
|
23
|
+
the second pass begins on day <%= Bible270::Plan.nt_second_pass_start_day %>.
|
|
24
|
+
Psalms & Proverbs runs alongside twice over, whole chapters at a time.</p>
|
|
25
|
+
</footer>
|
|
26
|
+
</div>
|
|
27
|
+
</body>
|
|
28
|
+
</html>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
Bible270::Engine.routes.draw do
|
|
3
|
+
root to: "days#index"
|
|
4
|
+
|
|
5
|
+
get "day/:day", to: "days#show", as: :day, constraints: { day: /\d+/ }
|
|
6
|
+
|
|
7
|
+
post "day/:day/toggle/:track", to: "checkoffs#toggle", as: :toggle_checkoff,
|
|
8
|
+
constraints: { day: /\d+/ }
|
|
9
|
+
post "day/:day/comments", to: "comments#create", as: :day_comments,
|
|
10
|
+
constraints: { day: /\d+/ }
|
|
11
|
+
delete "comments/:id", to: "comments#destroy", as: :comment
|
|
12
|
+
|
|
13
|
+
get "community", to: "readers#index", as: :community
|
|
14
|
+
get "readers/:id", to: "readers#show", as: :reader
|
|
15
|
+
|
|
16
|
+
patch "start-date", to: "readers#update_start_date", as: :start_date
|
|
17
|
+
delete "start-date", to: "readers#clear_start_date", as: :clear_start_date
|
|
18
|
+
|
|
19
|
+
# Built-in OmniAuth sign-in. The request phase (POST <mount>/auth/:provider)
|
|
20
|
+
# is handled by the OmniAuth middleware in the host app, not by these routes.
|
|
21
|
+
get "sign_in", to: "sessions#new", as: :sign_in
|
|
22
|
+
|
|
23
|
+
# Email (magic link) sign-in, for readers without a social account.
|
|
24
|
+
post "sign_in/email", to: "sessions#email_link", as: :email_sign_in_link
|
|
25
|
+
get "sign_in/email/:token", to: "sessions#email_callback", as: :email_sign_in
|
|
26
|
+
get "auth/:provider/callback", to: "sessions#create"
|
|
27
|
+
post "auth/:provider/callback", to: "sessions#create"
|
|
28
|
+
get "auth/failure", to: "sessions#failure"
|
|
29
|
+
delete "sign_out", to: "sessions#destroy", as: :sign_out
|
|
30
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
class CreateBible270Readers < ActiveRecord::Migration[7.0]
|
|
3
|
+
def change
|
|
4
|
+
create_table :bible270_readers do |t|
|
|
5
|
+
t.string :display_name, null: false
|
|
6
|
+
t.string :email
|
|
7
|
+
t.string :avatar_url
|
|
8
|
+
t.string :provider
|
|
9
|
+
t.string :uid
|
|
10
|
+
t.references :owner, polymorphic: true, index: true
|
|
11
|
+
t.date :started_on
|
|
12
|
+
t.timestamps
|
|
13
|
+
end
|
|
14
|
+
add_index :bible270_readers, %i[provider uid], unique: true,
|
|
15
|
+
name: "idx_b270_readers_provider_uid"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
class CreateBible270Checkoffs < ActiveRecord::Migration[7.0]
|
|
3
|
+
def change
|
|
4
|
+
create_table :bible270_checkoffs do |t|
|
|
5
|
+
t.references :reader, null: false, index: true,
|
|
6
|
+
foreign_key: { to_table: :bible270_readers }
|
|
7
|
+
t.integer :day, null: false
|
|
8
|
+
t.string :track, null: false
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
add_index :bible270_checkoffs, %i[reader_id day track], unique: true,
|
|
12
|
+
name: "idx_b270_checkoffs_unique"
|
|
13
|
+
add_index :bible270_checkoffs, :day
|
|
14
|
+
end
|
|
15
|
+
end
|