Add proper backend integration of for new feedback form

This commit is contained in:
Andrey Antukh
2025-10-16 13:47:43 +02:00
parent 854ad5bb4d
commit fbbee98c3d
11 changed files with 143 additions and 91 deletions

View File

@@ -8,38 +8,41 @@
<body>
<p>
<strong>Feedback from:</strong><br />
{% if profile %}
<span>
<span>Name: </span>
<span><code>{{profile.fullname|abbreviate:25}}</code></span>
</span>
<br />
<span>
<span>Email: </span>
<span>{{profile.email}}</span>
</span>
<br />
<span>
<span>ID: </span>
<span><code>{{profile.id}}</code></span>
</span>
{% else %}
<span>
<span>Email: </span>
<span>{{profile.email}}</span>
</span>
{% endif %}
<span>
<span>Name: </span>
<span><code>{{profile.fullname|abbreviate:25}}</code></span>
</span>
<br />
<span>
<span>Email: </span>
<span>{{profile.email}}</span>
</span>
<br />
<span>
<span>ID: </span>
<span><code>{{profile.id}}</code></span>
</span>
</p>
<p>
<strong>Subject:</strong><br />
<span>{{subject|abbreviate:300}}</span>
<span>{{feedback-subject|abbreviate:300}}</span>
</p>
<p>
<strong>Type:</strong><br />
<span>{{feedback-type|abbreviate:300}}</span>
</p>
{% if feedback-error-href %}
<p>
<strong>Error HREF:</strong><br />
<span>{{feedback-error-href|abbreviate:500}}</span>
</p>
{% endif %}
<p>
<strong>Message:</strong><br />
{{content|linebreaks-br|safe}}
{{feedback-content|linebreaks-br}}
</p>
</body>
</html>

View File

@@ -1 +1 @@
[PENPOT FEEDBACK]: {{subject}}
[PENPOT FEEDBACK]: {{feedback-subject}}

View File

@@ -1,9 +1,10 @@
{% if profile %}
Feedback profile: {{profile.fullname}} <{{profile.email}}> / {{profile.id}}
{% else %}
Feedback from: {{email}}
{% endif %}
From: {{profile.fullname}} <{{profile.email}}> / {{profile.id}}
Subject: {{feedback-subject}}
Type: {{feedback-type}}
{%- if feedback-error-href %}
HREF: {{feedback-error-href}}
{% endif -%}
Subject: {{subject}}
Message:
{{content}}
{{feedback-content}}

View File

@@ -359,8 +359,10 @@
(def ^:private schema:feedback
[:map
[:subject ::sm/text]
[:content ::sm/text]])
[:feedback-subject ::sm/text]
[:feedback-type ::sm/text]
[:feedback-content ::sm/text]
[:profile :map]])
(def user-feedback
"A profile feedback email."

View File

@@ -7,6 +7,7 @@
(ns app.rpc.commands.feedback
"A general purpose feedback module."
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.schema :as sm]
[app.config :as cf]
@@ -21,8 +22,11 @@
(def ^:private schema:send-user-feedback
[:map {:title "send-user-feedback"}
[:subject [:string {:max 400}]]
[:content [:string {:max 2500}]]])
[:subject [:string {:max 500}]]
[:content [:string {:max 2500}]]
[:type {:optional true} :string]
[:error-href {:optional true} [:string {:max 2500}]]
[:error-report {:optional true} :string]])
(sv/defmethod ::send-user-feedback
{::doc/added "1.18"
@@ -39,16 +43,26 @@
(defn- send-user-feedback!
[pool profile params]
(let [dest (or (cf/get :user-feedback-destination)
;; LEGACY
(cf/get :feedback-destination))]
(let [destination
(or (cf/get :user-feedback-destination)
;; LEGACY
(cf/get :feedback-destination))
attachments
(d/without-nils
{"error-report.txt" (:error-report params)})]
(eml/send! {::eml/conn pool
::eml/factory eml/user-feedback
:from dest
:to dest
:profile profile
:from (cf/get :smtp-default-from)
:to destination
:reply-to (:email profile)
:email (:email profile)
:subject (:subject params)
:content (:content params)})
:attachments attachments
:feedback-subject (:subject params)
:feedback-type (:type params "not-specified")
:feedback-content (:content params)
:feedback-error-href (:error-href params)
:profile profile})
nil))

View File

@@ -22,4 +22,4 @@
(t/is (contains? result :body))
(t/is (contains? result :to))
#_(t/is (contains? result :reply-to))
(t/is (vector? (:body result)))))
(t/is (map? (:body result)))))

View File

@@ -25,6 +25,9 @@
;; From app.main.data.workspace we can use directly because it causes a circular dependency
(def reload-file nil)
;; Will contain the latest error report assigned
(def last-report nil)
(defn- print-data!
[data]
(-> data

View File

@@ -46,8 +46,8 @@
(def dashboard-page*
(mf/lazy-component app.main.ui.dashboard/dashboard*))
(def settings-page
(mf/lazy-component app.main.ui.settings/settings))
(def settings-page*
(mf/lazy-component app.main.ui.settings/settings*))
(def workspace-page*
(mf/lazy-component app.main.ui.workspace/workspace*))
@@ -197,14 +197,13 @@
:settings-subscription
:settings-access-tokens
:settings-notifications)
(let [params (get params :query)
type (some-> params :type)
report-id (some-> params :report-id)
url-error (some-> params :url-error)]
[:? [:& settings-page {:route route
:type type
:report-id report-id
:url-error url-error}]])
(let [params (get params :query)
error-report-id (some-> params :error-report-id uuid/parse*)]
[:? [:> settings-page*
{:route route
:type (get params :type)
:error-report-id error-report-id
:error-href (get params :error-href)}]])
:debug-icons-preview
(when *assert*

View File

@@ -16,7 +16,7 @@
[app.main.ui.settings.access-tokens :refer [access-tokens-page]]
[app.main.ui.settings.change-email]
[app.main.ui.settings.delete-account]
[app.main.ui.settings.feedback :refer [feedback-page]]
[app.main.ui.settings.feedback :refer [feedback-page*]]
[app.main.ui.settings.notifications :refer [notifications-page*]]
[app.main.ui.settings.options :refer [options-page]]
[app.main.ui.settings.password :refer [password-page]]
@@ -33,8 +33,8 @@
[:div {:class (stl/css :dashboard-title)}
[:h1 {:data-testid "account-title"} (tr "dashboard.your-account-title")]]])
(mf/defc settings
[{:keys [route type report-id url-error]}]
(mf/defc settings*
[{:keys [route type error-report-id error-href]}]
(let [section (get-in route [:data :name])
profile (mf/deref refs/profile)]
@@ -60,9 +60,9 @@
[:& profile-page]
:settings-feedback
[:& feedback-page {:type type
:report-id report-id
:url-error url-error}]
[:> feedback-page* {:type type
:error-report-id error-report-id
:error-href error-href}]
:settings-password
[:& password-page]

View File

@@ -8,45 +8,63 @@
"Feedback form."
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.schema :as sm]
[app.main.data.notifications :as ntf]
[app.main.errors :as errors]
[app.main.refs :as refs]
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.ui.components.forms :as fm]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.timers :as tm]
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
[rumext.v2 :as mf]))
(defn schema:feedback-form [url-error]
(def ^:private schema:feedback-form
[:map {:title "FeedbackForm"}
[:subject [::sm/text {:max 250}]]
[:type [:string {:max 250}]]
[:content [::sm/text {:max 5000}]]
[:penpot-link [::sm/text {:max 2048 :value (or url-error "") :optional true}]]])
[:error-report {:optional true} ::sm/text]
[:error-href {:optional true} [::sm/text {:max 2048}]]])
(mf/defc feedback-form
(mf/defc feedback-form*
{::mf/private true}
[{:keys [report type url-error]}]
(let [profile (mf/deref refs/profile)
initial (mf/with-memo [url-error]
{:subject ""
:type (or type "")
:content ""
:penpot-link url-error})
form (fm/use-form :schema (schema:feedback-form url-error) :initial initial)
loading (mf/use-state false)
report (wapi/create-blob report "text/plain")
report-uri (wapi/create-uri report)
[{:keys [error-report type error-href]}]
(let [profile (mf/deref refs/profile)
initial
(mf/with-memo [error-href error-report]
(d/without-nils
{:subject ""
:type (d/nilv type "")
:content ""
:error-href error-href
:error-report error-report}))
form
(fm/use-form :schema schema:feedback-form
:initial initial)
loading
(mf/use-state false)
report
(mf/with-memo [error-report]
(wapi/create-blob error-report "text/plain"))
on-download
(mf/use-fn
(mf/deps report)
(fn [event]
(dom/prevent-default event)
(dom/trigger-download-uri "report" "text/plain" report-uri)))
(let [uri (wapi/create-uri report)]
(dom/trigger-download-uri "report" "text/plain" uri)
(tm/schedule-on-idle #(wapi/revoke-uri uri)))))
on-succes
(mf/use-fn
@@ -74,11 +92,12 @@
(->> (rp/cmd! :send-user-feedback data)
(rx/subs! on-succes on-error)))))]
[:& fm/form {:class (stl/css :feedback-form)
:on-submit on-submit
:form form}
;; --- Feedback section
;; --- Feedback section
[:h2 {:class (stl/css :field-title :feedback-title)} (tr "feedback.title-contact-us")]
[:p {:class (stl/css :field-text :feedback-title)} (tr "feedback.subtitle")]
@@ -106,7 +125,7 @@
[:div {:class (stl/css :fields-row)}
[:p {:class (stl/css :field-text)} (tr "feedback.penpot.link")]
[:& fm/input {:label ""
:name :penpot-link
:name :error-href
:placeholder "https://penpot.app/"
:show-success? true}]
@@ -136,13 +155,15 @@
(tr "feedback.twitter-title")]
[:p {:class (stl/css :field-text)} (tr "feedback.twitter-subtitle1")]]))
(mf/defc feedback-page
[{:keys [type report-id url-error]}]
(mf/defc feedback-page*
[{:keys [error-report-id] :as props}]
(mf/with-effect []
(dom/set-html-title (tr "title.settings.feedback")))
(let [report (.getItem js/localStorage report-id)]
(let [report (when (= error-report-id (:id errors/last-report))
(:content errors/last-report))
props (mf/spread-props props {:error-report report})]
[:div {:class (stl/css :dashboard-settings)}
[:div {:class (stl/css :form-container)}
[:& feedback-form {:report report
:type type
:url-error url-error}]]]))
[:> feedback-form* props]]]))

View File

@@ -11,9 +11,11 @@
[app.common.data :as d]
[app.common.pprint :as pp]
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.main.data.auth :refer [is-authenticated?]]
[app.main.data.common :as dcm]
[app.main.data.event :as ev]
[app.main.errors :as errors]
[app.main.refs :as refs]
[app.main.repo :as rp]
[app.main.router :as rt]
@@ -29,6 +31,7 @@
[app.main.ui.viewer.header :as viewer.header]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.timers :as tm]
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
@@ -352,16 +355,20 @@
(mf/defc internal-error*
[{:keys [on-reset report] :as props}]
(let [report-uri (mf/use-ref nil)
on-reset (or on-reset #(st/emit! (rt/assign-exception nil)))
on-reset (or on-reset #(st/emit! (rt/assign-exception nil)))
support-contact-click
(mf/use-fn
(mf/deps on-reset report)
(fn []
(let [report-id (str "report-" (random-uuid))]
(.setItem js/localStorage report-id report)
(st/emit! (rt/nav :settings-feedback {:type "issue"
:report-id report-id
:url-error (rt/get-current-href)})))))
(tm/schedule on-reset)
(let [error-report-id (uuid/next)
error-href (rt/get-current-href)]
(set! errors/last-report {:id error-report-id :content report})
(st/emit!
(rt/nav :settings-feedback {:type "issue"
:error-report-id error-report-id
:error-href error-href})))))
on-download
(mf/use-fn
@@ -372,6 +379,7 @@
(mf/with-effect [report]
(when (some? report)
(set! errors/last-report report)
(let [report (wapi/create-blob report "text/plain")
uri (wapi/create-uri report)]
(mf/set-ref-val! report-uri uri)
@@ -444,6 +452,7 @@
:path (get route :path)
:report report
:params params}))))
(case type
:not-found
[:> not-found* {}]