Allow login dialog on settings

This commit is contained in:
Andrey Antukh 2025-07-08 13:18:45 +02:00
parent 7f53860296
commit 1f05511add
5 changed files with 147 additions and 124 deletions

View File

@ -26,6 +26,7 @@
- Improved text layer resizing: Allow double-click on text bounding box to set auto-width/auto-height [Taiga #11577](https://tree.taiga.io/project/penpot/issue/11577)
- Improve text layer auto-resize: auto-width switches to auto-height on horizontal resize, and only switches to fixed on vertical resize [Taiga #11578](https://tree.taiga.io/project/penpot/issue/11578)
- Highlight first font in font selector search. Apply only on Enter or click. [Taiga #11579](https://tree.taiga.io/project/penpot/issue/11579)
- Add the ability to show login dialog on profile settings [Github #6871](https://github.com/penpot/penpot/pull/6871)
### :bug: Bugs fixed

View File

@ -102,23 +102,27 @@
(print-trace! error)
(print-data! error))))
;; We receive a explicit authentication error;
;; If the uri is for workspace, dashboard or view assign the
;; exception for the 'Oops' page. Otherwise this explicitly clears
;; all profile data and redirect the user to the login page. This is
;; here and not in app.main.errors because of circular dependency.
;; We receive a explicit authentication error; If the uri is for
;; workspace, dashboard, viewer or settings, then assign the exception
;; for show the error page. Otherwise this explicitly clears all
;; profile data and redirect the user to the login page. This is here
;; and not in app.main.errors because of circular dependency.
(defmethod ptk/handle-error :authentication
[e]
(let [msg (tr "errors.auth.unable-to-login")
uri (.-href glob/location)
show-oops? (or (str/includes? uri "workspace")
(str/includes? uri "dashboard")
(str/includes? uri "view"))]
(if show-oops?
(st/async-emit! (rt/assign-exception e))
[error]
(let [message (tr "errors.auth.unable-to-login")
uri (rt/get-current-href)
show-error?
(or (str/includes? uri "workspace")
(str/includes? uri "dashboard")
(str/includes? uri "view")
(str/includes? uri "settings"))]
(if show-error?
(st/async-emit! (rt/assign-exception error))
(do
(st/emit! (da/logout))
(ts/schedule 500 #(st/emit! (ntf/warn msg)))))))
(ts/schedule 500 #(st/emit! (ntf/warn message)))))))
;; Error that happens on an active business model validation does not
;; passes an validation (example: profile can't leave a team). From

View File

@ -11,6 +11,7 @@
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.team :as dtm]
[app.main.errors :as errors]
[app.main.repo :as rp]
[app.main.router :as rt]
[app.main.store :as st]
@ -130,7 +131,10 @@
(assoc query-params :team-id (:default-team-id profile))))))
:else
(st/emit! (rt/assign-exception {:type :not-found})))))))))
(st/emit! (rt/assign-exception {:type :not-found}))))
(fn [cause]
(errors/on-error cause)))))))
(defn init-routes
[]

View File

@ -42,7 +42,7 @@
(mf/with-effect [profile]
(when (nil? profile)
(st/emit! (rt/nav :auth-login))))
(st/emit! (rt/assign-exception {:type :authentication}))))
[:*
[:> modal-container*]

View File

@ -69,9 +69,8 @@
[:div {:class (stl/css :main-message)} (tr "errors.invite-invalid")]
[:div {:class (stl/css :desc-message)} (tr "errors.invite-invalid.info")]])
(mf/defc login-dialog
{::mf/props :obj}
[{:keys [show-dialog]}]
(mf/defc login-dialog*
[]
(let [current-section (mf/use-state :login)
user-email (mf/use-state "")
register-token (mf/use-state "")
@ -94,9 +93,7 @@
success-login
(mf/use-fn
(fn []
(reset! show-dialog false)
(st/emit! (rt/reload true))))
#(st/emit! (rt/reload true)))
success-register
(mf/use-fn
@ -117,7 +114,7 @@
(reset! current-section :recovery-email-sent)))
on-nav-root
(mf/use-fn #(st/emit! (rt/nav-root)))]
(mf/use-fn #(st/emit! (rt/nav :auth-login {})))]
[:div {:class (stl/css :overlay)}
[:div {:class (stl/css :dialog-login)}
@ -203,11 +200,9 @@
[:button {:on-click on-click} button-text]]]]))
(mf/defc request-access*
[{:keys [file-id team-id is-default is-workspace]}]
(let [profile (mf/deref refs/profile)
requested* (mf/use-state {:sent false :already-requested false})
[{:keys [file-id team-id is-default is-workspace profile]}]
(let [requested* (mf/use-state {:sent false :already-requested false})
requested (deref requested*)
show-dialog (mf/use-state true)
on-close
(mf/use-fn
@ -237,90 +232,47 @@
(st/emit! (dcm/create-team-access-request
(with-meta params mdata))))))]
[:*
(if (some? file-id)
(if is-workspace
[:div {:class (stl/css :workspace)}
[:div {:class (stl/css :workspace-left)}
i/logo-icon
[:div
[:div {:class (stl/css :project-name)} (tr "not-found.no-permission.project-name")]
[:div {:class (stl/css :file-name)} (tr "not-found.no-permission.penpot-file")]]]
[:div {:class (stl/css :workspace-right)}]]
(cond
is-default
[:& request-dialog {:title (tr "not-found.no-permission.project")
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
[:div {:class (stl/css :viewer)}
;; FIXME: the viewer header was never designed to be reused
;; from other parts of the application, and this code looks
;; like a fast workaround reusing it as-is without a proper
;; component adaptation for be able to use it easily it on
;; viewer context or static error page context
[:& viewer.header/header {:project
{:name (tr "not-found.no-permission.project-name")}
:index 0
:file {:name (tr "not-found.no-permission.penpot-file")}
:page nil
:frame nil
:permissions {:is-logged true}
:zoom 1
:section :interactions
:shown-thumbnails false
:interactions-mode nil}]])
(and (some? file-id) (:already-requested requested))
[:& request-dialog {:title (tr "not-found.no-permission.already-requested.file")
:content [(tr "not-found.no-permission.already-requested.or-others.file")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
[:div {:class (stl/css :dashboard)}
[:div {:class (stl/css :dashboard-sidebar)}
[:> sidebar*
{:team nil
:projects []
:project (:default-project-id profile)
:profile profile
:section :dashboard-projects
:search-term ""}]]])
(:already-requested requested)
[:& request-dialog {:title (tr "not-found.no-permission.already-requested.project")
:content [(tr "not-found.no-permission.already-requested.or-others.project")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(when @show-dialog
(cond
(nil? profile)
[:& login-dialog {:show-dialog show-dialog}]
(:sent requested)
[:& request-dialog {:title (tr "not-found.no-permission.done.success")
:content [(tr "not-found.no-permission.done.remember")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
is-default
[:& request-dialog {:title (tr "not-found.no-permission.project")
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(some? file-id)
[:& request-dialog {:title (tr "not-found.no-permission.file")
:content [(tr "not-found.no-permission.you-can-ask.file")
(tr "not-found.no-permission.if-approves")]
:button-text (tr "not-found.no-permission.ask")
:on-button-click on-request-access
:cancel-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(and (some? file-id) (:already-requested requested))
[:& request-dialog {:title (tr "not-found.no-permission.already-requested.file")
:content [(tr "not-found.no-permission.already-requested.or-others.file")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(:already-requested requested)
[:& request-dialog {:title (tr "not-found.no-permission.already-requested.project")
:content [(tr "not-found.no-permission.already-requested.or-others.project")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(:sent requested)
[:& request-dialog {:title (tr "not-found.no-permission.done.success")
:content [(tr "not-found.no-permission.done.remember")]
:button-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(some? file-id)
[:& request-dialog {:title (tr "not-found.no-permission.file")
:content [(tr "not-found.no-permission.you-can-ask.file")
(tr "not-found.no-permission.if-approves")]
:button-text (tr "not-found.no-permission.ask")
:on-button-click on-request-access
:cancel-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]
(some? team-id)
[:& request-dialog {:title (tr "not-found.no-permission.project")
:content [(tr "not-found.no-permission.you-can-ask.project")
(tr "not-found.no-permission.if-approves")]
:button-text (tr "not-found.no-permission.ask")
:on-button-click on-request-access
:cancel-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}]))]))
(some? team-id)
[:& request-dialog {:title (tr "not-found.no-permission.project")
:content [(tr "not-found.no-permission.you-can-ask.project")
(tr "not-found.no-permission.if-approves")]
:button-text (tr "not-found.no-permission.ask")
:on-button-click on-request-access
:cancel-text (tr "not-found.no-permission.go-dashboard")
:on-close on-close}])))
(mf/defc not-found*
[]
@ -484,29 +436,77 @@
[:> internal-error* props])))
(mf/defc context-wrapper*
[{:keys [is-workspace is-dashboard is-viewer profile children]}]
[:*
(cond
is-workspace
[:div {:class (stl/css :workspace)}
[:div {:class (stl/css :workspace-left)}
i/logo-icon
[:div
[:div {:class (stl/css :project-name)} (tr "not-found.no-permission.project-name")]
[:div {:class (stl/css :file-name)} (tr "not-found.no-permission.penpot-file")]]]
[:div {:class (stl/css :workspace-right)}]]
is-viewer
[:div {:class (stl/css :viewer)}
;; FIXME: the viewer header was never designed to be reused
;; from other parts of the application, and this code looks
;; like a fast workaround reusing it as-is without a proper
;; component adaptation for be able to use it easily it on
;; viewer context or static error page context
[:& viewer.header/header {:project
{:name (tr "not-found.no-permission.project-name")}
:index 0
:file {:name (tr "not-found.no-permission.penpot-file")}
:page nil
:frame nil
:permissions {:is-logged true}
:zoom 1
:section :interactions
:shown-thumbnails false
:interactions-mode nil}]]
is-dashboard
[:div {:class (stl/css :dashboard)}
[:div {:class (stl/css :dashboard-sidebar)}
[:> sidebar*
{:team nil
:projects []
:project (:default-project-id profile)
:profile profile
:section :dashboard-projects
:search-term ""}]]])
children])
(mf/defc exception-page*
{::mf/props :obj}
[{:keys [data route] :as props}]
(let [type (:type data)
path (:path route)
(let [type (:type data)
path (:path route)
params (:query-params route)
params (:query-params route)
workspace? (str/includes? path "workspace")
dashboard? (str/includes? path "dashboard")
view? (str/includes? path "view")
workspace? (str/includes? path "workspace")
dashboard? (str/includes? path "dashboard")
view? (str/includes? path "view")
;; We store the request access info int this state
info* (mf/use-state nil)
info (deref info*)
info* (mf/use-state nil)
info (deref info*)
loaded? (get info :loaded false)
loaded? (get info :loaded false)
profile (mf/deref refs/profile)
auth-error?
(= type :authentication)
request-access?
(and
(or (= type :not-found)
(= type :authentication))
(or (= type :not-found) auth-error?)
(or workspace? dashboard? view?)
(or (:file-id info)
(:team-id info)))]
@ -517,11 +517,25 @@
(rx/subs! (partial reset! info*)
(partial reset! info* {:loaded true})))))
(when loaded?
(if request-access?
[:> request-access* {:file-id (:file-id info)
:team-id (:team-id info)
:is-default (:team-default info)
:is-workspace workspace?}]
[:> exception-section* props]))))
(if auth-error?
[:> context-wrapper*
{:is-workspace workspace?
:is-dashboard dashboard?
:is-viewer view?
:profile profile}
[:> login-dialog* {}]]
(when loaded?
(if request-access?
[:> context-wrapper* {:is-workspace workspace?
:is-dashboard dashboard?
:is-viewer view?
:profile profile}
[:> request-access* {:file-id (:file-id info)
:team-id (:team-id info)
:is-default (:team-default info)
:is-workspace workspace?}]]
[:> exception-section* props])))))