mirror of
https://github.com/penpot/penpot.git
synced 2025-01-21 06:02:32 -05:00
🎉 Add user feedback module.
This commit is contained in:
parent
1cb18ad7cb
commit
c1a139fc51
20 changed files with 431 additions and 59 deletions
|
@ -1,5 +1,6 @@
|
|||
{:lint-as {potok.core/reify clojure.core/reify
|
||||
promesa.core/let clojure.core/let
|
||||
rumext.alpha/defc clojure.core/defn
|
||||
app.db/with-atomic clojure.core/with-open}
|
||||
:output
|
||||
{:exclude-files ["data_readers.clj"]}
|
||||
|
|
1
backend/resources/emails/feedback/en.subj
Normal file
1
backend/resources/emails/feedback/en.subj
Normal file
|
@ -0,0 +1 @@
|
|||
[FEEDBACK]: From {{ profile.email }}
|
7
backend/resources/emails/feedback/en.txt
Normal file
7
backend/resources/emails/feedback/en.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
Feedback from: {{profile.fullname}} <{{profile.email}}>
|
||||
|
||||
Profile ID: {{profile.id}}
|
||||
|
||||
Subject: {{subject}}
|
||||
|
||||
{{content}}
|
|
@ -40,6 +40,9 @@
|
|||
:storage-s3-region :eu-central-1
|
||||
:storage-s3-bucket "penpot-devenv-assets-pre"
|
||||
|
||||
:feedback-destination "info@example.com"
|
||||
:feedback-enabled false
|
||||
|
||||
:assets-path "/internal/assets/"
|
||||
|
||||
:rlimits-password 10
|
||||
|
@ -93,7 +96,11 @@
|
|||
(s/def ::media-directory ::us/string)
|
||||
(s/def ::asserts-enabled ::us/boolean)
|
||||
|
||||
(s/def ::feedback-enabled ::us/boolean)
|
||||
(s/def ::feedback-destination ::us/string)
|
||||
|
||||
(s/def ::error-report-webhook ::us/string)
|
||||
|
||||
(s/def ::smtp-enabled ::us/boolean)
|
||||
(s/def ::smtp-default-reply-to ::us/string)
|
||||
(s/def ::smtp-default-from ::us/string)
|
||||
|
@ -156,6 +163,8 @@
|
|||
::database-username
|
||||
::default-blob-version
|
||||
::error-report-webhook
|
||||
::feedback-enabled
|
||||
::feedback-destination
|
||||
::github-client-id
|
||||
::github-client-secret
|
||||
::gitlab-base-uri
|
||||
|
|
|
@ -43,6 +43,16 @@
|
|||
|
||||
;; --- Emails
|
||||
|
||||
(s/def ::subject ::us/string)
|
||||
(s/def ::content ::us/string)
|
||||
|
||||
(s/def ::feedback
|
||||
(s/keys :req-un [::subject ::content]))
|
||||
|
||||
(def feedback
|
||||
"A profile feedback email."
|
||||
(emails/template-factory ::feedback default-context))
|
||||
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::register
|
||||
(s/keys :req-un [::name]))
|
||||
|
|
|
@ -126,6 +126,7 @@
|
|||
'app.rpc.mutations.projects
|
||||
'app.rpc.mutations.viewer
|
||||
'app.rpc.mutations.teams
|
||||
'app.rpc.mutations.feedback
|
||||
'app.rpc.mutations.verify-token)
|
||||
(map (partial process-method cfg))
|
||||
(into {}))))
|
||||
|
|
41
backend/src/app/rpc/mutations/feedback.clj
Normal file
41
backend/src/app/rpc/mutations/feedback.clj
Normal file
|
@ -0,0 +1,41 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2021 UXBOX Labs SL
|
||||
|
||||
(ns app.rpc.mutations.feedback
|
||||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cfg]
|
||||
[app.db :as db]
|
||||
[app.emails :as emails]
|
||||
[app.rpc.queries.profile :as profile]
|
||||
[app.util.services :as sv]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
(s/def ::subject ::us/string)
|
||||
(s/def ::content ::us/string)
|
||||
|
||||
(s/def ::send-profile-feedback
|
||||
(s/keys :req-un [::profile-id ::subject ::content]))
|
||||
|
||||
(sv/defmethod ::send-profile-feedback
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id subject content] :as params}]
|
||||
(when-not (:feedback-enabled cfg/config)
|
||||
(ex/raise :type :validation
|
||||
:code :feedback-disabled
|
||||
:hint "feedback module is disabled"))
|
||||
|
||||
(db/with-atomic [conn pool]
|
||||
(let [profile (profile/retrieve-profile-data conn profile-id)]
|
||||
(emails/send! conn emails/feedback
|
||||
{:to (:feedback-destination cfg/config)
|
||||
:profile profile
|
||||
:subject subject
|
||||
:content content})
|
||||
nil)))
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
(ns app.util.emails
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.spec :as us]
|
||||
[app.util.template :as tmpl]
|
||||
|
@ -196,15 +197,17 @@
|
|||
text (render-email-template-part :txt id context)
|
||||
html (render-email-template-part :html id context)]
|
||||
(when (or (not subj)
|
||||
(not text)
|
||||
(not html))
|
||||
(and (not text)
|
||||
(not html)))
|
||||
(ex/raise :type :internal
|
||||
:code :missing-email-templates))
|
||||
{:subject subj
|
||||
:body [{:type "text/plain"
|
||||
:content text}
|
||||
{:type "text/html"
|
||||
:content html}]}))
|
||||
:body (d/concat
|
||||
[{:type "text/plain"
|
||||
:content text}]
|
||||
(when html
|
||||
[{:type "text/html"
|
||||
:content html}]))}))
|
||||
|
||||
(s/def ::priority #{:high :low})
|
||||
(s/def ::to (s/or :sigle ::us/email
|
||||
|
|
|
@ -974,6 +974,78 @@
|
|||
"es" : "La contraseña anterior no es correcta"
|
||||
}
|
||||
},
|
||||
"feedback.chat-start" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:112" ],
|
||||
"translations" : {
|
||||
"en" : "Join the chat",
|
||||
"es" : "Unirse al chat"
|
||||
}
|
||||
},
|
||||
"feedback.chat-subtitle" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:109" ],
|
||||
"translations" : {
|
||||
"en" : "Feeling like talking? Chat with us at Gitter",
|
||||
"es" : "¿Deseas conversar? Entra al nuestro chat de la comunidad en Gitter"
|
||||
}
|
||||
},
|
||||
"feedback.description" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:88" ],
|
||||
"translations" : {
|
||||
"en" : "Description",
|
||||
"es" : "Descripción"
|
||||
}
|
||||
},
|
||||
"feedback.discussions-go-to" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:104" ],
|
||||
"translations" : {
|
||||
"en" : "Go to discussions",
|
||||
"es" : "Ir a las discussiones"
|
||||
}
|
||||
},
|
||||
"feedback.discussions-subtitle1" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:99" ],
|
||||
"translations" : {
|
||||
"en" : "Join Penpot team collaborative communication forum.",
|
||||
"es" : "Entra al foro colaborativo de Penpot"
|
||||
}
|
||||
},
|
||||
"feedback.discussions-subtitle2" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:100" ],
|
||||
"translations" : {
|
||||
"en" : "You can ask and answer questions, have open-ended conversations, and follow along on decisions affecting the project.",
|
||||
"es" : ""
|
||||
}
|
||||
},
|
||||
"feedback.discussions-title" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:98" ],
|
||||
"translations" : {
|
||||
"en" : "Team discussions",
|
||||
"es" : ""
|
||||
}
|
||||
},
|
||||
"feedback.subject" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:84" ],
|
||||
"translations" : {
|
||||
"en" : "Subject",
|
||||
"es" : "Asunto"
|
||||
}
|
||||
},
|
||||
"feedback.subtitle" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:81" ],
|
||||
"translations" : {
|
||||
"en" : "Please describe the reason of your email, specifying if is an issue, an idea or a doubt. A member of our team will respond as soon as possible.",
|
||||
"es" : ""
|
||||
}
|
||||
},
|
||||
"feedback.title" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:80" ],
|
||||
"translations" : {
|
||||
"en" : "Email",
|
||||
"fr" : "Adresse email",
|
||||
"ru" : "Email",
|
||||
"es" : "Correo electrónico"
|
||||
}
|
||||
},
|
||||
"generic.error" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/password.cljs:31" ],
|
||||
"translations" : {
|
||||
|
@ -1605,7 +1677,7 @@
|
|||
"es" : "Correo electrónico"
|
||||
}
|
||||
},
|
||||
"labels.feedback" : {
|
||||
"labels.give-feedback" : {
|
||||
"used-in" : [ "src/app/main/ui/workspace/header.cljs:231", "src/app/main/ui/dashboard/sidebar.cljs:471" ],
|
||||
"translations" : {
|
||||
"en" : "Give feedback",
|
||||
|
@ -1823,6 +1895,37 @@
|
|||
"es" : "Cargo"
|
||||
}
|
||||
},
|
||||
"labels.send" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:93" ],
|
||||
"translations" : {
|
||||
"en" : "Send",
|
||||
"es" : "Enviar"
|
||||
}
|
||||
},
|
||||
"labels.sending" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs:93" ],
|
||||
"translations" : {
|
||||
"en" : "Sending...",
|
||||
"es" : "Enviando..."
|
||||
}
|
||||
},
|
||||
|
||||
"labels.feedback-disabled" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs" ],
|
||||
"translations" : {
|
||||
"en" : "Feedback disabled",
|
||||
"es" : "El modulo de recepción de opiniones esta deshabilitado."
|
||||
}
|
||||
},
|
||||
|
||||
"labels.feedback-sent" : {
|
||||
"used-in" : [ "src/app/main/ui/settings/feedback.cljs" ],
|
||||
"translations" : {
|
||||
"en" : "Feedback sent",
|
||||
"es" : "Opinión enviada"
|
||||
}
|
||||
},
|
||||
|
||||
"labels.service-unavailable.desc-message" : {
|
||||
"used-in" : [ "src/app/main/ui/static.cljs:75" ],
|
||||
"translations" : {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
min-width: 25px;
|
||||
padding: 0 1rem;
|
||||
transition: all .4s;
|
||||
text-decoration: none !important;
|
||||
svg {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
|
|
|
@ -102,6 +102,14 @@ textarea {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $color-gray-60;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: $color-gray-20;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
font-size: $fs14;
|
||||
|
@ -131,7 +139,8 @@ textarea {
|
|||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
input,
|
||||
textarea {
|
||||
background-color: $color-white;
|
||||
border-radius: 2px;
|
||||
border: 1px solid $color-gray-20;
|
||||
|
@ -143,6 +152,13 @@ textarea {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: auto;
|
||||
font-size: $fs14;
|
||||
font-family: "worksans", sans-serif;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
// Makes the background for autocomplete white
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
(def default-language "en")
|
||||
|
||||
(def demo-warning (obj/get global "penpotDemoWarning" false))
|
||||
(def feedback-enabled (obj/get global "penpotFeedbackEnabled" false))
|
||||
(def allow-demo-users (obj/get global "penpotAllowDemoUsers" true))
|
||||
(def google-client-id (obj/get global "penpotGoogleClientID" nil))
|
||||
(def gitlab-client-id (obj/get global "penpotGitlabClientID" nil))
|
||||
|
|
|
@ -59,17 +59,18 @@
|
|||
|
||||
(def routes
|
||||
[["/auth"
|
||||
["/login" :auth-login]
|
||||
["/register" :auth-register]
|
||||
["/login" :auth-login]
|
||||
["/register" :auth-register]
|
||||
["/register/success" :auth-register-success]
|
||||
["/recovery/request" :auth-recovery-request]
|
||||
["/recovery" :auth-recovery]
|
||||
["/verify-token" :auth-verify-token]]
|
||||
["/recovery" :auth-recovery]
|
||||
["/verify-token" :auth-verify-token]]
|
||||
|
||||
["/settings"
|
||||
["/profile" :settings-profile]
|
||||
["/profile" :settings-profile]
|
||||
["/password" :settings-password]
|
||||
["/options" :settings-options]]
|
||||
["/feedback" :settings-feedback]
|
||||
["/options" :settings-options]]
|
||||
|
||||
["/view/:file-id/:page-id"
|
||||
{:name :viewer
|
||||
|
@ -89,11 +90,11 @@
|
|||
["/render-object/:file-id/:page-id/:object-id" :render-object]
|
||||
|
||||
["/dashboard/team/:team-id"
|
||||
["/members" :dashboard-team-members]
|
||||
["/settings" :dashboard-team-settings]
|
||||
["/projects" :dashboard-projects]
|
||||
["/search" :dashboard-search]
|
||||
["/libraries" :dashboard-libraries]
|
||||
["/members" :dashboard-team-members]
|
||||
["/settings" :dashboard-team-settings]
|
||||
["/projects" :dashboard-projects]
|
||||
["/search" :dashboard-search]
|
||||
["/libraries" :dashboard-libraries]
|
||||
["/projects/:project-id" :dashboard-files]]
|
||||
|
||||
["/workspace/:project-id/:file-id" :workspace]])
|
||||
|
@ -121,7 +122,8 @@
|
|||
|
||||
(:settings-profile
|
||||
:settings-password
|
||||
:settings-options)
|
||||
:settings-options
|
||||
:settings-feedback)
|
||||
[:& settings/settings {:route route}]
|
||||
|
||||
:debug-icons-preview
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
[app.main.ui.icons :as i]
|
||||
[app.util.object :as obj]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :as i18n :refer [t]]
|
||||
[app.util.i18n :as i18n :refer [t tr]]
|
||||
["react" :as react]
|
||||
[app.util.dom :as dom]))
|
||||
|
||||
|
@ -28,7 +28,6 @@
|
|||
|
||||
type' (mf/use-state type)
|
||||
focus? (mf/use-state false)
|
||||
locale (mf/deref i18n/locale)
|
||||
|
||||
touched? (get-in @form [:touched name])
|
||||
error (get-in @form [:errors name])
|
||||
|
@ -94,7 +93,59 @@
|
|||
help-icon'])
|
||||
(cond
|
||||
(and touched? (:message error))
|
||||
[:span.error (t locale (:message error))]
|
||||
[:span.error (tr (:message error))]
|
||||
|
||||
(string? hint)
|
||||
[:span.hint hint])]]))
|
||||
|
||||
|
||||
(mf/defc textarea
|
||||
[{:keys [label disabled name form hint trim] :as props}]
|
||||
(let [form (or form (mf/use-ctx form-ctx))
|
||||
|
||||
type' (mf/use-state type)
|
||||
focus? (mf/use-state false)
|
||||
|
||||
touched? (get-in @form [:touched name])
|
||||
error (get-in @form [:errors name])
|
||||
|
||||
value (get-in @form [:data name] "")
|
||||
|
||||
klass (dom/classnames
|
||||
:focus @focus?
|
||||
:valid (and touched? (not error))
|
||||
:invalid (and touched? error)
|
||||
:disabled disabled
|
||||
;; :empty (str/empty? value)
|
||||
)
|
||||
|
||||
on-focus #(reset! focus? true)
|
||||
on-change (fm/on-input-change form name trim)
|
||||
|
||||
on-blur
|
||||
(fn [event]
|
||||
(reset! focus? false)
|
||||
(when-not (get-in @form [:touched name])
|
||||
(swap! form assoc-in [:touched name] true)))
|
||||
|
||||
props (-> props
|
||||
(dissoc :help-icon :form :trim)
|
||||
(assoc :value value
|
||||
:on-focus on-focus
|
||||
:on-blur on-blur
|
||||
;; :placeholder label
|
||||
:on-change on-change
|
||||
:type @type')
|
||||
(obj/clj->props))]
|
||||
|
||||
[:div.custom-input
|
||||
{:class klass}
|
||||
[:*
|
||||
[:label label]
|
||||
[:> :textarea props]
|
||||
(cond
|
||||
(and touched? (:message error))
|
||||
[:span.error (tr (:message error))]
|
||||
|
||||
(string? hint)
|
||||
[:span.hint hint])]]))
|
||||
|
|
|
@ -466,10 +466,12 @@
|
|||
[:li {:on-click (partial on-click (da/logout))}
|
||||
[:span.icon i/exit]
|
||||
[:span.text (t locale "labels.logout")]]
|
||||
[:li.feedback {:on-click #(.open js/window "https://github.com/penpot/penpot/discussions" "_blank")}
|
||||
[:span.icon i/msg-info]
|
||||
[:span.text (t locale "labels.feedback")]
|
||||
[:span.primary-badge "ALPHA"]]]]]
|
||||
|
||||
(when cfg/feedback-enabled
|
||||
[:li.feedback {:on-click (partial on-click :settings-feedback)}
|
||||
[:span.icon i/msg-info]
|
||||
[:span.text (t locale "labels.give-feedback")]
|
||||
[:span.primary-badge "ALPHA"]])]]]
|
||||
|
||||
(when (and team profile)
|
||||
[:& comments-section {:profile profile
|
||||
|
|
|
@ -5,30 +5,31 @@
|
|||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.settings
|
||||
(:require
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.settings.options :refer [options-page]]
|
||||
[app.main.ui.settings.feedback :refer [feedback-page]]
|
||||
[app.main.ui.settings.password :refer [password-page]]
|
||||
[app.main.ui.settings.profile :refer [profile-page]]
|
||||
[app.main.ui.settings.sidebar :refer [sidebar]]
|
||||
[app.main.ui.settings.change-email]
|
||||
[app.main.ui.settings.delete-account]
|
||||
[app.util.i18n :as i18n :refer [t]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc header
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [locale] :as props}]
|
||||
[]
|
||||
(let [logout (constantly nil)]
|
||||
[:header.dashboard-header
|
||||
[:div.dashboard-title
|
||||
[:h1 (t locale "dashboard.your-account-title")]]
|
||||
[:h1 (tr "dashboard.your-account-title")]]
|
||||
[:a.btn-secondary.btn-small {:on-click logout}
|
||||
(t locale "labels.logout")]]))
|
||||
(tr "labels.logout")]]))
|
||||
|
||||
(mf/defc settings
|
||||
[{:keys [route] :as props}]
|
||||
|
@ -41,12 +42,15 @@
|
|||
:section section}]
|
||||
|
||||
[:div.dashboard-content
|
||||
[:& header {:locale locale}]
|
||||
[:& header]
|
||||
[:section.dashboard-container
|
||||
(case section
|
||||
:settings-profile
|
||||
[:& profile-page {:locale locale}]
|
||||
|
||||
:settings-feedback
|
||||
[:& feedback-page]
|
||||
|
||||
:settings-password
|
||||
[:& password-page {:locale locale}]
|
||||
|
||||
|
|
120
frontend/src/app/main/ui/settings/feedback.cljs
Normal file
120
frontend/src/app/main/ui/settings/feedback.cljs
Normal file
|
@ -0,0 +1,120 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.settings.feedback
|
||||
"Feedback form."
|
||||
(:require
|
||||
[app.common.spec :as us]
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[beicon.core :as rx]
|
||||
[app.main.repo :as rp]
|
||||
[cljs.spec.alpha :as s]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(s/def ::content ::us/not-empty-string)
|
||||
(s/def ::subject ::us/not-empty-string)
|
||||
|
||||
(s/def ::feedback-form
|
||||
(s/keys :req-un [::subject ::content]))
|
||||
|
||||
(defn- on-error
|
||||
[form error]
|
||||
(st/emit! (dm/error (tr "errors.generic"))))
|
||||
|
||||
(defn- on-success
|
||||
[form]
|
||||
(st/emit! (dm/success (tr "notifications.profile-saved"))))
|
||||
|
||||
|
||||
(mf/defc options-form
|
||||
[]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
form (fm/use-form :spec ::feedback-form)
|
||||
|
||||
loading (mf/use-state false)
|
||||
|
||||
on-succes
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
(fn [event]
|
||||
(st/emit! (dm/success (tr "labels.feedback-sent")))
|
||||
(swap! form assoc :data {} :touched {} :errors {})))
|
||||
|
||||
on-error
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
(fn [{:keys [code] :as error}]
|
||||
(reset! loading false)
|
||||
(if (= code :feedbck-disabled)
|
||||
(st/emit! (dm/error (tr "labels.feedback-disabled")))
|
||||
(st/emit! (dm/error (tr "errors.generic"))))))
|
||||
|
||||
on-submit
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
(fn [form event]
|
||||
(reset! loading true)
|
||||
(let [data (:clean-data @form)]
|
||||
(prn "on-submit" data)
|
||||
(->> (rp/mutation! :send-profile-feedback data)
|
||||
(rx/subs on-succes on-error #(reset! loading false))))))]
|
||||
|
||||
[:& fm/form {:class "feedback-form"
|
||||
:on-submit on-submit
|
||||
:form form}
|
||||
|
||||
;; --- Feedback section
|
||||
[:h2 (tr "feedback.title")]
|
||||
[:p (tr "feedback.subtitle")]
|
||||
|
||||
[:div.fields-row
|
||||
[:& fm/input {:label (tr "feedback.subject")
|
||||
:name :subject}]]
|
||||
[:div.fields-row
|
||||
[:& fm/textarea
|
||||
{:label (tr "feedback.description")
|
||||
:name :content
|
||||
:rows 5}]]
|
||||
|
||||
[:& fm/submit-button
|
||||
{:label (if @loading (tr "labels.sending") (tr "labels.send"))
|
||||
:disabled @loading}]
|
||||
|
||||
[:hr]
|
||||
|
||||
[:h2 (tr "feedback.discussions-title")]
|
||||
[:p (tr "feedback.discussions-subtitle1")]
|
||||
[:p (tr "feedback.discussions-subtitle2")]
|
||||
|
||||
[:a.btn-secondary.btn-large
|
||||
{:href "https://github.com/penpot/penpot/discussions" :target "_blank"}
|
||||
(tr "feedback.discussions-go-to")]
|
||||
|
||||
[:hr]
|
||||
|
||||
[:h2 "Gitter"]
|
||||
[:p (tr "feedback.chat-subtitle")]
|
||||
[:a.btn-secondary.btn-large
|
||||
{:href "https://gitter.im/penpot/community" :target "_blank"}
|
||||
(tr "feedback.chat-start")]
|
||||
|
||||
]))
|
||||
|
||||
(mf/defc feedback-page
|
||||
[]
|
||||
[:div.dashboard-settings
|
||||
[:div.form-container
|
||||
[:& options-form]]])
|
|
@ -9,31 +9,20 @@
|
|||
|
||||
(ns app.main.ui.settings.sidebar
|
||||
(:require
|
||||
[app.common.spec :as us]
|
||||
[app.main.data.auth :as da]
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.refs :as refs]
|
||||
[app.config :as cfg]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.dashboard.sidebar :refer [profile-section]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.i18n :as i18n :refer [t tr]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[goog.functions :as f]
|
||||
[okulary.core :as l]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc sidebar-content
|
||||
[{:keys [locale profile section] :as props}]
|
||||
[{:keys [profile section] :as props}]
|
||||
(let [profile? (= section :settings-profile)
|
||||
password? (= section :settings-password)
|
||||
options? (= section :settings-options)
|
||||
feedback? (= section :settings-feedback)
|
||||
|
||||
go-dashboard
|
||||
(mf/use-callback
|
||||
|
@ -45,6 +34,11 @@
|
|||
(mf/deps profile)
|
||||
(st/emitf (rt/nav :settings-profile)))
|
||||
|
||||
go-settings-feedback
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
(st/emitf (rt/nav :settings-feedback)))
|
||||
|
||||
go-settings-password
|
||||
(mf/use-callback
|
||||
(mf/deps profile)
|
||||
|
@ -59,7 +53,7 @@
|
|||
[:div.sidebar-content-section
|
||||
[:div.back-to-dashboard {:on-click go-dashboard}
|
||||
[:span.icon i/arrow-down]
|
||||
[:span.text (t locale "labels.dashboard")]]]
|
||||
[:span.text (tr "labels.dashboard")]]]
|
||||
[:hr]
|
||||
|
||||
[:div.sidebar-content-section
|
||||
|
@ -67,25 +61,30 @@
|
|||
[:li {:class (when profile? "current")
|
||||
:on-click go-settings-profile}
|
||||
i/user
|
||||
[:span.element-title (t locale "labels.profile")]]
|
||||
[:span.element-title (tr "labels.profile")]]
|
||||
|
||||
[:li {:class (when password? "current")
|
||||
:on-click go-settings-password}
|
||||
i/lock
|
||||
[:span.element-title (t locale "labels.password")]]
|
||||
[:span.element-title (tr "labels.password")]]
|
||||
|
||||
[:li {:class (when options? "current")
|
||||
:on-click go-settings-options}
|
||||
i/tree
|
||||
[:span.element-title (t locale "labels.settings")]]]]]))
|
||||
[:span.element-title (tr "labels.settings")]]
|
||||
|
||||
(when cfg/feedback-enabled
|
||||
[:li {:class (when feedback? "current")
|
||||
:on-click go-settings-feedback}
|
||||
i/msg-info
|
||||
[:span.element-title (tr "labels.give-feedback")]])]]]))
|
||||
|
||||
(mf/defc sidebar
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [profile locale section]}]
|
||||
[:div.dashboard-sidebar.settings
|
||||
[:div.sidebar-inside
|
||||
[:& sidebar-content {:locale locale
|
||||
:profile profile
|
||||
[:& sidebar-content {:profile profile
|
||||
:section section}]
|
||||
[:& profile-section {:profile profile
|
||||
:locale locale}]]])
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.workspace.header
|
||||
(:require
|
||||
|
@ -227,9 +227,10 @@
|
|||
[:li {:on-click on-add-shared}
|
||||
[:span (tr "dashboard.add-shared")]])
|
||||
|
||||
[:li.feedback {:on-click #(.open js/window "https://github.com/penpot/penpot/discussions" "_blank")}
|
||||
[:span (tr "labels.feedback")]
|
||||
[:span.primary-badge "ALPHA"]]
|
||||
(when cfg/feedback-enabled
|
||||
[:li.feedback {:on-click (st/emitf (rt/nav :settings-feedback))}
|
||||
[:span (tr "labels.give-feedback")]
|
||||
[:span.primary-badge "ALPHA"]])
|
||||
]]]))
|
||||
|
||||
;; --- Header Component
|
||||
|
|
|
@ -57,7 +57,6 @@
|
|||
|
||||
form))
|
||||
|
||||
|
||||
(defn- wrap-update-fn
|
||||
[f {:keys [spec validators]}]
|
||||
(fn [& args]
|
||||
|
|
Loading…
Add table
Reference in a new issue