mirror of
https://github.com/penpot/penpot.git
synced 2025-05-19 08:56:10 +02:00
🎉 Add user feedback module.
This commit is contained in:
parent
1cb18ad7cb
commit
c1a139fc51
20 changed files with 431 additions and 59 deletions
|
@ -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
Add a link
Reference in a new issue