Skip to content

Commit

Permalink
Création d’un composant de feedback (#3428)
Browse files Browse the repository at this point in the history
* simple view of feedback component

* JS to display full form

* move to liveview

* start making a real form

* wip

* move to gtfs stops for no log noise

* contact form moved to a simple view

* change dummy email sender so it has the same return format as mailjet

* use form helpers

* working form

* I18n

* Check it works inside a liveview

* Add tests

* mix format

* insert feedback template on validators

* better mail output

* fix scss margin top

* mix format

* fix test

* guard clause and trimming

* replace get_env by fetch_env

* espaces avant double signe de ponctuation

Co-authored-by: Antoine Augusti <[email protected]>

* reindent scss

* fix test

* move to module attributes

* change sender of feedback form

* space before :

* remove double honeypot (mistake)

* remove params redirect

* sanitize inputs function

* french localization

* straight apostrophe in English

* improve tests

* wrap contact tests in describe block

* remove useless case

* Use swoosh for feedback component

* Apply suggestions from code review

Co-authored-by: Antoine Augusti <[email protected]>

* Pass current user email to feedback

* use Kernel.get_in

* use maps in sanitize inputs

* mix format

* start working on liveview

* duplicate feedback to liveview

* add test for live feedback

* Localisation

* migrate feedback view to feedback live

* remove feedback view / controller

* update locales

* revert to previous livecontrollers

* revert router

* add inclusion tests for feedback

* remove test

* verify on exit

* Apply suggestions from code review

Co-authored-by: Antoine Augusti <[email protected]>

* fix tests

* setup locale in component

* use notification class

---------

Co-authored-by: Thibaut Barrère <[email protected]>
Co-authored-by: Antoine Augusti <[email protected]>
  • Loading branch information
3 people authored Sep 20, 2023
1 parent 30b13cd commit f130873
Show file tree
Hide file tree
Showing 17 changed files with 440 additions and 11 deletions.
1 change: 1 addition & 0 deletions apps/transport/client/stylesheets/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
@import 'components/stats';
@import 'components/tooltip';
@import 'components/validation';
@import 'components/feedback';

// States: microinteractions...
@import 'states';
Expand Down
40 changes: 40 additions & 0 deletions apps/transport/client/stylesheets/components/_feedback.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.feedback-selector {
input {
display: none;
}

label {
cursor: pointer;
display: inline-block;
margin-right: 12px;
filter: brightness(1.8) grayscale(1) opacity(0.7);
}

label:hover {
filter: brightness(1.2) grayscale(0.5) opacity(0.9);
}

input:checked + label {
filter: none;
color: $primary;
}

.feedback-emojis {
font-size: 48px;
}
}

.feedback-form {
#full-feedback-form.hidden {
display: none;
}

.form__group + #full-feedback-form {
margin-top: var(--space-l);
}

.form-special-field {
// hide field used as honey-pot
display: none;
}
}
5 changes: 3 additions & 2 deletions apps/transport/lib/mailjet/email_sender.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ defmodule Transport.EmailSender.Dummy do
A development-time implementation which just happens to log to console
"""

def send_mail(_from_name, from_email, to_email, _reply_to, subject, _text_body, _html_body) do
Logger.info("Would send email: from #{from_email} to #{to_email}, subject '#{subject}'")
def send_mail(_from_name, from_email, to_email, _reply_to, subject, text_body, _html_body) do
Logger.info("Would send email: from #{from_email} to #{to_email}, subject '#{subject}'\n#{text_body}")
{:ok, "dummy email sent"}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ defmodule TransportWeb.ContactController do
end

def send_mail(conn, %{"email" => email, "topic" => subject, "question" => question} = params) do
%{email: email, subject: subject, question: question} =
sanitize_inputs(%{email: email, subject: subject, question: question})

contact_email = TransportWeb.ContactEmail.contact(email, subject, question)

case Transport.Mailer.deliver(contact_email) do
Expand All @@ -30,12 +33,14 @@ defmodule TransportWeb.ContactController do
end

def send_mail(conn, params) do
Logger.error("Bad parameters for sending email #{params}")
Logger.error("Bad parameters for sending email #{inspect(params)}")

conn
|> put_flash(:error, gettext("There has been an error, try again later"))
|> redirect(to: params["redirect_path"] || page_path(conn, :index))
end

defp sanitize_inputs(map), do: Map.new(map, fn {k, v} -> {k, v |> String.trim() |> HtmlSanitizeEx.strip_tags()} end)
end

defmodule TransportWeb.ContactEmail do
Expand All @@ -49,4 +54,31 @@ defmodule TransportWeb.ContactEmail do
|> subject(subject)
|> text_body(question)
end

def feedback(rating, explanation, email, feature) do
rating_t = %{"like" => "j’aime", "neutral" => "neutre", "dislike" => "mécontent"}

reply_email =
if email == "" do
Application.fetch_env!(:transport, :contact_email)
else
email
end

feedback_content = """
Vous avez un nouvel avis sur le PAN.
Fonctionnalité : #{feature}
Notation : #{rating_t[rating]}
Adresse e-mail : #{email}
Explication : #{explanation}
"""

new()
|> from({"Formulaire feedback", Application.fetch_env!(:transport, :contact_email)})
|> to(Application.fetch_env!(:transport, :contact_email))
|> reply_to(reply_email)
|> subject("Nouvel avis pour #{feature} : #{rating_t[rating]}")
|> text_body(feedback_content)
end
end
67 changes: 67 additions & 0 deletions apps/transport/lib/transport_web/live/feedback_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
defmodule TransportWeb.Live.FeedbackLive do
use Phoenix.LiveView
use TransportWeb.InputHelpers
import TransportWeb.InputHelpers
import TransportWeb.Gettext
require Logger

@moduledoc """
A reusable module to display a feedback form for a given feature, you can display inside a normal view or a live view.
In case of normal view, don’t forget to add the app.js script with LiveView js inside the page as if it is not included in the general layout.
If you add feedback for a new feature, add it to the list of features.
"""

@feedback_rating_values ["like", "neutral", "dislike"]
@feedback_features ["gtfs-stops", "on-demand-validation", "gbfs-validation"]

def mount(_params, %{"feature" => feature, "locale" => locale} = session, socket)
when feature in @feedback_features do
current_email = session |> get_in(["current_user", "email"])

socket =
socket
|> assign(
feature: feature,
current_email: current_email,
feedback_sent: false,
feedback_error: false
)

Gettext.put_locale(locale)

{:ok, socket}
end

def handle_event("submit", %{"feedback" => %{"name" => name, "email" => email}}, socket) when name !== "" do
# someone filled the honeypot field ("name") => discard as spam
Logger.info("Feedback coming from #{email} has been discarded because it filled the feedback form honeypot")
# spammer get a little fox emoji in their flash message, useful for testing purpose
{:noreply, socket |> assign(:feedback_sent, true)}
end

def handle_event(
"submit",
%{"feedback" => %{"rating" => rating, "explanation" => explanation, "email" => email, "feature" => feature}},
socket
)
when rating in @feedback_rating_values and feature in @feedback_features do
%{email: email, explanation: explanation} = sanitize_inputs(%{email: email, explanation: explanation})

feedback_email = TransportWeb.ContactEmail.feedback(rating, explanation, email, feature)

case Transport.Mailer.deliver(feedback_email) do
{:ok, _} ->
{:noreply, socket |> assign(:feedback_sent, true)}

{:error, _} ->
{:noreply, socket |> assign(:feedback_error, true)}
end
end

def handle_event("submit", session, socket) do
Logger.error("Bad parameters for feedback #{inspect(session)}")
{:noreply, socket |> assign(:feedback_error, true)}
end

defp sanitize_inputs(map), do: Map.new(map, fn {k, v} -> {k, v |> String.trim() |> HtmlSanitizeEx.strip_tags()} end)
end
59 changes: 59 additions & 0 deletions apps/transport/lib/transport_web/live/feedback_live.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<div class="container">
<h2><%= dgettext("feedback", "Leave your feedback") %></h2>
<.form :let={f} :if={!@feedback_sent} for={%{}} as={:feedback} phx-submit="submit" class="feedback-form no-margin">
<div class="form__group feedback-selector">
<fieldset>
<legend><%= dgettext("feedback", "What's your feedback on this page?") %></legend>

<%= radio_button(f, :rating, "like", id: "like") %>
<%= label f, "like", class: "label-inline", for: "like" do %>
<i class="fa-regular fa-face-smile feedback-emojis"></i>
<% end %>
<%= radio_button(f, :rating, "neutral", id: "neutral") %>
<%= label f, "neutral", class: "label-inline", for: "neutral" do %>
<i class="fa-regular fa-face-meh feedback-emojis"></i>
<% end %>
<%= radio_button(f, :rating, "dislike", id: "dislike") %>
<%= label f, "like", class: "label-inline", for: "dislike" do %>
<i class="fa-regular fa-face-frown feedback-emojis"></i>
<% end %>
</fieldset>
</div>

<div id="full-feedback-form" class="hidden">
<div class="form__group">
<%= label(f, :explanation, dgettext("feedback", "Why?"), class: "required") %>
<%= textarea(f, :explanation, required: true) %>
</div>

<div class="form__group">
<%= label(f, :email, dgettext("feedback", "Your email (optional)")) %>
<%= text_input(f, :email, type: "email", value: @current_email) %>
</div>

<%= text_input(f, :name,
placeholder: "your name",
class: "form-special-field",
tabindex: "-1",
autocomplete: "off"
) %>

<%= hidden_input(f, :feature, value: @feature) %>

<%= submit(dgettext("feedback", "Send the feedback"), class: "button") %>
</div>
</.form>

<p :if={@feedback_sent} class="notification"><%= dgettext("feedback", "Thanks for your feedback!") %></p>
<p :if={@feedback_error}><%= gettext("There has been an error, try again later") %></p>

<script>
document.querySelectorAll(".feedback-selector label").forEach(
function(el) {
el.addEventListener('click', function() {
document.getElementById("full-feedback-form").classList.remove("hidden");
});
}
);
</script>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,11 @@
</div>
</div>
</section>
<section class="section section-white">
<%= live_render(@socket, TransportWeb.Live.FeedbackLive,
id: "feedback-form",
session: %{"feature" => "on-demand-validation"}
) %>
</section>
<script defer type="text/javascript" src={static_path(@socket, "/js/app.js")}>
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,12 @@
</div>
</div>
</section>

<section class="section section-white">
<%= live_render(@socket, TransportWeb.Live.FeedbackLive,
id: "feedback-form",
session: %{"feature" => "on-demand-validation"}
) %>
</section>

<script defer type="text/javascript" src={static_path(@socket, "/js/app.js")} />
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,12 @@

<div id="map" class="explore_map"></div>

<section class="section section-white">
<%= live_render(@conn, TransportWeb.Live.FeedbackLive, session: %{"feature" => "gtfs-stops"}) %>
</section>

<script src={static_path(@conn, "/js/gtfs.js")}>
</script>

<script defer type="text/javascript" src={static_path(@conn, "/js/app.js")}>
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,16 @@
</div>
</section>

<section class="section section-white">
<%= live_render(@conn, TransportWeb.Live.FeedbackLive, session: %{"feature" => "gbfs-validation"}) %>
</section>

<script src={static_path(@conn, "/js/resourceviz.js")} />
<script>
document.addEventListener("DOMContentLoaded", function() {
createMap('map', "<%= @gbfs_url %>", "gbfs", "<%= get_session(@conn, :locale) %>")
})
</script>

<script defer type="text/javascript" src={static_path(@conn, "/js/app.js")}>
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,10 @@
</div>
</div>
</section>

<section class="section section-white">
<%= live_render(@conn, TransportWeb.Live.FeedbackLive, session: %{"feature" => "on-demand-validation"}) %>
</section>

<script defer type="text/javascript" src={static_path(@conn, "/js/app.js")}>
</script>
36 changes: 36 additions & 0 deletions apps/transport/priv/gettext/en/LC_MESSAGES/feedback.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#, elixir-autogen, elixir-format
msgid "Send the feedback"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Why?"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Your email (optional)"
msgstr ""

#, elixir-autogen, elixir-format
msgid "What's your feedback on this page?"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Leave your feedback"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Thanks for your feedback!"
msgstr ""
36 changes: 36 additions & 0 deletions apps/transport/priv/gettext/feedback.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## This file is a PO Template file.
##
## "msgid"s here are often extracted from source code.
## Add new messages manually only if they're dynamic
## messages that can't be statically extracted.
##
## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead.
#
msgid ""
msgstr ""

#, elixir-autogen, elixir-format
msgid "Send the feedback"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Why?"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Your email (optional)"
msgstr ""

#, elixir-autogen, elixir-format
msgid "What's your feedback on this page?"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Leave your feedback"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Thanks for your feedback!"
msgstr ""
Loading

0 comments on commit f130873

Please sign in to comment.