diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index 3108fcbb2..69a093aa2 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.exceptions :as ex] [app.common.schema :as sm] + [app.common.types.plugins :refer [schema:plugin-data]] [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] @@ -40,6 +41,33 @@ (declare strip-private-attrs) (declare verify-password) +(def schema:props + [:map {:title "ProfileProps"} + [:plugins {:optional true} schema:plugin-data] + [:newsletter-updates {:optional true} ::sm/boolean] + [:newsletter-news {:optional true} ::sm/boolean] + [:onboarding-team-id {:optional true} ::sm/uuid] + [:onboarding-viewed {:optional true} ::sm/boolean] + [:v2-info-shown {:optional true} ::sm/boolean] + [:welcome-file-id {:optional true} [:maybe ::sm/boolean]] + [:release-notes-viewed {:optional true} + [::sm/text {:max 100}]]]) + +(def schema:profile + [:map {:title "Profile"} + [:id ::sm/uuid] + [:fullname [::sm/word-string {:max 250}]] + [:email ::sm/email] + [:is-active {:optional true} ::sm/boolean] + [:is-blocked {:optional true} ::sm/boolean] + [:is-demo {:optional true} ::sm/boolean] + [:is-muted {:optional true} ::sm/boolean] + [:created-at {:optional true} ::sm/inst] + [:modified-at {:optional true} ::sm/inst] + [:default-project-id {:optional true} ::sm/uuid] + [:default-team-id {:optional true} ::sm/uuid] + [:props {:optional true} schema:props]]) + (defn clean-email "Clean and normalizes email address string" [email] @@ -53,24 +81,6 @@ email)] email)) -(def ^:private - schema:profile - (sm/define - [:map {:title "Profile"} - [:id ::sm/uuid] - [:fullname [::sm/word-string {:max 250}]] - [:email ::sm/email] - [:is-active {:optional true} ::sm/boolean] - [:is-blocked {:optional true} ::sm/boolean] - [:is-demo {:optional true} ::sm/boolean] - [:is-muted {:optional true} ::sm/boolean] - [:created-at {:optional true} ::sm/inst] - [:modified-at {:optional true} ::sm/inst] - [:default-project-id {:optional true} ::sm/uuid] - [:default-team-id {:optional true} ::sm/uuid] - [:props {:optional true} - [:map-of {:title "ProfileProps"} :keyword :any]]])) - ;; --- QUERY: Get profile (own) (sv/defmethod ::get-profile @@ -351,14 +361,12 @@ :extra-data ptoken}) nil)) - ;; --- MUTATION: Update Profile Props (def ^:private schema:update-profile-props - (sm/define - [:map {:title "update-profile-props"} - [:props [:map-of :keyword :any]]])) + [:map {:title "update-profile-props"} + [:props schema:props]]) (defn update-profile-props [{:keys [::db/conn] :as cfg} profile-id props] diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index deacac12a..46e8828e0 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -11,7 +11,6 @@ [app.main.data.dashboard :as dd] [app.main.data.events :as ev] [app.main.data.modal :as modal] - [app.main.data.users :as du] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.dashboard.grid :refer [line-grid]] @@ -24,6 +23,7 @@ [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] [app.util.router :as rt] + [app.util.storage :as storage] [app.util.time :as dt] [cuerdas.core :as str] [okulary.core :as l] @@ -54,24 +54,25 @@ :data-testid "new-project-button"} (tr "dashboard.new-project")]])) -(mf/defc team-hero - {::mf/wrap [mf/memo]} - [{:keys [team close-fn] :as props}] +(mf/defc team-hero* + {::mf/wrap [mf/memo] + ::mf/props :obj} + [{:keys [team on-close]}] (let [on-nav-members-click (mf/use-fn #(st/emit! (dd/go-to-team-members))) - on-invite-click + on-invite (mf/use-fn (mf/deps team) (fn [] (st/emit! (modal/show {:type :invite-members :team team :origin :hero})))) - on-close-click + on-close' (mf/use-fn - (mf/deps close-fn) + (mf/deps on-close) (fn [event] (dom/prevent-default event) - (close-fn)))] + (on-close event)))] [:div {:class (stl/css :team-hero)} [:div {:class (stl/css :img-wrapper)} @@ -85,11 +86,11 @@ [:a {:on-click on-nav-members-click} (tr "dasboard.team-hero.management")]] [:button {:class (stl/css :btn-primary :invite) - :on-click on-invite-click} + :on-click on-invite} (tr "onboarding.choice.team-up.invite-members")]] [:button {:class (stl/css :close) - :on-click on-close-click + :on-click on-close' :aria-label (tr "labels.close")} close-icon]])) @@ -292,26 +293,27 @@ (sort-by :modified-at) (reverse)) recent-map (mf/deref recent-files-ref) - props (some-> profile (get :props {})) you-owner? (get-in team [:permissions :is-owner]) you-admin? (get-in team [:permissions :is-admin]) can-invite? (or you-owner? you-admin?) - team-hero? (and can-invite? - (:team-hero? props true) - (not (:is-default team))) + + show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true)) + show-team-hero? (deref show-team-hero*) is-my-penpot (= (:default-team-id profile) (:id team)) + is-defalt-team? (:is-default team) team-id (:id team) - close-banner + on-close (mf/use-fn (fn [] - (st/emit! (du/update-profile-props {:team-hero? false}) - (ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero" - ::ev/origin "dashboard"})))) + (reset! show-team-hero* false) + (st/emit! (ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero" + ::ev/origin "dashboard"}))))] - show-team-hero? (and (not is-my-penpot) team-hero?)] + (mf/with-effect [show-team-hero?] + (swap! storage/global assoc ::show-team-hero show-team-hero?)) (mf/with-effect [team] (let [tname (if (:is-default team) @@ -328,13 +330,18 @@ [:& header] [:div {:class (stl/css :projects-container)} [:* - (when team-hero? - [:& team-hero {:team team :close-fn close-banner}]) + (when (and show-team-hero? + can-invite? + (not is-defalt-team?)) + [:> team-hero* {:team team :on-close on-close}]) [:div {:class (stl/css-case :dashboard-container true :no-bg true :dashboard-projects true - :with-team-hero show-team-hero?)} + :with-team-hero (and (not is-my-penpot) + (not is-defalt-team?) + show-team-hero? + can-invite?))} (for [{:keys [id] :as project} projects] (let [files (when recent-map (->> (vals recent-map) diff --git a/frontend/src/app/main/ui/dashboard/templates.cljs b/frontend/src/app/main/ui/dashboard/templates.cljs index 1d6e07998..ae9a8f5be 100644 --- a/frontend/src/app/main/ui/dashboard/templates.cljs +++ b/frontend/src/app/main/ui/dashboard/templates.cljs @@ -12,7 +12,6 @@ [app.main.data.dashboard :as dd] [app.main.data.events :as ev] [app.main.data.modal :as modal] - [app.main.data.users :as du] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.icons :as i] @@ -20,6 +19,7 @@ [app.util.i18n :refer [tr]] [app.util.keyboard :as kbd] [app.util.router :as rt] + [app.util.storage :as storage] [okulary.core :as l] [potok.v2.core :as ptk] [rumext.v2 :as mf])) @@ -60,17 +60,11 @@ :template template :on-finish-import on-finish})))) -(mf/defc title - {::mf/wrap-props false} - [{:keys [collapsed]}] - (let [on-click - (mf/use-fn - (mf/deps collapsed) - (fn [_event] - (let [props {:builtin-templates-collapsed-status (not collapsed)}] - (st/emit! (du/update-profile-props props))))) - - on-key-down +(mf/defc title* + {::mf/props :obj + ::mf/private true} + [{:keys [on-click is-collapsed]}] + (let [on-key-down (mf/use-fn (mf/deps on-click) (fn [event] @@ -86,7 +80,7 @@ :on-key-down on-key-down} [:span {:class (stl/css :title-text)} (tr "dashboard.libraries-and-templates")] - (if ^boolean collapsed + (if ^boolean is-collapsed [:span {:class (stl/css :title-icon :title-icon-collapsed)} arrow-icon] [:span {:class (stl/css :title-icon)} @@ -180,8 +174,12 @@ "dashboard-project") (name route-name)) - props (:props profile) - collapsed (:builtin-templates-collapsed-status props false) + collapsed* (mf/use-state + #(get storage/global ::collapsed)) + collapsed (deref collapsed*) + + + can-move (mf/use-state {:left false :right true}) total (count templates) @@ -192,6 +190,11 @@ move-left (fn [] (dom/scroll-by! (mf/ref-val content-ref) -300 0)) move-right (fn [] (dom/scroll-by! (mf/ref-val content-ref) 300 0)) + on-toggle-collapse + (mf/use-fn + (fn [_event] + (swap! collapsed* not))) + update-can-move (fn [scroll-left scroll-available client-width] (reset! can-move {:left (> scroll-left 0) @@ -231,12 +234,15 @@ (.dispatchEvent content (js/Event. "scroll"))))) (mf/with-effect [profile collapsed] + (swap! storage/global assoc ::collapsed collapsed) + (when (and profile (not collapsed)) (st/emit! (dd/fetch-builtin-templates)))) [:div {:class (stl/css-case :dashboard-templates-section true :collapsed collapsed)} - [:& title {:collapsed collapsed}] + [:> title* {:on-click on-toggle-collapse + :is-collapsed collapsed}] [:div {:class (stl/css :content) :on-scroll on-scroll