diff --git a/backend/resources/app/email/feedback/en.html b/backend/resources/app/email/feedback/en.html index 6de9cda624..742d4c3f66 100644 --- a/backend/resources/app/email/feedback/en.html +++ b/backend/resources/app/email/feedback/en.html @@ -8,38 +8,41 @@

Feedback from:
- {% if profile %} - - Name: - {{profile.fullname|abbreviate:25}} - -
- - - Email: - {{profile.email}} - -
- - - ID: - {{profile.id}} - - {% else %} - - Email: - {{profile.email}} - - {% endif %} + + Name: + {{profile.fullname|abbreviate:25}} + +
+ + Email: + {{profile.email}} + +
+ + ID: + {{profile.id}} +

Subject:
- {{subject|abbreviate:300}} + {{feedback-subject|abbreviate:300}}

+

+ Type:
+ {{feedback-type|abbreviate:300}} +

+ + {% if feedback-error-href %} +

+ Error HREF:
+ {{feedback-error-href|abbreviate:500}} +

+ {% endif %} +

Message:
- {{content|linebreaks-br|safe}} + {{feedback-content|linebreaks-br}}

diff --git a/backend/resources/app/email/feedback/en.subj b/backend/resources/app/email/feedback/en.subj index aa226f6df0..82bacc6648 100644 --- a/backend/resources/app/email/feedback/en.subj +++ b/backend/resources/app/email/feedback/en.subj @@ -1 +1 @@ -[PENPOT FEEDBACK]: {{subject}} +[PENPOT FEEDBACK]: {{feedback-subject}} diff --git a/backend/resources/app/email/feedback/en.txt b/backend/resources/app/email/feedback/en.txt index a60d380c8e..76a42e42cf 100644 --- a/backend/resources/app/email/feedback/en.txt +++ b/backend/resources/app/email/feedback/en.txt @@ -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}} diff --git a/backend/src/app/email.clj b/backend/src/app/email.clj index f21d88a935..91563b4d95 100644 --- a/backend/src/app/email.clj +++ b/backend/src/app/email.clj @@ -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." diff --git a/backend/src/app/rpc/commands/feedback.clj b/backend/src/app/rpc/commands/feedback.clj index e3525ded47..fea115d858 100644 --- a/backend/src/app/rpc/commands/feedback.clj +++ b/backend/src/app/rpc/commands/feedback.clj @@ -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)) diff --git a/backend/test/backend_tests/email_sending_test.clj b/backend/test/backend_tests/email_sending_test.clj index a61825ae49..a6f66c7b3c 100644 --- a/backend/test/backend_tests/email_sending_test.clj +++ b/backend/test/backend_tests/email_sending_test.clj @@ -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))))) diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index 4f2b6009c8..ed8462058f 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -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 diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index b2eb1b065c..bdb595cf2c 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -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* diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index 28488e4578..5935620068 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -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] diff --git a/frontend/src/app/main/ui/settings/feedback.cljs b/frontend/src/app/main/ui/settings/feedback.cljs index ae49270016..de25cde1e1 100644 --- a/frontend/src/app/main/ui/settings/feedback.cljs +++ b/frontend/src/app/main/ui/settings/feedback.cljs @@ -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]]])) diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index 3f18836686..f56688b7ef 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -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* {}]