mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
✨ Add proper backend integration of for new feedback form
This commit is contained in:
@@ -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>Email: </span>
|
||||||
<span>
|
<span>{{profile.email}}</span>
|
||||||
<span>Email: </span>
|
</span>
|
||||||
<span>{{profile.email}}</span>
|
<br />
|
||||||
</span>
|
<span>
|
||||||
<br />
|
<span>ID: </span>
|
||||||
|
<span><code>{{profile.id}}</code></span>
|
||||||
<span>
|
</span>
|
||||||
<span>ID: </span>
|
|
||||||
<span><code>{{profile.id}}</code></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>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[PENPOT FEEDBACK]: {{subject}}
|
[PENPOT FEEDBACK]: {{feedback-subject}}
|
||||||
|
|||||||
@@ -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}}
|
||||||
|
|||||||
@@ -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."
|
||||||
|
|||||||
@@ -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
|
||||||
;; LEGACY
|
(or (cf/get :user-feedback-destination)
|
||||||
(cf/get :feedback-destination))]
|
;; LEGACY
|
||||||
|
(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))
|
||||||
|
|||||||
@@ -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)))))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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*))
|
||||||
@@ -197,14 +197,13 @@
|
|||||||
:settings-subscription
|
:settings-subscription
|
||||||
: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*
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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]
|
|
||||||
{:subject ""
|
initial
|
||||||
:type (or type "")
|
(mf/with-memo [error-href error-report]
|
||||||
:content ""
|
(d/without-nils
|
||||||
:penpot-link url-error})
|
{:subject ""
|
||||||
form (fm/use-form :schema (schema:feedback-form url-error) :initial initial)
|
:type (d/nilv type "")
|
||||||
loading (mf/use-state false)
|
:content ""
|
||||||
report (wapi/create-blob report "text/plain")
|
:error-href error-href
|
||||||
report-uri (wapi/create-uri report)
|
: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
|
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,11 +92,12 @@
|
|||||||
(->> (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}
|
||||||
|
|
||||||
;; --- Feedback section
|
;; --- Feedback section
|
||||||
[:h2 {:class (stl/css :field-title :feedback-title)} (tr "feedback.title-contact-us")]
|
[:h2 {:class (stl/css :field-title :feedback-title)} (tr "feedback.title-contact-us")]
|
||||||
[:p {:class (stl/css :field-text :feedback-title)} (tr "feedback.subtitle")]
|
[:p {:class (stl/css :field-text :feedback-title)} (tr "feedback.subtitle")]
|
||||||
|
|
||||||
@@ -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}]]]))
|
|
||||||
|
|||||||
@@ -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]
|
||||||
@@ -352,16 +355,20 @@
|
|||||||
(mf/defc internal-error*
|
(mf/defc internal-error*
|
||||||
[{:keys [on-reset report] :as props}]
|
[{:keys [on-reset report] :as props}]
|
||||||
(let [report-uri (mf/use-ref nil)
|
(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
|
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* {}]
|
||||||
|
|||||||
Reference in New Issue
Block a user