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> <body>
<p> <p>
<strong>Feedback from:</strong><br /> <strong>Feedback from:</strong><br />
{% if profile %}
<span> <span>
<span>Name: </span> <span>Name: </span>
<span><code>{{profile.fullname|abbreviate:25}}</code></span> <span><code>{{profile.fullname|abbreviate:25}}</code></span>
</span> </span>
<br /> <br />
<span> <span>
<span>Email: </span> <span>Email: </span>
<span>{{profile.email}}</span> <span>{{profile.email}}</span>
</span> </span>
<br /> <br />
<span> <span>
<span>ID: </span> <span>ID: </span>
<span><code>{{profile.id}}</code></span> <span><code>{{profile.id}}</code></span>
</span> </span>
{% else %}
<span>
<span>Email: </span>
<span>{{profile.email}}</span>
</span>
{% endif %}
</p> </p>
<p> <p>
<strong>Subject:</strong><br /> <strong>Subject:</strong><br />
<span>{{subject|abbreviate:300}}</span> <span>{{feedback-subject|abbreviate:300}}</span>
</p> </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> <p>
<strong>Message:</strong><br /> <strong>Message:</strong><br />
{{content|linebreaks-br|safe}} {{feedback-content|linebreaks-br}}
</p> </p>
</body> </body>
</html> </html>

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,4 +22,4 @@
(t/is (contains? result :body)) (t/is (contains? result :body))
(t/is (contains? result :to)) (t/is (contains? result :to))
#_(t/is (contains? result :reply-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 ;; From app.main.data.workspace we can use directly because it causes a circular dependency
(def reload-file nil) (def reload-file nil)
;; Will contain the latest error report assigned
(def last-report nil)
(defn- print-data! (defn- print-data!
[data] [data]
(-> data (-> data

View File

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

View File

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

View File

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

View File

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