Merge pull request #7511 from penpot/marina-improve-users-give-feedback

 Improve the way users give us feedback
This commit is contained in:
Marina López
2025-10-17 10:19:34 +02:00
committed by GitHub
18 changed files with 449 additions and 193 deletions

View File

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

View File

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

View File

@@ -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}}

View File

@@ -20,6 +20,7 @@ export PENPOT_FLAGS="\
enable-audit-log \
enable-transit-readable-response \
enable-demo-users \
enable-user-feedback \
disable-secure-session-cookies \
enable-smtp \
enable-prepl-server \
@@ -46,6 +47,8 @@ export PENPOT_MEDIA_MAX_FILE_SIZE=104857600
# Setup default multipart upload size to 300MiB
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=314572800
export PENPOT_USER_FEEDBACK_DESTINATION="support@example.com"
export AWS_ACCESS_KEY_ID=penpot-devenv
export AWS_SECRET_ACCESS_KEY=penpot-devenv
export PENPOT_OBJECTS_STORAGE_BACKEND=s3

View File

@@ -7,6 +7,7 @@
(ns app.email
"Main api for send emails."
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.logging :as l]
@@ -93,36 +94,44 @@
headers)))
(defn- assign-body
[^MimeMessage mmsg {:keys [body charset] :or {charset "utf-8"}}]
(let [mpart (MimeMultipart. "mixed")]
[^MimeMessage mmsg {:keys [body charset attachments] :or {charset "utf-8"}}]
(let [mixed-mpart (MimeMultipart. "mixed")]
(cond
(string? body)
(let [bpart (MimeBodyPart.)]
(.setContent bpart ^String body (str "text/plain; charset=" charset))
(.addBodyPart mpart bpart))
(vector? body)
(let [mmp (MimeMultipart. "alternative")
mbp (MimeBodyPart.)]
(.addBodyPart mpart mbp)
(.setContent mbp mmp)
(doseq [item body]
(let [mbp (MimeBodyPart.)]
(.setContent mbp
^String (:content item)
^String (str (:type item "text/plain") "; charset=" charset))
(.addBodyPart mmp mbp))))
(let [text-part (MimeBodyPart.)]
(.setText text-part ^String body ^String charset)
(.addBodyPart mixed-mpart text-part))
(map? body)
(let [bpart (MimeBodyPart.)]
(.setContent bpart
^String (:content body)
^String (str (:type body "text/plain") "; charset=" charset))
(.addBodyPart mpart bpart))
(let [content-part (MimeBodyPart.)
alternative-mpart (MimeMultipart. "alternative")]
(when-let [content (get body "text/html")]
(let [html-part (MimeBodyPart.)]
(.setContent html-part ^String content
(str "text/html; charset=" charset))
(.addBodyPart alternative-mpart html-part)))
(when-let [content (get body "text/plain")]
(let [text-part (MimeBodyPart.)]
(.setText text-part ^String content ^String charset)
(.addBodyPart alternative-mpart text-part)))
(.setContent content-part alternative-mpart)
(.addBodyPart mixed-mpart content-part))
:else
(throw (ex-info "Unsupported type" {:body body})))
(.setContent mmsg mpart)
(throw (IllegalArgumentException. "invalid email body provided")))
(doseq [[name content] attachments]
(prn "attachment" name)
(let [attachment-part (MimeBodyPart.)]
(.setFileName attachment-part ^String name)
(.setContent attachment-part ^String content (str "text/plain; charset=" charset))
(.addBodyPart mixed-mpart attachment-part)))
(.setContent mmsg mixed-mpart)
mmsg))
(defn- opts->props
@@ -210,24 +219,26 @@
(ex/raise :type :internal
:code :missing-email-templates))
{:subject subj
:body (into
[{:type "text/plain"
:content text}]
(when html
[{:type "text/html"
:content html}]))}))
:body (d/without-nils
{"text/plain" text
"text/html" html})}))
(def ^:private schema:context
[:map
(def ^:private schema:params
[:map {:title "Email Params"}
[:to [:or ::sm/email [::sm/vec ::sm/email]]]
[:reply-to {:optional true} ::sm/email]
[:from {:optional true} ::sm/email]
[:lang {:optional true} ::sm/text]
[:subject {:optional true} ::sm/text]
[:priority {:optional true} [:enum :high :low]]
[:extra-data {:optional true} ::sm/text]])
[:extra-data {:optional true} ::sm/text]
[:body {:optional true}
[:or :string [:map-of :string :string]]]
[:attachments {:optional true}
[:map-of :string :string]]])
(def ^:private check-context
(sm/check-fn schema:context))
(def ^:private check-params
(sm/check-fn schema:params))
(defn template-factory
[& {:keys [id schema]}]
@@ -235,9 +246,9 @@
(let [check-fn (if schema
(sm/check-fn schema)
(constantly nil))]
(fn [context]
(let [context (-> context check-context check-fn)
email (build-email-template id context)]
(fn [params]
(let [params (-> params check-params check-fn)
email (build-email-template id params)]
(when-not email
(ex/raise :type :internal
:code :email-template-does-not-exists
@@ -245,35 +256,40 @@
:template-id id))
(cond-> (assoc email :id (name id))
(:extra-data context)
(assoc :extra-data (:extra-data context))
(:extra-data params)
(assoc :extra-data (:extra-data params))
(:from context)
(assoc :from (:from context))
(seq (:attachments params))
(assoc :attachments (:attachments params))
(:reply-to context)
(assoc :reply-to (:reply-to context))
(:from params)
(assoc :from (:from params))
(:to context)
(assoc :to (:to context)))))))
(:reply-to params)
(assoc :reply-to (:reply-to params))
(:to params)
(assoc :to (:to params)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PUBLIC HIGH-LEVEL API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn render
[email-factory context]
(email-factory context))
[email-factory params]
(email-factory params))
(defn send!
"Schedule an already defined email to be sent using asynchronously
using worker task."
[{:keys [::conn ::factory] :as context}]
[{:keys [::conn ::factory] :as params}]
(assert (db/connectable? conn) "expected a valid database connection or pool")
(let [email (if factory
(factory context)
(dissoc context ::conn))]
(factory params)
(-> params
(dissoc params)
(check-params)))]
(wrk/submit! {::wrk/task :sendmail
::wrk/delay 0
::wrk/max-retries 4
@@ -343,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."

View File

@@ -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))

View File

@@ -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)))))

View File

@@ -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

View File

@@ -40,16 +40,16 @@
(def verify-token-page
(mf/lazy-component app.main.ui.auth.verify-token/verify-token))
(def viewer-page
(def viewer-page*
(mf/lazy-component app.main.ui.viewer/viewer*))
(def dashboard-page
(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
(def workspace-page*
(mf/lazy-component app.main.ui.workspace/workspace*))
(mf/defc workspace-legacy-redirect*
@@ -197,7 +197,13 @@
:settings-subscription
:settings-access-tokens
:settings-notifications)
[:? [:& settings-page {:route route}]]
(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*
@@ -239,13 +245,13 @@
[:& release-notes-modal {:version (:main cf/version)}])
[:> team-container* {:team-id team-id}
[:> dashboard-page {:profile profile
:section section
:team-id team-id
:search-term search-term
:plugin-url plugin-url
:project-id project-id
:template template}]]])
[:> dashboard-page* {:profile profile
:section section
:team-id team-id
:search-term search-term
:plugin-url plugin-url
:project-id project-id
:template template}]]])
:workspace
(let [params (get params :query)
@@ -266,11 +272,11 @@
[:& release-notes-modal {:version (:main cf/version)}]))
[:> team-container* {:team-id team-id}
[:> workspace-page {:team-id team-id
:file-id file-id
:page-id page-id
:layout-name layout
:key file-id}]]])
[:> workspace-page* {:team-id team-id
:file-id file-id
:page-id page-id
:layout-name layout
:key file-id}]]])
:viewer
(let [params (get params :query)
@@ -287,7 +293,7 @@
share (:share params)]
[:? {}
[:> viewer-page
[:> viewer-page*
{:page-id page-id
:file-id file-id
:frame-id frame-id
@@ -369,7 +375,7 @@
[:& (mf/provider ctx/current-profile) {:value profile}
(if edata
[:> static/exception-page* {:data edata :route route}]
[:> error-boundary* {:fallback static/internal-error*}
[:> error-boundary* {:fallback static/exception-page*}
[:> notifications/current-notification*]
(when route
[:> page* {:route route :profile profile}])])]]))

View File

@@ -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] :as props}]
(mf/defc settings*
[{:keys [route type error-report-id error-href]}]
(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
:error-report-id error-report-id
:error-href error-href}]
:settings-password
[:& password-page]

View File

@@ -8,28 +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]))
(def ^:private schema:feedback-form
[:map {:title "FeedbackForm"}
[:subject [::sm/text {:max 250}]]
[:content [::sm/text {:max 5000}]]])
[:type [:string {:max 250}]]
[:content [::sm/text {:max 5000}]]
[: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 [error-report type error-href]}]
(let [profile (mf/deref refs/profile)
form (fm/use-form :schema schema:feedback-form)
loading (mf/use-state false)
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)
(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
@@ -57,24 +92,47 @@
(->> (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
[:h2 {:class (stl/css :field-title)} (tr "feedback.title")]
[:p {:class (stl/css :field-text)} (tr "feedback.subtitle")]
;; --- 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")]
[: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 :error-href
: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 +140,30 @@
[: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
[]
(mf/defc feedback-page*
[{:keys [error-report-id] :as props}]
(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 (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* props]]]))

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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]

View File

@@ -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]
@@ -22,12 +24,14 @@
[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]
[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]
@@ -353,6 +357,19 @@
(let [report-uri (mf/use-ref nil)
on-reset (or on-reset #(st/emit! (rt/assign-exception nil)))
support-contact-click
(mf/use-fn
(mf/deps on-reset report)
(fn []
(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
(fn [event]
@@ -362,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)
@@ -370,11 +388,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"
@@ -422,6 +452,7 @@
:path (get route :path)
:report report
:params params}))))
(case type
:not-found
[:> not-found* {}]

View File

@@ -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);
}
}

View File

@@ -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"

View File

@@ -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"