🎉 Add user feedback module.

This commit is contained in:
Andrey Antukh 2021-02-08 22:39:11 +01:00 committed by Hirunatan
parent 1cb18ad7cb
commit c1a139fc51
20 changed files with 431 additions and 59 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View 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]]])

View file

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

View file

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

View file

@ -57,7 +57,6 @@
form))
(defn- wrap-update-fn
[f {:keys [spec validators]}]
(fn [& args]