generated from dxw/rails-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ae5a0a3
commit 209cb89
Showing
23 changed files
with
938 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# frozen_string_literal: true | ||
|
||
class SubscriptionsController < ApplicationController | ||
include Wicked::Wizard | ||
|
||
STEPS = %i[contact_details contact_verification zone_selection time_selection wrap_up confirmation] | ||
steps(*STEPS) | ||
|
||
def show | ||
session[:subscription_form] = nil if params[:reset] | ||
load_step | ||
|
||
# Validate against the previous step | ||
@form.current_step = previous_step | ||
jump_to(previous_step) if @step_number != 1 && @form.nil? && @form.invalid? | ||
|
||
# Switch back to the current step | ||
@form.current_step = step | ||
|
||
render_wizard | ||
end | ||
|
||
def update | ||
load_step | ||
case step | ||
when :contact_verification | ||
# Do nothing | ||
else | ||
@form.assign_attributes(subscription_params) | ||
end | ||
session[:subscription_form] = @form.attributes | ||
render_wizard @form | ||
end | ||
|
||
private | ||
|
||
def finish_wizard_path | ||
forecast_path notice: "Thank you for subscribing!" | ||
end | ||
|
||
def load_step | ||
@step_number = steps.index(step).to_i + 1 | ||
@current_step = step | ||
@first_step = (step == steps.first) | ||
@final_step = (step == steps.last) | ||
@form = SubscriptionForm.new(session[:subscription_form]) | ||
end | ||
|
||
def subscription_params | ||
params[:subscription_form].permit! # .permit(:email, :sms_number, :voice_number, :zone, :time) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import { Controller } from "@hotwired/stimulus"; | ||
import { useDebounce } from "stimulus-use"; | ||
import { geocoding } from "@maptiler/client"; | ||
import * as zones from "../zone_boundaries/zone-boundaries"; | ||
import booleanPointInPolygon from "@turf/boolean-point-in-polygon"; | ||
|
||
export default class SubscriptionController extends Controller { | ||
static targets = [ | ||
"email", | ||
"sms", | ||
"voice", | ||
"searchField", | ||
"searchResults", | ||
"tags", | ||
]; | ||
static debounces = ["search"]; | ||
|
||
connect() { | ||
useDebounce(this); | ||
} | ||
|
||
// Contact details | ||
toggleEmail() { | ||
this.emailTarget.classList.toggle("hidden"); | ||
} | ||
|
||
toggleSms() { | ||
this.smsTarget.classList.toggle("hidden"); | ||
} | ||
|
||
toggleVoice() { | ||
this.voiceTarget.classList.toggle("hidden"); | ||
} | ||
|
||
// Zone selection | ||
searchFieldTargetConnected() { | ||
this.maptilerApiKey = this.searchFieldTarget.dataset.maptilerApiKey; | ||
} | ||
|
||
async search() { | ||
const userInput = this.searchFieldTarget.value; | ||
const coordinates = await this.geoCode(userInput); | ||
const zones = this.findZones(coordinates); | ||
this.displaySearchResults(zones); | ||
} | ||
|
||
async geoCode(location) { | ||
if (!location) { | ||
return; | ||
} | ||
|
||
const result = await geocoding.forward(location, { | ||
apiKey: this.maptilerApiKey, | ||
country: ["GB"], | ||
proximity: [-0.116773, 51.510357], | ||
types: [ | ||
"region", | ||
"subregion", | ||
"county", | ||
"joint_municipality", | ||
"joint_submunicipality", | ||
"municipality", | ||
"municipal_district", | ||
"locality", | ||
"neighbourhood", | ||
"place", | ||
"postal_code", | ||
"address", | ||
"road", | ||
"poi", | ||
], | ||
}); | ||
|
||
return result?.features[0]?.geometry?.coordinates; | ||
} | ||
|
||
findZones(point) { | ||
if (!point) { | ||
return; | ||
} | ||
|
||
const matches = []; | ||
// Point has to be in the format [longitude, latitude] | ||
for (const [, zone] of Object.entries(zones)) { | ||
if (booleanPointInPolygon(point, zone)) { | ||
matches.push(zone); | ||
} | ||
} | ||
|
||
// Sort by zone level so more specific zones are returned first | ||
matches.sort((a, b) => b.properties.level - a.properties.level); | ||
|
||
return matches; | ||
} | ||
|
||
displaySearchResults(results) { | ||
this.searchResultsTarget.innerHTML = ""; | ||
this.searchResultsTarget.classList.remove("hidden"); | ||
|
||
if (!results || results.length === 0) { | ||
const li = document.createElement("li"); | ||
li.textContent = "No results found within the area covered by airTEXT"; | ||
this.searchResultsTarget.appendChild(li); | ||
return; | ||
} | ||
|
||
results.forEach((result) => { | ||
const li = document.createElement("li"); | ||
li.textContent = result.properties.name; | ||
li.dataset.action = "click->subscription#selectSearchResult"; | ||
this.searchResultsTarget.appendChild(li); | ||
}); | ||
} | ||
|
||
selectSearchResult(event) { | ||
const zoneName = event.target.textContent; | ||
this.addZoneTag(zoneName); | ||
|
||
// Remove from search results | ||
event.target.remove(); | ||
} | ||
|
||
tagsTargetConnected() { | ||
const checkboxes = document.querySelectorAll( | ||
"input[name='subscription_form[zones][]']" | ||
); | ||
checkboxes.forEach((checkbox) => { | ||
checkbox.addEventListener("change", (event) => | ||
this.checkboxChanged(event) | ||
); | ||
|
||
if (checkbox.checked) { | ||
this.addZoneTag(checkbox.value); | ||
} | ||
}); | ||
} | ||
|
||
checkboxChanged(event) { | ||
const zoneName = event.target.value; | ||
if (event.target.checked) { | ||
this.addZoneTag(zoneName); | ||
} else { | ||
this.removeZoneTag(zoneName); | ||
} | ||
} | ||
|
||
setCheckboxState(zone_name, state) { | ||
const checkbox = document.querySelector(`input[value="${zone_name}"]`); | ||
checkbox.checked = state; | ||
checkbox.dispatchEvent(new Event("change")); | ||
} | ||
|
||
tag(zoneName) { | ||
return this.tagsTarget.querySelector(`span[data-zone-name="${zoneName}"]`); | ||
} | ||
|
||
tagClicked(event) { | ||
const zoneName = event.target.dataset.zoneName; | ||
this.setCheckboxState(zoneName, false); | ||
} | ||
|
||
addZoneTag(zoneName) { | ||
if (this.tag(zoneName)) { | ||
return; | ||
} | ||
|
||
const tag = document.createElement("span"); | ||
tag.textContent = zoneName; | ||
tag.classList.add("tag"); | ||
tag.dataset.zoneName = zoneName; | ||
tag.dataset.action = "click->subscription#tagClicked"; | ||
this.tagsTarget.appendChild(tag); | ||
|
||
this.setCheckboxState(zoneName, true); | ||
} | ||
|
||
removeZoneTag(zoneName) { | ||
this.tag(zoneName).remove(); | ||
} | ||
} |
Oops, something went wrong.