From 854ad5bb4dbbf9bc992bb3bd148a48d6f5c210f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20L=C3=B3pez?= Date: Fri, 29 Aug 2025 14:07:55 +0200 Subject: [PATCH] :sparkles: Improve the way users give us feedback --- frontend/src/app/main/ui.cljs | 9 +- frontend/src/app/main/ui/settings.cljs | 6 +- .../src/app/main/ui/settings/feedback.cljs | 95 +++++++++++++------ .../src/app/main/ui/settings/feedback.scss | 66 +++++++++++-- .../src/app/main/ui/settings/profile.scss | 2 +- .../src/app/main/ui/settings/sidebar.cljs | 2 +- frontend/src/app/main/ui/static.cljs | 32 ++++++- frontend/src/app/main/ui/static.scss | 27 +++++- frontend/translations/en.po | 49 +++++++--- frontend/translations/es.po | 49 +++++++--- 10 files changed, 261 insertions(+), 76 deletions(-) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 16a882f518..b2eb1b065c 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -197,7 +197,14 @@ :settings-subscription :settings-access-tokens :settings-notifications) - [:? [:& settings-page {:route route}]] + (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}]]) :debug-icons-preview (when *assert* diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index 36cd8da7ba..28488e4578 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -34,7 +34,7 @@ [:h1 {:data-testid "account-title"} (tr "dashboard.your-account-title")]]]) (mf/defc settings - [{:keys [route] :as props}] + [{:keys [route type report-id url-error]}] (let [section (get-in route [:data :name]) profile (mf/deref refs/profile)] @@ -60,7 +60,9 @@ [:& profile-page] :settings-feedback - [:& feedback-page] + [:& feedback-page {:type type + :report-id report-id + :url-error url-error}] :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 4555e2a57c..ae49270016 100644 --- a/frontend/src/app/main/ui/settings/feedback.cljs +++ b/frontend/src/app/main/ui/settings/feedback.cljs @@ -16,20 +16,37 @@ [app.main.ui.components.forms :as fm] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] + [app.util.webapi :as wapi] [beicon.v2.core :as rx] [rumext.v2 :as mf])) -(def ^:private schema:feedback-form +(defn schema:feedback-form [url-error] [:map {:title "FeedbackForm"} [:subject [::sm/text {:max 250}]] - [:content [::sm/text {:max 5000}]]]) + [:type [:string {:max 250}]] + [:content [::sm/text {:max 5000}]] + [:penpot-link [::sm/text {:max 2048 :value (or url-error "") :optional true}]]]) (mf/defc feedback-form {::mf/private true} - [] - (let [profile (mf/deref refs/profile) - form (fm/use-form :schema schema:feedback-form) - loading (mf/use-state false) + [{: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) + + on-download + (mf/use-fn + (mf/deps report) + (fn [event] + (dom/prevent-default event) + (dom/trigger-download-uri "report" "text/plain" report-uri))) on-succes (mf/use-fn @@ -62,19 +79,41 @@ :form form} ;; --- Feedback section - [:h2 {:class (stl/css :field-title)} (tr "feedback.title")] - [:p {:class (stl/css :field-text)} (tr "feedback.subtitle")] + [:h2 {:class (stl/css :field-title :feedback-title)} (tr "feedback.title-contact-us")] + [:p {:class (stl/css :field-text :feedback-title)} (tr "feedback.subtitle")] [:div {:class (stl/css :fields-row)} [:& fm/input {:label (tr "feedback.subject") :name :subject :show-success? true}]] + + [:div {:class (stl/css :fields-row)} + [:label {:class (stl/css :field-label)} (tr "feedback.type")] + [:& fm/select {:label (tr "feedback.type") + :name :type + :options [{:label (tr "feedback.type.idea") :value "idea"} + {:label (tr "feedback.type.issue") :value "issue"} + {:label (tr "feedback.type.doubt") :value "doubt"}]}]] + [:div {:class (stl/css :fields-row :description)} [:& fm/textarea - {:label (tr "feedback.description") + {:class (stl/css :feedback-description) + :label (tr "feedback.description") :name :content + :placeholder (tr "feedback.description-placeholder") :rows 5}]] + [:div {:class (stl/css :fields-row)} + [:p {:class (stl/css :field-text)} (tr "feedback.penpot.link")] + [:& fm/input {:label "" + :name :penpot-link + :placeholder "https://penpot.app/" + :show-success? true}] + + (when report + [:a {:class (stl/css :link :download-button) :on-click on-download} + (tr "labels.download" "report.txt")])] + [:> fm/submit-button* {:label (if @loading (tr "labels.sending") (tr "labels.send")) :class (stl/css :feedback-button-link) @@ -82,30 +121,28 @@ [:hr] - [:h2 {:class (stl/css :field-title)} (tr "feedback.discourse-title")] - [:p {:class (stl/css :field-text)} (tr "feedback.discourse-subtitle1")] + [:h2 {:class (stl/css :feedback-title)} (tr "feedback.other-ways-contact")] - [:a - {:class (stl/css :feedback-button-link) - :href "https://community.penpot.app" - :target "_blank"} - (tr "feedback.discourse-go-to")] - [:hr] - [:h2 {:class (stl/css :field-title)} (tr "feedback.twitter-title")] - [:p {:class (stl/css :field-text)} (tr "feedback.twitter-subtitle1")] + [:a {:class (stl/css :link) + :href "https://community.penpot.app" + :target "_blank"} + (tr "feedback.discourse-title")] + [:p {:class (stl/css :field-text :bottom-margin)} (tr "feedback.discourse-subtitle1")] - [:a - {:class (stl/css :feedback-button-link) - :href "https://twitter.com/penpotapp" - :target "_blank"} - (tr "feedback.twitter-go-to")]])) + [:a {:class (stl/css :link) + :href "https://x.com/penpotapp" + :target "_blank"} + (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/with-effect [] (dom/set-html-title (tr "title.settings.feedback"))) - - [:div {:class (stl/css :dashboard-settings)} - [:div {:class (stl/css :form-container)} - [:& feedback-form]]]) + (let [report (.getItem js/localStorage report-id)] + [:div {:class (stl/css :dashboard-settings)} + [:div {:class (stl/css :form-container)} + [:& feedback-form {:report report + :type type + :url-error url-error}]]])) diff --git a/frontend/src/app/main/ui/settings/feedback.scss b/frontend/src/app/main/ui/settings/feedback.scss index b319db4ebf..ee91182b80 100644 --- a/frontend/src/app/main/ui/settings/feedback.scss +++ b/frontend/src/app/main/ui/settings/feedback.scss @@ -6,24 +6,74 @@ @use "common/refactor/common-refactor" as *; @use "./profile"; +@use "../ds/typography.scss" as t; +@use "../ds/_borders.scss" as b; +@use "../ds/_sizes.scss" as *; +@use "../ds/_utils.scss" as *; +@use "../ds/spacing.scss" as *; .feedback-form { - textarea { - border-radius: $br-8; - padding: $br-12; + .fields-row { + margin-block-end: $sz-32; + } + + .feedback-description { + @include t.use-typography("body-medium"); + border-radius: b.$br-8; + padding: var(--sp-m); background-color: var(--color-background-tertiary); color: var(--color-foreground-primary); border: none; ::placeholder { - color: var(--color-background-disabled); + color: var(--input-placeholder-color); } &:focus { - outline: $s-1 solid var(--color-accent-primary); + outline: b.$b-1 solid var(--color-accent-primary); } } -} -.feedback-button-link { - @extend .button-primary; + .field-label { + @include t.use-typography("headline-small"); + block-size: $sz-32; + color: var(--color-foreground-primary); + margin-block-end: var(--sp-l); + } + + .feedback-button-link { + @extend .button-primary; + margin-block-end: px2rem(72); + } + + .feedback-title { + margin-block-end: var(--sp-xxxl); + } + + .field-text { + @include t.use-typography("body-medium"); + } + + .bottom-margin { + margin-block-end: var(--sp-xxxl); + } + + .link { + @include t.use-typography("headline-small"); + color: var(--color-accent-tertiary); + margin-block-end: var(--sp-s); + } + + .download-button { + @include t.use-typography("body-small"); + color: var(--color-foreground-primary); + text-transform: lowercase; + border: b.$b-1 solid var(--color-background-quaternary); + margin-block-start: var(--sp-s); + padding: var(--sp-s); + border-radius: b.$br-8; + block-size: $sz-32; + display: inline-block; + min-inline-size: $sz-160; + text-align: center; + } } diff --git a/frontend/src/app/main/ui/settings/profile.scss b/frontend/src/app/main/ui/settings/profile.scss index b08da057ca..4e8473b6e8 100644 --- a/frontend/src/app/main/ui/settings/profile.scss +++ b/frontend/src/app/main/ui/settings/profile.scss @@ -11,7 +11,7 @@ width: 100%; justify-content: center; align-items: center; - a:not(.button-primary) { + a:not(.button-primary):not(.link) { color: var(--color-foreground-secondary); } } diff --git a/frontend/src/app/main/ui/settings/sidebar.cljs b/frontend/src/app/main/ui/settings/sidebar.cljs index 5ebec1e4bc..0808e2299d 100644 --- a/frontend/src/app/main/ui/settings/sidebar.cljs +++ b/frontend/src/app/main/ui/settings/sidebar.cljs @@ -133,7 +133,7 @@ :settings-item true) :on-click go-settings-feedback} feedback-icon - [:span {:class (stl/css :element-title)} (tr "labels.give-feedback")]])]]])) + [:span {:class (stl/css :element-title)} (tr "labels.contact-us")]])]]])) (mf/defc sidebar {::mf/wrap [mf/memo] diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index 2f6da79171..3f18836686 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -22,6 +22,7 @@ [app.main.ui.auth.recovery-request :refer [recovery-request-page recovery-sent-page]] [app.main.ui.auth.register :as register] [app.main.ui.dashboard.sidebar :refer [sidebar*]] + [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]] [app.main.ui.icons :as deprecated-icon] @@ -351,7 +352,16 @@ (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 + (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)}))))) on-download (mf/use-fn @@ -370,11 +380,23 @@ [:> error-container* {} [:div {:class (stl/css :main-message)} (tr "labels.internal-error.main-message")] - [:div {:class (stl/css :desc-message)} (tr "labels.internal-error.desc-message")] + + [:div {:class (stl/css :desc-message)} + [:p {:class (stl/css :desc-text)} (tr "labels.internal-error.desc-message-first")] + [:p {:class (stl/css :desc-text)} (tr "labels.internal-error.desc-message-second")]] + (when (some? report) - [:a {:on-click on-download} "Download report.txt"]) - [:div {:class (stl/css :sign-info)} - [:button {:on-click on-reset} (tr "labels.retry")]]])) + [:a {:class (stl/css :download-link) :on-click on-download} (tr "labels.download" "report.txt")]) + + [:div {:class (stl/css :buttons-container)} + [:> button* {:variant "secondary" + :type "button" + :class (stl/css :support-btn) + :on-click support-contact-click} (tr "labels.contact-support")] + [:> button* {:variant "primary" + :type "button" + :class (stl/css :retry-btn) + :on-click on-reset} (tr "labels.retry")]]])) (defn- load-info "Load exception page info" diff --git a/frontend/src/app/main/ui/static.scss b/frontend/src/app/main/ui/static.scss index 77bbfbff5b..3f77cf7d38 100644 --- a/frontend/src/app/main/ui/static.scss +++ b/frontend/src/app/main/ui/static.scss @@ -5,6 +5,7 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "./ds/typography.scss" as t; .exception-layout { width: 100%; @@ -133,15 +134,27 @@ } .main-message { - @include deprecated.bigTitleTipography; + @include t.use-typography("title-large"); color: var(--color-foreground-primary); } .desc-message { - @include deprecated.bigTitleTipography; + @include t.use-typography("title-large"); color: var(--color-foreground-secondary); } +.desc-text { + @include t.use-typography("title-large"); + color: var(--color-foreground-secondary); + margin-block-end: 0; +} + +.download-link { + @include t.use-typography("code-font"); + color: var(--color-foreground-primary); + text-transform: lowercase; +} + .sign-info { text-align: center; @@ -333,5 +346,13 @@ .login-container { width: 100%; - background-color: red; + background-color: var(--color-background-error); +} + +.buttons-container { + display: flex; + + .retry-btn { + margin-inline-start: var(--sp-xxl); + } } diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 5bfc2bef3c..376cd0cf0d 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1559,9 +1559,8 @@ msgstr "Old password is incorrect" msgid "feedback.description" msgstr "Description" -#: src/app/main/ui/settings/feedback.cljs:92 -msgid "feedback.discourse-go-to" -msgstr "Go to Penpot forum" +msgid "feedback.description-placeholder" +msgstr "Please describe the reason of your feedback" #: src/app/main/ui/settings/feedback.cljs:86 msgid "feedback.discourse-subtitle1" @@ -1569,6 +1568,9 @@ msgstr "" "We're happy to have you here. If you need help, please search before you " "post." +msgid "feedback.other-ways-contact" +msgstr "Other ways to contact us" + #: src/app/main/ui/settings/feedback.cljs:85 msgid "feedback.discourse-title" msgstr "Penpot community" @@ -1577,6 +1579,21 @@ msgstr "Penpot community" msgid "feedback.subject" msgstr "Subject" +msgid "feedback.type" +msgstr "Type" + +msgid "feedback.type.idea" +msgstr "Idea" + +msgid "feedback.type.issue" +msgstr "Issue" + +msgid "feedback.type.doubt" +msgstr "Doubt" + +msgid "feedback.penpot.link" +msgstr "If the feedback is something related with a file or a project, add the penpot link in here:" + #: src/app/main/ui/settings/feedback.cljs:66 msgid "feedback.subtitle" msgstr "" @@ -1584,12 +1601,8 @@ msgstr "" "idea or a doubt. A member of our team will respond as soon as possible." #: src/app/main/ui/settings/feedback.cljs:65 -msgid "feedback.title" -msgstr "Email" - -#: src/app/main/ui/settings/feedback.cljs:102 -msgid "feedback.twitter-go-to" -msgstr "Go to X" +msgid "feedback.title-contact-us" +msgstr "Contact us" #: src/app/main/ui/settings/feedback.cljs:96 msgid "feedback.twitter-subtitle1" @@ -2217,6 +2230,9 @@ msgstr "Github repository" msgid "labels.give-feedback" msgstr "Give feedback" +msgid "labels.contact-us" +msgstr "Contact us" + #: src/app/main/ui/auth/recovery_request.cljs:104, src/app/main/ui/auth/register.cljs:359, src/app/main/ui/static.cljs:170, src/app/main/ui/viewer/login.cljs:111 msgid "labels.go-back" msgstr "Go back" @@ -2254,10 +2270,11 @@ msgid "labels.installed-fonts" msgstr "Installed fonts" #: src/app/main/ui/static.cljs:373 -msgid "labels.internal-error.desc-message" -msgstr "" -"Something bad happened. Please retry the operation and if the problem " -"persists, contact support." +msgid "labels.internal-error.desc-message-first" +msgstr "Something bad happened." + +msgid "labels.internal-error.desc-message-second" +msgstr "You can retry the operation or contact support to report the error." #: src/app/main/ui/static.cljs:372 msgid "labels.internal-error.main-message" @@ -2525,6 +2542,12 @@ msgstr "Restore" msgid "labels.retry" msgstr "Retry" +msgid "labels.download" +msgstr "Download %s" + +msgid "labels.contact-support" +msgstr "Contact support" + #: src/app/main/ui/dashboard/team.cljs:513, src/app/main/ui/dashboard/team.cljs:945 msgid "labels.role" msgstr "Role" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 6b31583b5a..a33be1b5c9 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1556,9 +1556,8 @@ msgstr "La contraseña anterior no es correcta" msgid "feedback.description" msgstr "Descripción" -#: src/app/main/ui/settings/feedback.cljs:92 -msgid "feedback.discourse-go-to" -msgstr "Ir al foro de Penpot" +msgid "feedback.description-placeholder" +msgstr "Describe el motivo de tu comentario" #: src/app/main/ui/settings/feedback.cljs:86 msgid "feedback.discourse-subtitle1" @@ -1566,6 +1565,9 @@ msgstr "" "Estamos encantados de tenerte por aquí. Si necesitas ayuda, busca, escribe " "o pregunta lo que necesites." +msgid "feedback.other-ways-contact" +msgstr "Otras formas de contactarnos" + #: src/app/main/ui/settings/feedback.cljs:85 msgid "feedback.discourse-title" msgstr "Comunidad de Penpot" @@ -1574,6 +1576,21 @@ msgstr "Comunidad de Penpot" msgid "feedback.subject" msgstr "Asunto" +msgid "feedback.type" +msgstr "Tipo" + +msgid "feedback.type.idea" +msgstr "Idea" + +msgid "feedback.type.issue" +msgstr "Problema" + +msgid "feedback.type.doubt" +msgstr "Duda" + +msgid "feedback.penpot.link" +msgstr "Si el comentario está relacionado con un archivo o un proyecto, añade aquí el enlace de Penpot:" + #: src/app/main/ui/settings/feedback.cljs:66 msgid "feedback.subtitle" msgstr "" @@ -1582,12 +1599,8 @@ msgstr "" "pronto como sea posible." #: src/app/main/ui/settings/feedback.cljs:65 -msgid "feedback.title" -msgstr "Correo electrónico" - -#: src/app/main/ui/settings/feedback.cljs:102 -msgid "feedback.twitter-go-to" -msgstr "Ir a X" +msgid "feedback.title-contact-us" +msgstr "Contáctanos" #: src/app/main/ui/settings/feedback.cljs:96 msgid "feedback.twitter-subtitle1" @@ -2195,6 +2208,9 @@ msgstr "Repositorio de Github" msgid "labels.give-feedback" msgstr "Danos tu opinión" +msgid "labels.contact-us" +msgstr "Contáctanos" + #: src/app/main/ui/auth/recovery_request.cljs:104, src/app/main/ui/auth/register.cljs:359, src/app/main/ui/static.cljs:170, src/app/main/ui/viewer/login.cljs:111 msgid "labels.go-back" msgstr "Volver" @@ -2232,10 +2248,11 @@ msgid "labels.installed-fonts" msgstr "Fuentes instaladas" #: src/app/main/ui/static.cljs:373 -msgid "labels.internal-error.desc-message" -msgstr "" -"Ha ocurrido algo extraño. Por favor, reintenta la operación, y si el " -"problema persiste, contacta con el servicio técnico." +msgid "labels.internal-error.desc-message-first" +msgstr "Ha ocurrido algo extraño." + +msgid "labels.internal-error.desc-message-second" +msgstr "Puedes reintentar la operación o contacta con soporte para reportar el error." #: src/app/main/ui/static.cljs:372 msgid "labels.internal-error.main-message" @@ -2495,6 +2512,12 @@ msgstr "Restaurar" msgid "labels.retry" msgstr "Reintentar" +msgid "labels.download" +msgstr "Descargar %s" + +msgid "labels.contact-support" +msgstr "Contacta con soporte" + #: src/app/main/ui/dashboard/team.cljs:513, src/app/main/ui/dashboard/team.cljs:945 msgid "labels.role" msgstr "Rol"