Improve the way users give us feedback

This commit is contained in:
Marina López 2025-08-29 14:07:55 +02:00 committed by Andrey Antukh
parent a32f44a62c
commit 854ad5bb4d
10 changed files with 261 additions and 76 deletions

View File

@ -197,7 +197,14 @@
:settings-subscription :settings-subscription
:settings-access-tokens :settings-access-tokens
:settings-notifications) :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 :debug-icons-preview
(when *assert* (when *assert*

View File

@ -34,7 +34,7 @@
[: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] :as props}] [{:keys [route type report-id url-error]}]
(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,7 +60,9 @@
[:& profile-page] [:& profile-page]
:settings-feedback :settings-feedback
[:& feedback-page] [:& feedback-page {:type type
:report-id report-id
:url-error url-error}]
:settings-password :settings-password
[:& password-page] [:& password-page]

View File

@ -16,20 +16,37 @@
[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.webapi :as wapi]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(def ^:private schema:feedback-form (defn schema:feedback-form [url-error]
[:map {:title "FeedbackForm"} [:map {:title "FeedbackForm"}
[:subject [::sm/text {:max 250}]] [: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/defc feedback-form
{::mf/private true} {::mf/private true}
[] [{:keys [report type url-error]}]
(let [profile (mf/deref refs/profile) (let [profile (mf/deref refs/profile)
form (fm/use-form :schema schema:feedback-form) initial (mf/with-memo [url-error]
loading (mf/use-state false) {: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 on-succes
(mf/use-fn (mf/use-fn
@ -62,19 +79,41 @@
:form form} :form form}
;; --- Feedback section ;; --- Feedback section
[:h2 {:class (stl/css :field-title)} (tr "feedback.title")] [:h2 {:class (stl/css :field-title :feedback-title)} (tr "feedback.title-contact-us")]
[:p {:class (stl/css :field-text)} (tr "feedback.subtitle")] [:p {:class (stl/css :field-text :feedback-title)} (tr "feedback.subtitle")]
[:div {:class (stl/css :fields-row)} [:div {:class (stl/css :fields-row)}
[:& fm/input {:label (tr "feedback.subject") [:& fm/input {:label (tr "feedback.subject")
:name :subject :name :subject
:show-success? true}]] :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)} [:div {:class (stl/css :fields-row :description)}
[:& fm/textarea [:& fm/textarea
{:label (tr "feedback.description") {:class (stl/css :feedback-description)
:label (tr "feedback.description")
:name :content :name :content
:placeholder (tr "feedback.description-placeholder")
:rows 5}]] :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* [:> fm/submit-button*
{:label (if @loading (tr "labels.sending") (tr "labels.send")) {:label (if @loading (tr "labels.sending") (tr "labels.send"))
:class (stl/css :feedback-button-link) :class (stl/css :feedback-button-link)
@ -82,30 +121,28 @@
[:hr] [:hr]
[:h2 {:class (stl/css :field-title)} (tr "feedback.discourse-title")] [:h2 {:class (stl/css :feedback-title)} (tr "feedback.other-ways-contact")]
[:p {:class (stl/css :field-text)} (tr "feedback.discourse-subtitle1")]
[: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")] [:a {:class (stl/css :link)
[:p {:class (stl/css :field-text)} (tr "feedback.twitter-subtitle1")] :href "https://community.penpot.app"
:target "_blank"}
(tr "feedback.discourse-title")]
[:p {:class (stl/css :field-text :bottom-margin)} (tr "feedback.discourse-subtitle1")]
[:a [:a {:class (stl/css :link)
{:class (stl/css :feedback-button-link) :href "https://x.com/penpotapp"
:href "https://twitter.com/penpotapp" :target "_blank"}
:target "_blank"} (tr "feedback.twitter-title")]
(tr "feedback.twitter-go-to")]])) [:p {:class (stl/css :field-text)} (tr "feedback.twitter-subtitle1")]]))
(mf/defc feedback-page (mf/defc feedback-page
[] [{:keys [type report-id url-error]}]
(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)]
[: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]]]) [:& feedback-form {:report report
:type type
:url-error url-error}]]]))

View File

@ -6,24 +6,74 @@
@use "common/refactor/common-refactor" as *; @use "common/refactor/common-refactor" as *;
@use "./profile"; @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 { .feedback-form {
textarea { .fields-row {
border-radius: $br-8; margin-block-end: $sz-32;
padding: $br-12; }
.feedback-description {
@include t.use-typography("body-medium");
border-radius: b.$br-8;
padding: var(--sp-m);
background-color: var(--color-background-tertiary); background-color: var(--color-background-tertiary);
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
border: none; border: none;
::placeholder { ::placeholder {
color: var(--color-background-disabled); color: var(--input-placeholder-color);
} }
&:focus { &:focus {
outline: $s-1 solid var(--color-accent-primary); outline: b.$b-1 solid var(--color-accent-primary);
} }
} }
}
.feedback-button-link { .field-label {
@extend .button-primary; @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;
}
} }

View File

@ -11,7 +11,7 @@
width: 100%; width: 100%;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
a:not(.button-primary) { a:not(.button-primary):not(.link) {
color: var(--color-foreground-secondary); color: var(--color-foreground-secondary);
} }
} }

View File

@ -133,7 +133,7 @@
:settings-item true) :settings-item true)
:on-click go-settings-feedback} :on-click go-settings-feedback}
feedback-icon 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/defc sidebar
{::mf/wrap [mf/memo] {::mf/wrap [mf/memo]

View File

@ -22,6 +22,7 @@
[app.main.ui.auth.recovery-request :refer [recovery-request-page recovery-sent-page]] [app.main.ui.auth.recovery-request :refer [recovery-request-page recovery-sent-page]]
[app.main.ui.auth.register :as register] [app.main.ui.auth.register :as register]
[app.main.ui.dashboard.sidebar :refer [sidebar*]] [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.icon :refer [icon*] :as i]
[app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]] [app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.icons :as deprecated-icon]
@ -351,7 +352,16 @@
(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
(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 on-download
(mf/use-fn (mf/use-fn
@ -370,11 +380,23 @@
[:> error-container* {} [:> error-container* {}
[:div {:class (stl/css :main-message)} (tr "labels.internal-error.main-message")] [: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) (when (some? report)
[:a {:on-click on-download} "Download report.txt"]) [:a {:class (stl/css :download-link) :on-click on-download} (tr "labels.download" "report.txt")])
[:div {:class (stl/css :sign-info)}
[:button {:on-click on-reset} (tr "labels.retry")]]])) [: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 (defn- load-info
"Load exception page info" "Load exception page info"

View File

@ -5,6 +5,7 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "./ds/typography.scss" as t;
.exception-layout { .exception-layout {
width: 100%; width: 100%;
@ -133,15 +134,27 @@
} }
.main-message { .main-message {
@include deprecated.bigTitleTipography; @include t.use-typography("title-large");
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
} }
.desc-message { .desc-message {
@include deprecated.bigTitleTipography; @include t.use-typography("title-large");
color: var(--color-foreground-secondary); 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 { .sign-info {
text-align: center; text-align: center;
@ -333,5 +346,13 @@
.login-container { .login-container {
width: 100%; width: 100%;
background-color: red; background-color: var(--color-background-error);
}
.buttons-container {
display: flex;
.retry-btn {
margin-inline-start: var(--sp-xxl);
}
} }

View File

@ -1559,9 +1559,8 @@ msgstr "Old password is incorrect"
msgid "feedback.description" msgid "feedback.description"
msgstr "Description" msgstr "Description"
#: src/app/main/ui/settings/feedback.cljs:92 msgid "feedback.description-placeholder"
msgid "feedback.discourse-go-to" msgstr "Please describe the reason of your feedback"
msgstr "Go to Penpot forum"
#: src/app/main/ui/settings/feedback.cljs:86 #: src/app/main/ui/settings/feedback.cljs:86
msgid "feedback.discourse-subtitle1" msgid "feedback.discourse-subtitle1"
@ -1569,6 +1568,9 @@ msgstr ""
"We're happy to have you here. If you need help, please search before you " "We're happy to have you here. If you need help, please search before you "
"post." "post."
msgid "feedback.other-ways-contact"
msgstr "Other ways to contact us"
#: src/app/main/ui/settings/feedback.cljs:85 #: src/app/main/ui/settings/feedback.cljs:85
msgid "feedback.discourse-title" msgid "feedback.discourse-title"
msgstr "Penpot community" msgstr "Penpot community"
@ -1577,6 +1579,21 @@ msgstr "Penpot community"
msgid "feedback.subject" msgid "feedback.subject"
msgstr "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 #: src/app/main/ui/settings/feedback.cljs:66
msgid "feedback.subtitle" msgid "feedback.subtitle"
msgstr "" msgstr ""
@ -1584,12 +1601,8 @@ msgstr ""
"idea or a doubt. A member of our team will respond as soon as possible." "idea or a doubt. A member of our team will respond as soon as possible."
#: src/app/main/ui/settings/feedback.cljs:65 #: src/app/main/ui/settings/feedback.cljs:65
msgid "feedback.title" msgid "feedback.title-contact-us"
msgstr "Email" msgstr "Contact us"
#: src/app/main/ui/settings/feedback.cljs:102
msgid "feedback.twitter-go-to"
msgstr "Go to X"
#: src/app/main/ui/settings/feedback.cljs:96 #: src/app/main/ui/settings/feedback.cljs:96
msgid "feedback.twitter-subtitle1" msgid "feedback.twitter-subtitle1"
@ -2217,6 +2230,9 @@ msgstr "Github repository"
msgid "labels.give-feedback" msgid "labels.give-feedback"
msgstr "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 #: 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" msgid "labels.go-back"
msgstr "Go back" msgstr "Go back"
@ -2254,10 +2270,11 @@ msgid "labels.installed-fonts"
msgstr "Installed fonts" msgstr "Installed fonts"
#: src/app/main/ui/static.cljs:373 #: src/app/main/ui/static.cljs:373
msgid "labels.internal-error.desc-message" msgid "labels.internal-error.desc-message-first"
msgstr "" msgstr "Something bad happened."
"Something bad happened. Please retry the operation and if the problem "
"persists, contact support." 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 #: src/app/main/ui/static.cljs:372
msgid "labels.internal-error.main-message" msgid "labels.internal-error.main-message"
@ -2525,6 +2542,12 @@ msgstr "Restore"
msgid "labels.retry" msgid "labels.retry"
msgstr "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 #: src/app/main/ui/dashboard/team.cljs:513, src/app/main/ui/dashboard/team.cljs:945
msgid "labels.role" msgid "labels.role"
msgstr "Role" msgstr "Role"

View File

@ -1556,9 +1556,8 @@ msgstr "La contraseña anterior no es correcta"
msgid "feedback.description" msgid "feedback.description"
msgstr "Descripción" msgstr "Descripción"
#: src/app/main/ui/settings/feedback.cljs:92 msgid "feedback.description-placeholder"
msgid "feedback.discourse-go-to" msgstr "Describe el motivo de tu comentario"
msgstr "Ir al foro de Penpot"
#: src/app/main/ui/settings/feedback.cljs:86 #: src/app/main/ui/settings/feedback.cljs:86
msgid "feedback.discourse-subtitle1" msgid "feedback.discourse-subtitle1"
@ -1566,6 +1565,9 @@ msgstr ""
"Estamos encantados de tenerte por aquí. Si necesitas ayuda, busca, escribe " "Estamos encantados de tenerte por aquí. Si necesitas ayuda, busca, escribe "
"o pregunta lo que necesites." "o pregunta lo que necesites."
msgid "feedback.other-ways-contact"
msgstr "Otras formas de contactarnos"
#: src/app/main/ui/settings/feedback.cljs:85 #: src/app/main/ui/settings/feedback.cljs:85
msgid "feedback.discourse-title" msgid "feedback.discourse-title"
msgstr "Comunidad de Penpot" msgstr "Comunidad de Penpot"
@ -1574,6 +1576,21 @@ msgstr "Comunidad de Penpot"
msgid "feedback.subject" msgid "feedback.subject"
msgstr "Asunto" 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 #: src/app/main/ui/settings/feedback.cljs:66
msgid "feedback.subtitle" msgid "feedback.subtitle"
msgstr "" msgstr ""
@ -1582,12 +1599,8 @@ msgstr ""
"pronto como sea posible." "pronto como sea posible."
#: src/app/main/ui/settings/feedback.cljs:65 #: src/app/main/ui/settings/feedback.cljs:65
msgid "feedback.title" msgid "feedback.title-contact-us"
msgstr "Correo electrónico" msgstr "Contáctanos"
#: src/app/main/ui/settings/feedback.cljs:102
msgid "feedback.twitter-go-to"
msgstr "Ir a X"
#: src/app/main/ui/settings/feedback.cljs:96 #: src/app/main/ui/settings/feedback.cljs:96
msgid "feedback.twitter-subtitle1" msgid "feedback.twitter-subtitle1"
@ -2195,6 +2208,9 @@ msgstr "Repositorio de Github"
msgid "labels.give-feedback" msgid "labels.give-feedback"
msgstr "Danos tu opinión" 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 #: 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" msgid "labels.go-back"
msgstr "Volver" msgstr "Volver"
@ -2232,10 +2248,11 @@ msgid "labels.installed-fonts"
msgstr "Fuentes instaladas" msgstr "Fuentes instaladas"
#: src/app/main/ui/static.cljs:373 #: src/app/main/ui/static.cljs:373
msgid "labels.internal-error.desc-message" msgid "labels.internal-error.desc-message-first"
msgstr "" msgstr "Ha ocurrido algo extraño."
"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-second"
msgstr "Puedes reintentar la operación o contacta con soporte para reportar el error."
#: src/app/main/ui/static.cljs:372 #: src/app/main/ui/static.cljs:372
msgid "labels.internal-error.main-message" msgid "labels.internal-error.main-message"
@ -2495,6 +2512,12 @@ msgstr "Restaurar"
msgid "labels.retry" msgid "labels.retry"
msgstr "Reintentar" 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 #: src/app/main/ui/dashboard/team.cljs:513, src/app/main/ui/dashboard/team.cljs:945
msgid "labels.role" msgid "labels.role"
msgstr "Rol" msgstr "Rol"