mirror of
https://github.com/penpot/penpot.git
synced 2025-12-12 06:24:17 +01:00
Merge pull request #7511 from penpot/marina-improve-users-give-feedback
✨ Improve the way users give us feedback
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}}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export PENPOT_FLAGS="\
|
|||||||
enable-audit-log \
|
enable-audit-log \
|
||||||
enable-transit-readable-response \
|
enable-transit-readable-response \
|
||||||
enable-demo-users \
|
enable-demo-users \
|
||||||
|
enable-user-feedback \
|
||||||
disable-secure-session-cookies \
|
disable-secure-session-cookies \
|
||||||
enable-smtp \
|
enable-smtp \
|
||||||
enable-prepl-server \
|
enable-prepl-server \
|
||||||
@@ -46,6 +47,8 @@ export PENPOT_MEDIA_MAX_FILE_SIZE=104857600
|
|||||||
# Setup default multipart upload size to 300MiB
|
# Setup default multipart upload size to 300MiB
|
||||||
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=314572800
|
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_ACCESS_KEY_ID=penpot-devenv
|
||||||
export AWS_SECRET_ACCESS_KEY=penpot-devenv
|
export AWS_SECRET_ACCESS_KEY=penpot-devenv
|
||||||
export PENPOT_OBJECTS_STORAGE_BACKEND=s3
|
export PENPOT_OBJECTS_STORAGE_BACKEND=s3
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
(ns app.email
|
(ns app.email
|
||||||
"Main api for send emails."
|
"Main api for send emails."
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
@@ -93,36 +94,44 @@
|
|||||||
headers)))
|
headers)))
|
||||||
|
|
||||||
(defn- assign-body
|
(defn- assign-body
|
||||||
[^MimeMessage mmsg {:keys [body charset] :or {charset "utf-8"}}]
|
[^MimeMessage mmsg {:keys [body charset attachments] :or {charset "utf-8"}}]
|
||||||
(let [mpart (MimeMultipart. "mixed")]
|
(let [mixed-mpart (MimeMultipart. "mixed")]
|
||||||
(cond
|
(cond
|
||||||
(string? body)
|
(string? body)
|
||||||
(let [bpart (MimeBodyPart.)]
|
(let [text-part (MimeBodyPart.)]
|
||||||
(.setContent bpart ^String body (str "text/plain; charset=" charset))
|
(.setText text-part ^String body ^String charset)
|
||||||
(.addBodyPart mpart bpart))
|
(.addBodyPart mixed-mpart text-part))
|
||||||
|
|
||||||
(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))))
|
|
||||||
|
|
||||||
(map? body)
|
(map? body)
|
||||||
(let [bpart (MimeBodyPart.)]
|
(let [content-part (MimeBodyPart.)
|
||||||
(.setContent bpart
|
alternative-mpart (MimeMultipart. "alternative")]
|
||||||
^String (:content body)
|
|
||||||
^String (str (:type body "text/plain") "; charset=" charset))
|
(when-let [content (get body "text/html")]
|
||||||
(.addBodyPart mpart bpart))
|
(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
|
:else
|
||||||
(throw (ex-info "Unsupported type" {:body body})))
|
(throw (IllegalArgumentException. "invalid email body provided")))
|
||||||
(.setContent mmsg mpart)
|
|
||||||
|
(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))
|
mmsg))
|
||||||
|
|
||||||
(defn- opts->props
|
(defn- opts->props
|
||||||
@@ -210,24 +219,26 @@
|
|||||||
(ex/raise :type :internal
|
(ex/raise :type :internal
|
||||||
:code :missing-email-templates))
|
:code :missing-email-templates))
|
||||||
{:subject subj
|
{:subject subj
|
||||||
:body (into
|
:body (d/without-nils
|
||||||
[{:type "text/plain"
|
{"text/plain" text
|
||||||
:content text}]
|
"text/html" html})}))
|
||||||
(when html
|
|
||||||
[{:type "text/html"
|
|
||||||
:content html}]))}))
|
|
||||||
|
|
||||||
(def ^:private schema:context
|
(def ^:private schema:params
|
||||||
[:map
|
[:map {:title "Email Params"}
|
||||||
[:to [:or ::sm/email [::sm/vec ::sm/email]]]
|
[:to [:or ::sm/email [::sm/vec ::sm/email]]]
|
||||||
[:reply-to {:optional true} ::sm/email]
|
[:reply-to {:optional true} ::sm/email]
|
||||||
[:from {:optional true} ::sm/email]
|
[:from {:optional true} ::sm/email]
|
||||||
[:lang {:optional true} ::sm/text]
|
[:lang {:optional true} ::sm/text]
|
||||||
|
[:subject {:optional true} ::sm/text]
|
||||||
[:priority {:optional true} [:enum :high :low]]
|
[: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
|
(def ^:private check-params
|
||||||
(sm/check-fn schema:context))
|
(sm/check-fn schema:params))
|
||||||
|
|
||||||
(defn template-factory
|
(defn template-factory
|
||||||
[& {:keys [id schema]}]
|
[& {:keys [id schema]}]
|
||||||
@@ -235,9 +246,9 @@
|
|||||||
(let [check-fn (if schema
|
(let [check-fn (if schema
|
||||||
(sm/check-fn schema)
|
(sm/check-fn schema)
|
||||||
(constantly nil))]
|
(constantly nil))]
|
||||||
(fn [context]
|
(fn [params]
|
||||||
(let [context (-> context check-context check-fn)
|
(let [params (-> params check-params check-fn)
|
||||||
email (build-email-template id context)]
|
email (build-email-template id params)]
|
||||||
(when-not email
|
(when-not email
|
||||||
(ex/raise :type :internal
|
(ex/raise :type :internal
|
||||||
:code :email-template-does-not-exists
|
:code :email-template-does-not-exists
|
||||||
@@ -245,35 +256,40 @@
|
|||||||
:template-id id))
|
:template-id id))
|
||||||
|
|
||||||
(cond-> (assoc email :id (name id))
|
(cond-> (assoc email :id (name id))
|
||||||
(:extra-data context)
|
(:extra-data params)
|
||||||
(assoc :extra-data (:extra-data context))
|
(assoc :extra-data (:extra-data params))
|
||||||
|
|
||||||
(:from context)
|
(seq (:attachments params))
|
||||||
(assoc :from (:from context))
|
(assoc :attachments (:attachments params))
|
||||||
|
|
||||||
(:reply-to context)
|
(:from params)
|
||||||
(assoc :reply-to (:reply-to context))
|
(assoc :from (:from params))
|
||||||
|
|
||||||
(:to context)
|
(:reply-to params)
|
||||||
(assoc :to (:to context)))))))
|
(assoc :reply-to (:reply-to params))
|
||||||
|
|
||||||
|
(:to params)
|
||||||
|
(assoc :to (:to params)))))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; PUBLIC HIGH-LEVEL API
|
;; PUBLIC HIGH-LEVEL API
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(defn render
|
(defn render
|
||||||
[email-factory context]
|
[email-factory params]
|
||||||
(email-factory context))
|
(email-factory params))
|
||||||
|
|
||||||
(defn send!
|
(defn send!
|
||||||
"Schedule an already defined email to be sent using asynchronously
|
"Schedule an already defined email to be sent using asynchronously
|
||||||
using worker task."
|
using worker task."
|
||||||
[{:keys [::conn ::factory] :as context}]
|
[{:keys [::conn ::factory] :as params}]
|
||||||
(assert (db/connectable? conn) "expected a valid database connection or pool")
|
(assert (db/connectable? conn) "expected a valid database connection or pool")
|
||||||
|
|
||||||
(let [email (if factory
|
(let [email (if factory
|
||||||
(factory context)
|
(factory params)
|
||||||
(dissoc context ::conn))]
|
(-> params
|
||||||
|
(dissoc params)
|
||||||
|
(check-params)))]
|
||||||
(wrk/submit! {::wrk/task :sendmail
|
(wrk/submit! {::wrk/task :sendmail
|
||||||
::wrk/delay 0
|
::wrk/delay 0
|
||||||
::wrk/max-retries 4
|
::wrk/max-retries 4
|
||||||
@@ -343,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
|
||||||
|
|||||||
@@ -40,16 +40,16 @@
|
|||||||
(def verify-token-page
|
(def verify-token-page
|
||||||
(mf/lazy-component app.main.ui.auth.verify-token/verify-token))
|
(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*))
|
(mf/lazy-component app.main.ui.viewer/viewer*))
|
||||||
|
|
||||||
(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*))
|
||||||
|
|
||||||
(mf/defc workspace-legacy-redirect*
|
(mf/defc workspace-legacy-redirect*
|
||||||
@@ -197,7 +197,13 @@
|
|||||||
:settings-subscription
|
:settings-subscription
|
||||||
:settings-access-tokens
|
:settings-access-tokens
|
||||||
:settings-notifications)
|
: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
|
:debug-icons-preview
|
||||||
(when *assert*
|
(when *assert*
|
||||||
@@ -239,13 +245,13 @@
|
|||||||
[:& release-notes-modal {:version (:main cf/version)}])
|
[:& release-notes-modal {:version (:main cf/version)}])
|
||||||
|
|
||||||
[:> team-container* {:team-id team-id}
|
[:> team-container* {:team-id team-id}
|
||||||
[:> dashboard-page {:profile profile
|
[:> dashboard-page* {:profile profile
|
||||||
:section section
|
:section section
|
||||||
:team-id team-id
|
:team-id team-id
|
||||||
:search-term search-term
|
:search-term search-term
|
||||||
:plugin-url plugin-url
|
:plugin-url plugin-url
|
||||||
:project-id project-id
|
:project-id project-id
|
||||||
:template template}]]])
|
:template template}]]])
|
||||||
|
|
||||||
:workspace
|
:workspace
|
||||||
(let [params (get params :query)
|
(let [params (get params :query)
|
||||||
@@ -266,11 +272,11 @@
|
|||||||
[:& release-notes-modal {:version (:main cf/version)}]))
|
[:& release-notes-modal {:version (:main cf/version)}]))
|
||||||
|
|
||||||
[:> team-container* {:team-id team-id}
|
[:> team-container* {:team-id team-id}
|
||||||
[:> workspace-page {:team-id team-id
|
[:> workspace-page* {:team-id team-id
|
||||||
:file-id file-id
|
:file-id file-id
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:layout-name layout
|
:layout-name layout
|
||||||
:key file-id}]]])
|
:key file-id}]]])
|
||||||
|
|
||||||
:viewer
|
:viewer
|
||||||
(let [params (get params :query)
|
(let [params (get params :query)
|
||||||
@@ -287,7 +293,7 @@
|
|||||||
share (:share params)]
|
share (:share params)]
|
||||||
|
|
||||||
[:? {}
|
[:? {}
|
||||||
[:> viewer-page
|
[:> viewer-page*
|
||||||
{:page-id page-id
|
{:page-id page-id
|
||||||
:file-id file-id
|
:file-id file-id
|
||||||
:frame-id frame-id
|
:frame-id frame-id
|
||||||
@@ -369,7 +375,7 @@
|
|||||||
[:& (mf/provider ctx/current-profile) {:value profile}
|
[:& (mf/provider ctx/current-profile) {:value profile}
|
||||||
(if edata
|
(if edata
|
||||||
[:> static/exception-page* {:data edata :route route}]
|
[:> static/exception-page* {:data edata :route route}]
|
||||||
[:> error-boundary* {:fallback static/internal-error*}
|
[:> error-boundary* {:fallback static/exception-page*}
|
||||||
[:> notifications/current-notification*]
|
[:> notifications/current-notification*]
|
||||||
(when route
|
(when route
|
||||||
[:> page* {:route route :profile profile}])])]]))
|
[:> page* {:route route :profile profile}])])]]))
|
||||||
|
|||||||
@@ -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] :as props}]
|
[{: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,7 +60,9 @@
|
|||||||
[:& profile-page]
|
[:& profile-page]
|
||||||
|
|
||||||
:settings-feedback
|
:settings-feedback
|
||||||
[:& feedback-page]
|
[:> feedback-page* {:type type
|
||||||
|
:error-report-id error-report-id
|
||||||
|
:error-href error-href}]
|
||||||
|
|
||||||
:settings-password
|
:settings-password
|
||||||
[:& password-page]
|
[:& password-page]
|
||||||
|
|||||||
@@ -8,28 +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]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(def ^:private schema:feedback-form
|
(def ^:private schema:feedback-form
|
||||||
[: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}]]
|
||||||
|
[: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 [error-report type error-href]}]
|
||||||
(let [profile (mf/deref refs/profile)
|
(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
|
on-succes
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@@ -57,24 +92,47 @@
|
|||||||
(->> (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)} (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 :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*
|
[:> 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 +140,30 @@
|
|||||||
|
|
||||||
[: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 [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")))
|
||||||
|
|
||||||
[:div {:class (stl/css :dashboard-settings)}
|
(let [report (when (= error-report-id (:id errors/last-report))
|
||||||
[:div {:class (stl/css :form-container)}
|
(:content errors/last-report))
|
||||||
[:& feedback-form]]])
|
props (mf/spread-props props {:error-report report})]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :dashboard-settings)}
|
||||||
|
[:div {:class (stl/css :form-container)}
|
||||||
|
[:> feedback-form* props]]]))
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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]
|
||||||
@@ -22,12 +24,14 @@
|
|||||||
[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]
|
||||||
[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]
|
||||||
@@ -353,6 +357,19 @@
|
|||||||
(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
|
||||||
|
(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
|
on-download
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [event]
|
(fn [event]
|
||||||
@@ -362,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)
|
||||||
@@ -370,11 +388,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"
|
||||||
@@ -422,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* {}]
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user