🐛 Show request access modal on viewer and dashboard urls (#12456)

The exception page compared the screen query param against literals
that never occur ("view", "dashboard"), so viewer and dashboard urls
of inaccessible files and teams fell through to the plain not-found
page instead of the request access modal. Classify the param with a
tested helper that recognizes "viewer" and every "dashboard-*" route.

Closes #11224

AI-assisted-by: muse-spark-1.3-contributor
This commit is contained in:
Andrey Antukh 2026-10-19 22:33:10 +02:00 committed by GitHub
parent e94e148ac6
commit 4635eaa467
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 82 additions and 4 deletions

View File

@ -34,6 +34,7 @@
[app.util.timers :as tm]
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
;; FIXME: this is a workaround until we export this class on beicon library
@ -608,16 +609,37 @@
[:> internal-error* props])))
(defn screen-kind
"Classify the `screen` query param for the exception page.
The param carries the route name (`?screen=<name>&params`), so the
viewer is \"viewer\" and every team dashboard is \"dashboard-*\". Returns
`:workspace`, `:dashboard`, `:viewer`, or nil when the screen is not a
request-access candidate. Reads the param with `rt/get-query-param`
because a repeated param arrives as a vector."
[query-params]
(let [screen (rt/get-query-param query-params :screen)]
(cond
(= screen "workspace")
:workspace
(and (string? screen)
(str/starts-with? screen "dashboard"))
:dashboard
(= screen "viewer")
:viewer)))
(mf/defc exception-page*
[{:keys [data route] :as props}]
(let [type (:type data)
params (:query-params route)
screen (get params :screen)
workspace? (= screen "workspace")
dashboard? (= screen "dashboard")
view? (= screen "view")
kind (screen-kind params)
workspace? (= kind :workspace)
dashboard? (= kind :dashboard)
view? (= kind :viewer)
;; We store the request access info int this state
info* (mf/use-state nil)

View File

@ -125,6 +125,7 @@
[frontend-tests.ui.shape-filters-test]
[frontend-tests.ui.shortcuts-labels-test]
[frontend-tests.ui.sidebar-scroll-test]
[frontend-tests.ui.static-test]
[frontend-tests.ui.stroke-menu-test]
[frontend-tests.ui.workspace-libraries-test]
[frontend-tests.util-clipboard-test]
@ -272,6 +273,7 @@
'frontend-tests.ui.shape-filters-test
'frontend-tests.ui.shortcuts-labels-test
'frontend-tests.ui.sidebar-scroll-test
'frontend-tests.ui.static-test
'frontend-tests.ui.stroke-menu-test
'frontend-tests.ui.workspace-libraries-test
'frontend-tests.util-clipboard-test

View File

@ -0,0 +1,54 @@
;; 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/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns frontend-tests.ui.static-test
(:require
[app.main.ui.static :as static]
[cljs.test :as t :include-macros true]))
(t/deftest screen-kind-classifies-known-screens
;; Regression test (penpot/penpot#11224): the exception page shows the
;; REQUEST ACCESS modal only when it recognizes the screen the failing
;; URL belongs to. The `screen` query param carries the route name, so
;; the viewer is "viewer" and every team dashboard is "dashboard-*".
(t/testing "workspace"
(t/is (= :workspace (static/screen-kind {:screen "workspace"}))))
(t/testing "bare dashboard prefix also classifies"
;; No such route exists (routes are always "dashboard-*"), and the
;; modal still needs loaded team/file info, so this is harmless and
;; matches the prefix convention used in routes.cljs.
(t/is (= :dashboard (static/screen-kind {:screen "dashboard"}))))
(t/testing "viewer"
(t/is (= :viewer (static/screen-kind {:screen "viewer"}))))
(t/testing "dashboard variants"
(doseq [screen ["dashboard-recent"
"dashboard-members"
"dashboard-files"
"dashboard-settings"]]
(t/is (= :dashboard (static/screen-kind {:screen screen}))
(str "screen: " screen)))))
(t/deftest screen-kind-ignores-unknown-screens
;; Unknown, missing, or auth screens are not request-access candidates:
;; the exception page must keep showing the plain not-found page.
(doseq [params [nil
{}
{:screen "auth-login"}
{:screen "settings-profile"}
;; The "view" literal never was a real screen name; the
;; exception page compared against it before the fix.
{:screen "view"}]]
(t/is (nil? (static/screen-kind params))
(str "params: " (pr-str params)))))
(t/deftest screen-kind-tolerates-duplicate-screen-params
;; A repeated query param arrives as a vector; the classifier reads the
;; scalar the same way the router does instead of crashing on it.
(t/is (= :dashboard
(static/screen-kind {:screen ["dashboard-recent" "dashboard-recent"]}))))