diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index 2a1c1cd40..febc707de 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -10,20 +10,25 @@ (ns app.http.errors "A errors handling for the http server." (:require + [clojure.pprint :refer [pprint]] [app.common.exceptions :as ex] [clojure.tools.logging :as log] [cuerdas.core :as str] [expound.alpha :as expound])) - (defn get-context-string [request edata] (str "=| uri: " (pr-str (:uri request)) "\n" "=| method: " (pr-str (:request-method request)) "\n" - "=| params: " (pr-str (:params request)) "\n" + "=| params: \n" + (with-out-str + (pprint (:params request))) + "\n" (when (map? edata) - (str "=| ex-data: " (pr-str edata) "\n")) + (str "=| ex-data: \n" + (with-out-str + (pprint edata)))) "\n")) @@ -33,69 +38,60 @@ (or (:type edata) (class err))))) -(defmethod handle-exception :authorization - [err _] - {:status 403 - :body (ex-data err)}) - (defmethod handle-exception :authentication [err _] - {:status 401 - :body (ex-data err)}) + {:status 401 :body (ex-data err)}) + +(defn- explain-error + [error] + (with-out-str + (expound/printer (:data error)))) (defmethod handle-exception :validation [err req] (let [header (get-in req [:headers "accept"]) edata (ex-data err)] - (cond - (= :spec-validation (:code edata)) - (if (str/starts-with? header "text/html") - {:status 400 - :headers {"content-type" "text/html"} - :body (str "
"
-                    (with-out-str
-                      (expound/printer (:data edata)))
-                    "
\n")} - {:status 400 - :body (assoc edata :explain (with-out-str (expound/printer (:data edata))))}) - - :else + (if (and (= :spec-validation (:code edata)) + (str/starts-with? header "text/html")) {:status 400 - :body edata}))) + :headers {"content-type" "text/html"} + :body (str "
"
+                  (explain-error edata)
+                  "
\n")} + {:status 400 + :body (cond-> edata + (map? (:data edata)) + (-> (assoc :explain (explain-error edata)) + (dissoc :data)))}))) (defmethod handle-exception :assertion [error request] (let [edata (ex-data error)] (log/error error - (str "Assertion error\n" - (get-context-string request edata) - (with-out-str (expound/printer (:data edata))))) + (str "Internal error: assertion\n" + (get-context-string request edata) + (explain-error edata))) {:status 500 - :body (assoc edata :explain (with-out-str (expound/printer (:data edata))))})) + :body {:type :server-error + :data (-> edata + (assoc :explain (explain-error edata)) + (dissoc :data))}})) (defmethod handle-exception :not-found [err _] - (let [response (ex-data err)] - {:status 404 - :body response})) - -(defmethod handle-exception :service-error - [err req] - (handle-exception (.getCause ^Throwable err) req)) - + {:status 404 :body (ex-data err)}) (defmethod handle-exception :default [error request] (let [edata (ex-data error)] (log/error error - (str "Internal Error\n" - (get-context-string request edata))) - (if (nil? edata) - {:status 500 - :body {:type :server-error - :hint (ex-message error)}} - {:status 500 - :body (dissoc edata :data)}))) + (str "Internal Error: " + (ex-message error) + (get-context-string request edata))) + {:status 500 + :body {:type :server-error + :hint (ex-message error) + :data edata}})) (defn handle [error req] diff --git a/backend/src/app/rpc/mutations/projects.clj b/backend/src/app/rpc/mutations/projects.clj index 766cbdcb5..bb2c4772d 100644 --- a/backend/src/app/rpc/mutations/projects.clj +++ b/backend/src/app/rpc/mutations/projects.clj @@ -108,7 +108,8 @@ (proj/check-edition-permissions! conn profile-id id) (db/update! conn :project {:name name} - {:id id}))) + {:id id}) + nil)) ;; --- Mutation: Delete Project diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj index 89032bb21..4cb957f20 100644 --- a/backend/src/app/rpc/queries/viewer.clj +++ b/backend/src/app/rpc/queries/viewer.clj @@ -71,8 +71,8 @@ [conn file-id page-id token] (let [sql "select exists(select 1 from file_share_token where file_id=? and page_id=? and token=?) as exists"] (when-not (:exists (db/exec-one! conn [sql file-id page-id token])) - (ex/raise :type :authorization - :code :unauthorized-token)))) + (ex/raise :type :not-found + :code :object-not-found)))) (defn retrieve-shared-token [conn file-id page-id] diff --git a/common/app/common/spec.cljc b/common/app/common/spec.cljc index 8c104e40e..907395b40 100644 --- a/common/app/common/spec.cljc +++ b/common/app/common/spec.cljc @@ -140,13 +140,14 @@ [spec x message context] (if (s/valid? spec x) x - (let [data (s/explain-data spec x) - hint (with-out-str (s/explain-out data))] + (let [data (s/explain-data spec x) + explain (with-out-str (s/explain-out data))] (ex/raise :type :assertion + :code :spec-validation + :hint message :data data - :hint hint + :explain explain :context context - :message message #?@(:cljs [:stack (.-stack (ex-info message {}))]))))) @@ -181,13 +182,13 @@ [spec data] (let [result (s/conform spec data)] (when (= result ::s/invalid) - (let [data (s/explain-data spec data) - hint (with-out-str - (s/explain-out data))] + (let [data (s/explain-data spec data) + explain (with-out-str + (s/explain-out data))] (throw (ex/error :type :validation :code :spec-validation - :data data - :hint hint)))) + :explain explain + :data data)))) result)) (defmacro instrument! diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index e725683ab..7f2466e86 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -18,7 +18,7 @@ } }, "auth.create-demo-account" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:160", "src/app/main/ui/auth/register.cljs:136" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:136", "src/app/main/ui/auth/login.cljs:160" ], "translations" : { "en" : "Create demo account", "fr" : "Vous voulez juste essayer?", @@ -27,7 +27,7 @@ } }, "auth.create-demo-profile" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:157", "src/app/main/ui/auth/register.cljs:133" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:133", "src/app/main/ui/auth/login.cljs:157" ], "translations" : { "en" : "Just wanna try it?", "fr" : "Vous voulez juste essayer?", @@ -45,7 +45,7 @@ } }, "auth.email" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:99", "src/app/main/ui/auth/register.cljs:101", "src/app/main/ui/auth/recovery_request.cljs:47" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:101", "src/app/main/ui/auth/recovery_request.cljs:47", "src/app/main/ui/auth/login.cljs:99" ], "translations" : { "en" : "Email", "fr" : "Adresse email", @@ -196,7 +196,7 @@ } }, "auth.password" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:106", "src/app/main/ui/auth/register.cljs:106" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:106", "src/app/main/ui/auth/login.cljs:106" ], "translations" : { "en" : "Password", "fr" : "Mot de passe", @@ -259,7 +259,7 @@ } }, "auth.register-submit" : { - "used-in" : [ "src/app/main/ui/auth/login.cljs:134", "src/app/main/ui/auth/register.cljs:110" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:110", "src/app/main/ui/auth/login.cljs:134" ], "translations" : { "en" : "Create an account", "fr" : "Créer un compte", @@ -295,7 +295,7 @@ } }, "dashboard.add-shared" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:225", "src/app/main/ui/dashboard/grid.cljs:180" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:225", "src/app/main/ui/dashboard/grid.cljs:182" ], "translations" : { "en" : "Add as Shared Library", "fr" : "", @@ -343,7 +343,7 @@ } }, "dashboard.empty-files" : { - "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:187" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:189" ], "translations" : { "en" : "You still have no files here", "fr" : "Vous n'avez encore aucun fichier ici", @@ -517,7 +517,7 @@ } }, "dashboard.num-of-members" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:299" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:301" ], "translations" : { "en" : "%s members", "es" : "%s integrantes" @@ -542,7 +542,7 @@ } }, "dashboard.promote-to-owner" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:196" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:198" ], "translations" : { "en" : "Promote to owner", "es" : "Promover a dueño" @@ -558,7 +558,7 @@ } }, "dashboard.remove-shared" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:223", "src/app/main/ui/dashboard/grid.cljs:179" ], + "used-in" : [ "src/app/main/ui/workspace/header.cljs:223", "src/app/main/ui/dashboard/grid.cljs:181" ], "translations" : { "en" : "Remove as Shared Library", "fr" : "", @@ -603,7 +603,7 @@ } }, "dashboard.show-all-files" : { - "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:250" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:252" ], "translations" : { "en" : "Show all files", "es" : "Ver todos los ficheros" @@ -626,21 +626,21 @@ } }, "dashboard.team-info" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:282" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:284" ], "translations" : { "en" : "Team info", "es" : "Información del equipo" } }, "dashboard.team-members" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:293" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:295" ], "translations" : { "en" : "Team members", "es" : "Integrantes del equipo" } }, "dashboard.team-projects" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:302" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:304" ], "translations" : { "en" : "Team projects", "es" : "Proyectos del equipo" @@ -674,7 +674,7 @@ } }, "dashboard.update-settings" : { - "used-in" : [ "src/app/main/ui/settings/options.cljs:72", "src/app/main/ui/settings/profile.cljs:82", "src/app/main/ui/settings/password.cljs:96" ], + "used-in" : [ "src/app/main/ui/settings/profile.cljs:82", "src/app/main/ui/settings/password.cljs:96", "src/app/main/ui/settings/options.cljs:72" ], "translations" : { "en" : "Update settings", "fr" : "Mettre à jour les paramètres", @@ -796,7 +796,7 @@ } }, "errors.clipboard-not-implemented" : { - "used-in" : [ "src/app/main/data/workspace.cljs:1251" ], + "used-in" : [ "src/app/main/data/workspace.cljs:1375" ], "translations" : { "en" : "Your browser cannot do this operation, please use Ctrl-V", "fr" : "", @@ -805,7 +805,7 @@ } }, "errors.email-already-exists" : { - "used-in" : [ "src/app/main/ui/settings/change_email.cljs:47", "src/app/main/ui/auth/verify_token.cljs:80" ], + "used-in" : [ "src/app/main/ui/auth/verify_token.cljs:80", "src/app/main/ui/settings/change_email.cljs:47" ], "translations" : { "en" : "Email already used", "fr" : "Adresse e-mail déjà utilisée", @@ -832,7 +832,7 @@ } }, "errors.generic" : { - "used-in" : [ "src/app/main/ui/settings/options.cljs:32", "src/app/main/ui/settings/profile.cljs:42", "src/app/main/ui/auth/verify_token.cljs:89" ], + "used-in" : [ "src/app/main/ui/auth/verify_token.cljs:89", "src/app/main/ui/settings/profile.cljs:42", "src/app/main/ui/settings/options.cljs:32" ], "translations" : { "en" : "Something wrong has happened.", "fr" : "Quelque chose c'est mal passé.", @@ -859,7 +859,7 @@ } }, "errors.media-type-mismatch" : { - "used-in" : [ "src/app/main/data/media.cljs:78", "src/app/main/data/workspace/persistence.cljs:394" ], + "used-in" : [ "src/app/main/data/workspace/persistence.cljs:381", "src/app/main/data/media.cljs:78" ], "translations" : { "en" : "Seems that the contents of the image does not match the file extension.", "fr" : "", @@ -868,7 +868,7 @@ } }, "errors.media-type-not-allowed" : { - "used-in" : [ "src/app/main/data/media.cljs:75", "src/app/main/data/workspace/persistence.cljs:391" ], + "used-in" : [ "src/app/main/data/workspace/persistence.cljs:378", "src/app/main/data/media.cljs:75" ], "translations" : { "en" : "Seems that this is not a valid image.", "fr" : "", @@ -913,7 +913,7 @@ } }, "errors.unexpected-error" : { - "used-in" : [ "src/app/main/data/media.cljs:81", "src/app/main/ui/auth/register.cljs:45", "src/app/main/ui/workspace/sidebar/options/exports.cljs:66", "src/app/main/ui/handoff/exports.cljs:41" ], + "used-in" : [ "src/app/main/data/media.cljs:81", "src/app/main/ui/workspace/sidebar/options/exports.cljs:66", "src/app/main/ui/auth/register.cljs:45", "src/app/main/ui/handoff/exports.cljs:41" ], "translations" : { "en" : "An unexpected error occurred.", "fr" : "Une erreur inattendue c'est produite", @@ -986,21 +986,21 @@ } }, "handoff.attributes.image.download" : { - "used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:45" ], + "used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:61" ], "translations" : { "en" : "Dowload source image", "es" : "Descargar imagen original" } }, "handoff.attributes.image.height" : { - "used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:37" ], + "used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:49" ], "translations" : { "en" : "Height", "es" : "Altura" } }, "handoff.attributes.image.width" : { - "used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:32" ], + "used-in" : [ "src/app/main/ui/handoff/attributes/image.cljs:44" ], "translations" : { "en" : "Width", "es" : "Ancho" @@ -1368,7 +1368,7 @@ "unused" : true }, "labels.admin" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:85", "src/app/main/ui/dashboard/team.cljs:174", "src/app/main/ui/dashboard/team.cljs:190" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:85", "src/app/main/ui/dashboard/team.cljs:176", "src/app/main/ui/dashboard/team.cljs:192" ], "translations" : { "en" : "Admin", "es" : "Administración" @@ -1381,6 +1381,18 @@ "es" : "Todo" } }, + "labels.bad-gateway.desc-message" : { + "used-in" : [ "src/app/main/ui/static.cljs:58" ], + "translations" : { + "en" : "Looks like you need to wait a bit and retry; we are performing small maintenance of our servers." + } + }, + "labels.bad-gateway.main-message" : { + "used-in" : [ "src/app/main/ui/static.cljs:57" ], + "translations" : { + "en" : "Bad Gateway" + } + }, "labels.cancel" : { "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:201" ], "translations" : { @@ -1414,7 +1426,7 @@ } }, "labels.delete" : { - "used-in" : [ "src/app/main/ui/dashboard/files.cljs:85", "src/app/main/ui/dashboard/grid.cljs:177" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:179", "src/app/main/ui/dashboard/files.cljs:85" ], "translations" : { "en" : "Delete", "fr" : "Supprimer", @@ -1453,14 +1465,14 @@ } }, "labels.editor" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:86", "src/app/main/ui/dashboard/team.cljs:177", "src/app/main/ui/dashboard/team.cljs:191" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:86", "src/app/main/ui/dashboard/team.cljs:179", "src/app/main/ui/dashboard/team.cljs:193" ], "translations" : { "en" : "Editor", "es" : "Editor" } }, "labels.email" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:112", "src/app/main/ui/dashboard/team.cljs:215" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:114", "src/app/main/ui/dashboard/team.cljs:217" ], "translations" : { "en" : "Email", "fr" : "Adresse email", @@ -1478,7 +1490,7 @@ } }, "labels.hide-resolved-comments" : { - "used-in" : [ "src/app/main/ui/viewer/header.cljs:175", "src/app/main/ui/workspace/comments.cljs:129" ], + "used-in" : [ "src/app/main/ui/workspace/comments.cljs:129", "src/app/main/ui/viewer/header.cljs:175" ], "translations" : { "en" : "Hide resolved comments", "es" : "Ocultar comentarios resueltos" @@ -1503,14 +1515,14 @@ } }, "labels.members" : { - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:297", "src/app/main/ui/dashboard/team.cljs:60", "src/app/main/ui/dashboard/team.cljs:66" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:60", "src/app/main/ui/dashboard/team.cljs:66", "src/app/main/ui/dashboard/sidebar.cljs:297" ], "translations" : { "en" : "Members", "es" : "Integrantes" } }, "labels.name" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:214" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:216" ], "translations" : { "en" : "Name", "fr" : "Nom", @@ -1534,15 +1546,33 @@ "es" : "No tienes notificaciones de comentarios pendientes" } }, + "labels.not-found.auth-info" : { + "used-in" : [ "src/app/main/ui/static.cljs:42" ], + "translations" : { + "en" : "You’re signed in as" + } + }, + "labels.not-found.desc-message" : { + "used-in" : [ "src/app/main/ui/static.cljs:40" ], + "translations" : { + "en" : "This page might not exist or you don’t have permissions to access to it." + } + }, + "labels.not-found.main-message" : { + "used-in" : [ "src/app/main/ui/static.cljs:39" ], + "translations" : { + "en" : "Oops!" + } + }, "labels.num-of-files" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:308" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:310" ], "translations" : { "en" : [ "1 file", "%s files" ], "es" : [ "1 archivo", "%s archivos" ] } }, "labels.num-of-projects" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:305" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:307" ], "translations" : { "en" : [ "1 project", "%s projects" ], "es" : [ "1 proyecto", "%s proyectos" ] @@ -1565,7 +1595,7 @@ } }, "labels.owner" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:171", "src/app/main/ui/dashboard/team.cljs:296" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:173", "src/app/main/ui/dashboard/team.cljs:298" ], "translations" : { "en" : "Owner", "es" : "Dueño" @@ -1581,7 +1611,7 @@ } }, "labels.permissions" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:216" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:218" ], "translations" : { "en" : "Permissions", "es" : "Permisos" @@ -1606,7 +1636,7 @@ } }, "labels.remove" : { - "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:92", "src/app/main/ui/dashboard/team.cljs:202" ], + "used-in" : [ "src/app/main/ui/workspace/libraries.cljs:92", "src/app/main/ui/dashboard/team.cljs:204" ], "translations" : { "en" : "Remove", "fr" : "", @@ -1615,12 +1645,18 @@ } }, "labels.rename" : { - "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:300", "src/app/main/ui/dashboard/files.cljs:84", "src/app/main/ui/dashboard/grid.cljs:176" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:178", "src/app/main/ui/dashboard/sidebar.cljs:300", "src/app/main/ui/dashboard/files.cljs:84" ], "translations" : { "en" : "Rename", "es" : "Renombrar" } }, + "labels.retry" : { + "used-in" : [ "src/app/main/ui/static.cljs:62", "src/app/main/ui/static.cljs:79" ], + "translations" : { + "en" : "Retry" + } + }, "labels.role" : { "used-in" : [ "src/app/main/ui/dashboard/team.cljs:84" ], "translations" : { @@ -1628,8 +1664,20 @@ "es" : "Cargo" } }, + "labels.service-unavailable.desc-message" : { + "used-in" : [ "src/app/main/ui/static.cljs:75" ], + "translations" : { + "en" : "We are in programmed maintenance of our systems." + } + }, + "labels.service-unavailable.main-message" : { + "used-in" : [ "src/app/main/ui/static.cljs:74" ], + "translations" : { + "en" : "Service Unavailable" + } + }, "labels.settings" : { - "used-in" : [ "src/app/main/ui/settings/sidebar.cljs:80", "src/app/main/ui/dashboard/sidebar.cljs:298", "src/app/main/ui/dashboard/team.cljs:61", "src/app/main/ui/dashboard/team.cljs:68" ], + "used-in" : [ "src/app/main/ui/settings/sidebar.cljs:80", "src/app/main/ui/dashboard/team.cljs:61", "src/app/main/ui/dashboard/team.cljs:68", "src/app/main/ui/dashboard/sidebar.cljs:298" ], "translations" : { "en" : "Settings", "fr" : "Settings", @@ -1647,19 +1695,28 @@ } }, "labels.show-all-comments" : { - "used-in" : [ "src/app/main/ui/viewer/header.cljs:163", "src/app/main/ui/workspace/comments.cljs:117" ], + "used-in" : [ "src/app/main/ui/workspace/comments.cljs:117", "src/app/main/ui/viewer/header.cljs:163" ], "translations" : { "en" : "Show all comments", "es" : "Mostrar todos los comentarios" } }, "labels.show-your-comments" : { - "used-in" : [ "src/app/main/ui/viewer/header.cljs:168", "src/app/main/ui/workspace/comments.cljs:122" ], + "used-in" : [ "src/app/main/ui/workspace/comments.cljs:122", "src/app/main/ui/viewer/header.cljs:168" ], "translations" : { "en" : "Show only yours comments", "es" : "Mostrar sólo tus comentarios" } }, + "labels.sign-out" : { + "used-in" : [ "src/app/main/ui/static.cljs:45" ], + "translations" : { + "en" : "Sign out", + "fr" : "Quitter", + "ru" : "Выход", + "es" : "Salir" + } + }, "labels.update" : { "used-in" : [ "src/app/main/ui/settings/profile.cljs:104" ], "translations" : { @@ -1670,7 +1727,7 @@ } }, "labels.viewer" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:87", "src/app/main/ui/dashboard/team.cljs:180", "src/app/main/ui/dashboard/team.cljs:192" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:87", "src/app/main/ui/dashboard/team.cljs:182", "src/app/main/ui/dashboard/team.cljs:194" ], "translations" : { "en" : "Viewer", "es" : "Visualizador" @@ -1684,7 +1741,7 @@ } }, "media.loading" : { - "used-in" : [ "src/app/main/data/media.cljs:60", "src/app/main/data/workspace/persistence.cljs:472", "src/app/main/data/workspace/persistence.cljs:527" ], + "used-in" : [ "src/app/main/data/workspace/persistence.cljs:459", "src/app/main/data/workspace/persistence.cljs:514", "src/app/main/data/media.cljs:60" ], "translations" : { "en" : "Loading image...", "fr" : "Chargement de l'image...", @@ -1852,13 +1909,13 @@ } }, "modals.delete-page.body" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:45" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:60" ], "translations" : { "en" : "Are you sure you want to delete this page?" } }, "modals.delete-page.title" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:44" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:59" ], "translations" : { "en" : "Delete page" } @@ -1906,28 +1963,28 @@ } }, "modals.delete-team-member-confirm.accept" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:160" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:162" ], "translations" : { "en" : "Delete member", "es" : "Eliminando miembro" } }, "modals.delete-team-member-confirm.message" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:159" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:161" ], "translations" : { "en" : "Are you sure you want to delete this member from the team?", "es" : "¿Seguro que quieres eliminar este integrante del equipo?" } }, "modals.delete-team-member-confirm.title" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:158" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:160" ], "translations" : { "en" : "Delete team member", "es" : "Eliminar integrante del equipo" } }, "modals.invite-member.title" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:108" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:110" ], "translations" : { "en" : "Invite to join the team", "es" : "Invitar a unirse al equipo" @@ -1990,21 +2047,21 @@ } }, "modals.promote-owner-confirm.accept" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:147" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:149" ], "translations" : { "en" : "Promote", "es" : "Promocionar" } }, "modals.promote-owner-confirm.message" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:146" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:148" ], "translations" : { "en" : "Are you sure you want to promote this user to owner?", "es" : "¿Seguro que quieres promocionar este usuario a dueño?" } }, "modals.promote-owner-confirm.title" : { - "used-in" : [ "src/app/main/ui/dashboard/team.cljs:145" ], + "used-in" : [ "src/app/main/ui/dashboard/team.cljs:147" ], "translations" : { "en" : "Promote to owner", "es" : "Promocionar a dueño" @@ -2047,7 +2104,7 @@ } }, "notifications.profile-saved" : { - "used-in" : [ "src/app/main/ui/settings/options.cljs:36", "src/app/main/ui/settings/profile.cljs:38" ], + "used-in" : [ "src/app/main/ui/settings/profile.cljs:38", "src/app/main/ui/settings/options.cljs:36" ], "translations" : { "en" : "Profile saved successfully!", "fr" : "Profil enregistré avec succès!", @@ -2056,7 +2113,7 @@ } }, "notifications.validation-email-sent" : { - "used-in" : [ "src/app/main/ui/settings/change_email.cljs:56", "src/app/main/ui/auth/register.cljs:54" ], + "used-in" : [ "src/app/main/ui/auth/register.cljs:54", "src/app/main/ui/settings/change_email.cljs:56" ], "translations" : { "en" : "Verification email sent to %s. Check your email!", "es" : "Verificación de email enviada a %s. Comprueba tu correo." @@ -2072,7 +2129,7 @@ } }, "settings.multiple" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/shadow.cljs:213", "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:161", "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:170", "src/app/main/ui/workspace/sidebar/options/typography.cljs:99", "src/app/main/ui/workspace/sidebar/options/typography.cljs:149", "src/app/main/ui/workspace/sidebar/options/typography.cljs:162", "src/app/main/ui/workspace/sidebar/options/blur.cljs:79", "src/app/main/ui/workspace/sidebar/options/stroke.cljs:154" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/stroke.cljs:154", "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:161", "src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:170", "src/app/main/ui/workspace/sidebar/options/typography.cljs:99", "src/app/main/ui/workspace/sidebar/options/typography.cljs:149", "src/app/main/ui/workspace/sidebar/options/typography.cljs:162", "src/app/main/ui/workspace/sidebar/options/shadow.cljs:213", "src/app/main/ui/workspace/sidebar/options/blur.cljs:79" ], "translations" : { "en" : "Mixed", "fr" : null, @@ -2351,7 +2408,7 @@ } }, "workspace.assets.delete" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:140", "src/app/main/ui/workspace/sidebar/assets.cljs:260", "src/app/main/ui/workspace/sidebar/assets.cljs:375", "src/app/main/ui/workspace/sidebar/assets.cljs:504" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:151", "src/app/main/ui/workspace/sidebar/assets.cljs:140", "src/app/main/ui/workspace/sidebar/assets.cljs:260", "src/app/main/ui/workspace/sidebar/assets.cljs:375", "src/app/main/ui/workspace/sidebar/assets.cljs:504" ], "translations" : { "en" : "Delete", "fr" : "", @@ -2360,7 +2417,7 @@ } }, "workspace.assets.duplicate" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:139" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:155", "src/app/main/ui/workspace/sidebar/assets.cljs:139" ], "translations" : { "en" : "Duplicate", "fr" : "", @@ -2414,7 +2471,7 @@ } }, "workspace.assets.rename" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:138", "src/app/main/ui/workspace/sidebar/assets.cljs:259", "src/app/main/ui/workspace/sidebar/assets.cljs:373", "src/app/main/ui/workspace/sidebar/assets.cljs:502" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:154", "src/app/main/ui/workspace/sidebar/assets.cljs:138", "src/app/main/ui/workspace/sidebar/assets.cljs:259", "src/app/main/ui/workspace/sidebar/assets.cljs:373", "src/app/main/ui/workspace/sidebar/assets.cljs:502" ], "translations" : { "en" : "Rename", "fr" : "", @@ -2502,14 +2559,14 @@ } }, "workspace.gradients.linear" : { - "used-in" : [ "src/app/main/data/workspace/libraries.cljs:43", "src/app/main/ui/components/color_bullet.cljs:31" ], + "used-in" : [ "src/app/main/data/workspace/libraries.cljs:72", "src/app/main/ui/components/color_bullet.cljs:31" ], "translations" : { "en" : "Linear gradient", "es" : "Degradado lineal" } }, "workspace.gradients.radial" : { - "used-in" : [ "src/app/main/data/workspace/libraries.cljs:44", "src/app/main/ui/components/color_bullet.cljs:32" ], + "used-in" : [ "src/app/main/data/workspace/libraries.cljs:73", "src/app/main/ui/components/color_bullet.cljs:32" ], "translations" : { "en" : "Radial gradient", "es" : "Degradado radial" @@ -2706,21 +2763,21 @@ } }, "workspace.libraries.colors.big-thumbnails" : { - "used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:170" ], + "used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:171" ], "translations" : { "en" : "Big thumbnails", "es" : "Miniaturas grandes" } }, "workspace.libraries.colors.file-library" : { - "used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs:89", "src/app/main/ui/workspace/colorpalette.cljs:148" ], + "used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs:89", "src/app/main/ui/workspace/colorpalette.cljs:149" ], "translations" : { "en" : "File library", "es" : "Biblioteca del archivo" } }, "workspace.libraries.colors.recent-colors" : { - "used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs:88", "src/app/main/ui/workspace/colorpalette.cljs:158" ], + "used-in" : [ "src/app/main/ui/workspace/colorpicker/libraries.cljs:88", "src/app/main/ui/workspace/colorpalette.cljs:159" ], "translations" : { "en" : "Recent colors", "es" : "Colores recientes" @@ -2734,7 +2791,7 @@ } }, "workspace.libraries.colors.small-thumbnails" : { - "used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:175" ], + "used-in" : [ "src/app/main/ui/workspace/colorpalette.cljs:176" ], "translations" : { "en" : "Small thumbnails", "es" : "Miniaturas pequeñas" @@ -3251,7 +3308,7 @@ } }, "workspace.options.position" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame.cljs:118", "src/app/main/ui/workspace/sidebar/options/measures.cljs:146" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/measures.cljs:146", "src/app/main/ui/workspace/sidebar/options/frame.cljs:118" ], "translations" : { "en" : "Position", "fr" : "Position", @@ -3384,7 +3441,7 @@ } }, "workspace.options.size" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/frame.cljs:93", "src/app/main/ui/workspace/sidebar/options/measures.cljs:118" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/measures.cljs:118", "src/app/main/ui/workspace/sidebar/options/frame.cljs:93" ], "translations" : { "en" : "Size", "fr" : "Taille", @@ -3594,7 +3651,7 @@ } }, "workspace.options.text-options.none" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/options/typography.cljs:176", "src/app/main/ui/workspace/sidebar/options/text.cljs:153" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/text.cljs:153", "src/app/main/ui/workspace/sidebar/options/typography.cljs:176" ], "translations" : { "en" : "None", "fr" : "Aucune", @@ -3733,7 +3790,7 @@ } }, "workspace.shape.menu.detach-instance" : { - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:163", "src/app/main/ui/workspace/context_menu.cljs:173", "src/app/main/ui/workspace/sidebar/options/component.cljs:79", "src/app/main/ui/workspace/sidebar/options/component.cljs:84" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/component.cljs:79", "src/app/main/ui/workspace/sidebar/options/component.cljs:84", "src/app/main/ui/workspace/context_menu.cljs:163", "src/app/main/ui/workspace/context_menu.cljs:173" ], "translations" : { "en" : "Detach instance", "es" : "Desacoplar instancia" @@ -3761,7 +3818,7 @@ } }, "workspace.shape.menu.go-master" : { - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:177", "src/app/main/ui/workspace/sidebar/options/component.cljs:86" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/component.cljs:86", "src/app/main/ui/workspace/context_menu.cljs:177" ], "translations" : { "en" : "Go to master component file", "es" : "Ir al archivo del componente maestro" @@ -3803,7 +3860,7 @@ } }, "workspace.shape.menu.reset-overrides" : { - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:165", "src/app/main/ui/workspace/context_menu.cljs:175", "src/app/main/ui/workspace/sidebar/options/component.cljs:80", "src/app/main/ui/workspace/sidebar/options/component.cljs:85" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/component.cljs:80", "src/app/main/ui/workspace/sidebar/options/component.cljs:85", "src/app/main/ui/workspace/context_menu.cljs:165", "src/app/main/ui/workspace/context_menu.cljs:175" ], "translations" : { "en" : "Reset overrides", "es" : "Deshacer modificaciones" @@ -3817,7 +3874,7 @@ } }, "workspace.shape.menu.show-master" : { - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:169", "src/app/main/ui/workspace/sidebar/options/component.cljs:82" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/component.cljs:82", "src/app/main/ui/workspace/context_menu.cljs:169" ], "translations" : { "en" : "Show master component", "es" : "Ver componente maestro" @@ -3845,7 +3902,7 @@ } }, "workspace.shape.menu.update-master" : { - "used-in" : [ "src/app/main/ui/workspace/context_menu.cljs:167", "src/app/main/ui/workspace/sidebar/options/component.cljs:81" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/options/component.cljs:81", "src/app/main/ui/workspace/context_menu.cljs:167" ], "translations" : { "en" : "Update master component", "es" : "Actualizar componente maestro" @@ -3861,7 +3918,7 @@ "unused" : true }, "workspace.sidebar.sitemap" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:171" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/sitemap.cljs:207" ], "translations" : { "en" : "Pages", "fr" : "Pages", @@ -4244,7 +4301,7 @@ } }, "workspace.updates.dismiss" : { - "used-in" : [ "src/app/main/data/workspace/libraries.cljs:649" ], + "used-in" : [ "src/app/main/data/workspace/libraries.cljs:697" ], "translations" : { "en" : "Dismiss", "fr" : "", @@ -4253,7 +4310,7 @@ } }, "workspace.updates.there-are-updates" : { - "used-in" : [ "src/app/main/data/workspace/libraries.cljs:645" ], + "used-in" : [ "src/app/main/data/workspace/libraries.cljs:693" ], "translations" : { "en" : "There are updates in shared libraries", "fr" : "", @@ -4262,7 +4319,7 @@ } }, "workspace.updates.update" : { - "used-in" : [ "src/app/main/data/workspace/libraries.cljs:647" ], + "used-in" : [ "src/app/main/data/workspace/libraries.cljs:695" ], "translations" : { "en" : "Update", "fr" : "", diff --git a/frontend/resources/styles/main-default.scss b/frontend/resources/styles/main-default.scss index a18f57a44..8c6c1e622 100644 --- a/frontend/resources/styles/main-default.scss +++ b/frontend/resources/styles/main-default.scss @@ -12,22 +12,22 @@ // //################################################# -@import 'common/dependencies/colors'; -@import 'common/dependencies/helpers'; -@import 'common/dependencies/mixin'; -@import 'common/dependencies/fonts'; -@import 'common/dependencies/reset'; -@import 'common/dependencies/animations'; -@import 'common/dependencies/z-index'; -@import 'common/dependencies/highlightjs-theme'; +@import "common/dependencies/colors"; +@import "common/dependencies/helpers"; +@import "common/dependencies/mixin"; +@import "common/dependencies/fonts"; +@import "common/dependencies/reset"; +@import "common/dependencies/animations"; +@import "common/dependencies/z-index"; +@import "common/dependencies/highlightjs-theme"; //################################################# // Layouts //################################################# -@import 'common/base'; -@import 'main/layouts/login'; -@import 'main/layouts/main-layout'; +@import "common/base"; +@import "main/layouts/login"; +@import "main/layouts/main-layout"; @import "main/layouts/not-found"; @import "main/layouts/viewer"; @import "main/layouts/handoff"; @@ -36,12 +36,12 @@ // Commons //################################################# -@import 'common/framework'; -@import 'main/partials/modal'; -@import 'main/partials/forms'; +@import "common/framework"; +@import "main/partials/modal"; +@import "main/partials/forms"; @import "main/partials/texts"; -@import 'main/partials/context-menu'; -@import 'main/partials/dropdown'; +@import "main/partials/context-menu"; +@import "main/partials/dropdown"; //################################################# // Partials @@ -51,35 +51,36 @@ @import "main/partials/viewer-header"; @import "main/partials/viewer-thumbnails"; @import "main/partials/zoom-widget"; -@import 'main/partials/activity-bar'; -@import 'main/partials/color-palette'; -@import 'main/partials/colorpicker'; -@import 'main/partials/dashboard'; -@import 'main/partials/dashboard-header'; -@import 'main/partials/dashboard-grid'; -@import 'main/partials/dashboard-sidebar'; -@import 'main/partials/dashboard-team'; -@import 'main/partials/dashboard-settings'; -@import 'main/partials/debug-icons-preview'; -@import 'main/partials/editable-label'; -@import 'main/partials/left-toolbar'; -@import 'main/partials/loader'; -@import 'main/partials/project-bar'; -@import 'main/partials/sidebar'; -@import 'main/partials/sidebar-align-options'; -@import 'main/partials/sidebar-assets'; -@import 'main/partials/sidebar-document-history'; -@import 'main/partials/sidebar-element-options'; -@import 'main/partials/sidebar-icons'; -@import 'main/partials/sidebar-interactions'; -@import 'main/partials/sidebar-layers'; -@import 'main/partials/sidebar-sitemap'; -@import 'main/partials/sidebar-tools'; -@import 'main/partials/tab-container'; -@import 'main/partials/tool-bar'; -@import 'main/partials/user-settings'; -@import 'main/partials/workspace'; -@import 'main/partials/workspace-header'; -@import 'main/partials/comments'; -@import 'main/partials/color-bullet'; +@import "main/partials/activity-bar"; +@import "main/partials/color-palette"; +@import "main/partials/colorpicker"; +@import "main/partials/dashboard"; +@import "main/partials/dashboard-header"; +@import "main/partials/dashboard-grid"; +@import "main/partials/dashboard-sidebar"; +@import "main/partials/dashboard-team"; +@import "main/partials/dashboard-settings"; +@import "main/partials/debug-icons-preview"; +@import "main/partials/editable-label"; +@import "main/partials/left-toolbar"; +@import "main/partials/loader"; +@import "main/partials/project-bar"; +@import "main/partials/sidebar"; +@import "main/partials/sidebar-align-options"; +@import "main/partials/sidebar-assets"; +@import "main/partials/sidebar-document-history"; +@import "main/partials/sidebar-element-options"; +@import "main/partials/sidebar-icons"; +@import "main/partials/sidebar-interactions"; +@import "main/partials/sidebar-layers"; +@import "main/partials/sidebar-sitemap"; +@import "main/partials/sidebar-tools"; +@import "main/partials/tab-container"; +@import "main/partials/tool-bar"; +@import "main/partials/user-settings"; +@import "main/partials/workspace"; +@import "main/partials/workspace-header"; +@import "main/partials/comments"; +@import "main/partials/color-bullet"; @import "main/partials/handoff"; +@import "main/partials/exception-page"; diff --git a/frontend/resources/styles/main/layouts/not-found.scss b/frontend/resources/styles/main/layouts/not-found.scss index 8b14d9862..fbecd5910 100644 --- a/frontend/resources/styles/main/layouts/not-found.scss +++ b/frontend/resources/styles/main/layouts/not-found.scss @@ -29,34 +29,55 @@ justify-content: center; align-items: center; - .error-img { + + .container { + max-width: 600px; + } + + .image { align-items: center; display: flex; justify-content: center; margin-bottom: 2rem; svg { - height: 320px; - width: 200px; + height: 220px; + width: 220px; } } .main-message { color: $color-black; - font-size: 11rem; - line-height: 200px; + font-size: 5rem; + line-height: 150px; text-align: center; } .desc-message { color: $color-black; - font-size: 2.2rem; + font-size: 1.6rem; font-weight: 300; text-align: center; } - .btn-primary { - margin-top: $x-big; + .sign-info { + margin-top: 20px; + color: $color-black; + font-size: 1rem; + font-weight: 200; + text-align: center; + + display: flex; + flex-direction: column; + align-items: center; + + b { + font-weight: 400; + } + + .btn-primary { + margin-top: 15px; + } } } diff --git a/frontend/resources/styles/main/partials/exception-page.scss b/frontend/resources/styles/main/partials/exception-page.scss new file mode 100644 index 000000000..c2ab1a3a8 --- /dev/null +++ b/frontend/resources/styles/main/partials/exception-page.scss @@ -0,0 +1,83 @@ +.exception-layout { + display: grid; + + grid-template-rows: 120px auto; + grid-template-columns: 1fr; +} + +.exception-header { + grid-column: 1 / span 1; + grid-row: 1 / span 1; + + display: flex; + align-items: center; + padding: 32px; + + svg { + height: 55px; + width: 170px; + } + +} + +.exception-content { + grid-column: 1 / span 1; + grid-row: 1 / span 2; + height: 100vh; + + display: flex; + justify-content: center; + align-items: center; + + + .container { + max-width: 600px; + } + + .image { + align-items: center; + display: flex; + justify-content: center; + margin-bottom: 2rem; + + svg { + height: 220px; + width: 220px; + } + } + + .main-message { + color: $color-black; + font-size: 5rem; + line-height: 150px; + text-align: center; + } + + .desc-message { + color: $color-black; + font-size: 1.6rem; + font-weight: 300; + text-align: center; + } + + .sign-info { + margin-top: 20px; + color: $color-black; + font-size: 1rem; + font-weight: 200; + text-align: center; + + display: flex; + flex-direction: column; + align-items: center; + + b { + font-weight: 400; + } + + .btn-primary { + margin-top: 15px; + } + } +} + diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 2498735e3..56508548a 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -81,7 +81,7 @@ (rt/initialize-history on-navigate)) (st/emit! udu/fetch-profile) - (mf/mount (mf/element ui/app-wrapper) (dom/get-element "app")) + (mf/mount (mf/element ui/app) (dom/get-element "app")) (mf/mount (mf/element modal) (dom/get-element "modal"))) (defn ^:export init diff --git a/frontend/src/app/main/data/messages.cljs b/frontend/src/app/main/data/messages.cljs index 7eb90707c..107b9e17f 100644 --- a/frontend/src/app/main/data/messages.cljs +++ b/frontend/src/app/main/data/messages.cljs @@ -129,3 +129,13 @@ :actions actions :tag tag}))) +(defn assign-exception + [{:keys [type] :as error}] + (us/assert (s/nilable map?) error) + (us/assert (s/nilable ::us/keyword) type) + (ptk/reify ::assign-exception + ptk/UpdateEvent + (update [_ state] + (if (nil? error) + (dissoc state :exception) + (assoc state :exception error))))) diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 37533024b..36db83cb4 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -130,13 +130,11 @@ (rx/map #(shapes-changes-persisted file-id %)))))) on-error - (fn [{:keys [type status] :as error}] - (if (and (= :server-error type) - (= 502 status)) + (fn [{:keys [type] :as error}] + (if (or (= :bad-gateway type) + (= :service-unavailable type)) (rx/of (update-persistence-status {:status :error :reason type})) - (rx/of update-persistence-queue - (update-persistence-status {:status :error :reason type}))))] - + (rx/throw error)))] (when (= file-id (:id file)) (->> (rp/mutation :update-file params) @@ -219,18 +217,7 @@ (rp/query :project {:id project-id}) (rp/query :file-libraries {:file-id file-id})) (rx/first) - (rx/map (fn [bundle] (apply bundle-fetched bundle))) - (rx/catch (fn [{:keys [type code] :as error}] - (cond - (= :not-found type) - (rx/of (rt/nav' :not-found)) - - (and (= :authentication type) - (= :unauthorized code)) - (rx/of (rt/nav' :not-authorized)) - - :else - (throw error)))))))) + (rx/map (fn [bundle] (apply bundle-fetched bundle))))))) (defn- bundle-fetched [file users project libraries] diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 101f8c564..635eb826f 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -31,6 +31,9 @@ (def profile (l/derived :profile st/state)) +(def exception + (l/derived :exception st/state)) + ;; ---- Dashboard refs (def dashboard-local diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 498d22c15..83c114e04 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -15,34 +15,32 @@ [app.util.http-api :as http])) (defn- handle-response - [response] + [{:keys [status body] :as response}] (cond - (http/success? response) - (rx/of (:body response)) + (= 204 status) + (rx/empty) - (= (:status response) 400) - (rx/throw (:body response)) + (= 502 status) + (rx/throw {:type :bad-gateway}) - (= (:status response) 401) - (rx/throw {:type :authentication - :code :not-authenticated}) - - (= (:status response) 403) - (rx/throw {:type :authorization - :code :not-authorized}) - - (= (:status response) 404) - (rx/throw (:body response)) + (= 503 status) + (rx/throw {:type :service-unavailable}) (= 0 (:status response)) (rx/throw {:type :offline}) + (and (= 200 status) + (coll? body)) + (rx/of body) + + (and (>= status 400) + (map? body)) + (rx/throw body) + :else - (rx/throw (merge {:type :server-error - :status (:status response)} - (:body response))))) - - + (rx/throw {:type :unexpected-error + :status status + :data body}))) (defn send-query! [id params] diff --git a/frontend/src/app/main/store.cljs b/frontend/src/app/main/store.cljs index 14788dfc8..b295aee83 100644 --- a/frontend/src/app/main/store.cljs +++ b/frontend/src/app/main/store.cljs @@ -26,6 +26,12 @@ (defonce state (ptk/store {:resolve ptk/resolve})) (defonce stream (ptk/input-stream state)) +(defn ^boolean is-logged? + [pdata] + (and (some? pdata) + (uuid? (:id pdata)) + (not= uuid/zero (:id pdata)))) + (when *assert* (defonce debug-subscription (->> stream diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 301e6b5fd..d2e394b5e 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -28,7 +28,7 @@ [app.main.ui.messages :as msgs] [app.main.ui.render :as render] [app.main.ui.settings :as settings] - [app.main.ui.static :refer [not-found-page not-authorized-page]] + [app.main.ui.static :as static] [app.main.ui.viewer :refer [viewer-page]] [app.main.ui.handoff :refer [handoff]] [app.main.ui.workspace :as workspace] @@ -37,6 +37,7 @@ [app.util.router :as rt] [cuerdas.core :as str] [cljs.spec.alpha :as s] + [cljs.pprint :refer [pprint]] [expound.alpha :as expound] [potok.core :as ptk] [rumext.alpha :as mf])) @@ -81,9 +82,6 @@ :conform {:path-params ::viewer-path-params :query-params ::viewer-query-params}}] - ["/not-found" :not-found] - ["/not-authorized" :not-authorized] - (when *assert* ["/debug/icons-preview" :debug-icons-preview]) @@ -100,19 +98,15 @@ ["/workspace/:project-id/:file-id" :workspace]]) -(mf/defc app-error +(mf/defc on-main-error [{:keys [error] :as props}] (let [data (ex-data error)] - (case (:type data) - :not-found [:& not-found-page {:error data}] - (do - (ptk/handle-error error) - [:span "Internal application errror"])))) + (ptk/handle-error error) + [:span "Internal application errror"])) -(mf/defc app - {::mf/wrap [#(mf/catch % {:fallback app-error})]} +(mf/defc main-page + {::mf/wrap [#(mf/catch % {:fallback on-main-error})]} [{:keys [route] :as props}] - [:& (mf/provider ctx/current-route) {:value route} (case (get-in route [:data :name]) (:auth-login @@ -189,67 +183,71 @@ :page-id page-id :layout-name (keyword layout-name) :key file-id}]) - - :not-authorized - [:& not-authorized-page] - - :not-found - [:& not-found-page] - nil)]) -(mf/defc app-wrapper +(mf/defc app [] - (let [route (mf/deref refs/route)] - [:* - [:& msgs/notifications] - (when route - [:& app {:route route}])])) + (let [route (mf/deref refs/route) + edata (mf/deref refs/exception)] + [:& (mf/provider ctx/current-route) {:value route} + (if edata + [:& static/exception-page {:data edata}] + [:* + [:& msgs/notifications] + (when route + [:& main-page {:route route}])])])) ;; --- Error Handling +;; That are special case server-errors that should be treated +;; differently. +(derive :not-found ::exceptional-state) +(derive :bad-gateway ::exceptional-state) +(derive :service-unavailable ::exceptional-state) + +(defmethod ptk/handle-error ::exceptional-state + [{:keys [status] :as error}] + (ts/schedule + (st/emitf (dm/assign-exception error)))) + +;; We receive a explicit authentication error; this explicitly clears +;; all profile data and redirect the user to the login page. +(defmethod ptk/handle-error :authentication + [error] + (ts/schedule (st/emitf (logout)))) + +;; Error that happens on an active bussines model validation does not +;; passes an validation (example: profile can't leave a team). From +;; the user perspective a error flash message should be visualized but +;; user can continue operate on the application. (defmethod ptk/handle-error :validation [error] (ts/schedule - (st/emitf (dm/show {:content "Unexpected validation error (server side)." - :type :error - :timeout 5000}))) - (when-let [explain (:hint-verbose error)] - (js/console.group "Server Error") - (js/console.error (if (map? error) (pr-str error) error)) - (js/console.error explain) - (js/console.endGroup "Server Error"))) + (st/emitf + (dm/show {:content "Unexpected validation error (server side)." + :type :error + :timeout 3000}))) -(defmethod ptk/handle-error :spec-validation - [error] - (ts/schedule - (st/emitf (dm/show {:content "Unexpected validation error (server side)." - :type :error - :timeout 5000}))) + ;; Print to the console some debug info. + (js/console.group "Server Error") + (js/console.info + (with-out-str + (pprint (dissoc error :explain)))) (when-let [explain (:explain error)] - (js/console.group "Server Error") - (js/console.error (if (map? error) (pr-str error) error)) - (js/console.error explain) - (js/console.endGroup "Server Error"))) - - -(defmethod ptk/handle-error :authentication - [error] - (ts/schedule 0 #(st/emit! (logout)))) - -(defmethod ptk/handle-error :authorization - [error] - (ts/schedule - (st/emitf (dm/show {:content "Not authorized to see this content." - :timeout 2000 - :type :error})))) + (js/console.error explain)) + (js/console.endGroup "Server Error")) +;; This is a pure frontend error that can be caused by an active +;; assertion (assertion that is preserved on production builds). From +;; the user perspective this should be treated as internal error. (defmethod ptk/handle-error :assertion [{:keys [data stack message context] :as error}] (ts/schedule - (st/emitf (dm/show {:content "Internal assertion error." + (st/emitf (dm/show {:content "Internal error: assertion." :type :error - :timeout 2000}))) + :timeout 3000}))) + + ;; Print to the console some debugging info (js/console.group message) (js/console.info (str/format "ns: '%s'\nname: '%s'\nfile: '%s:%s'" (:ns context) @@ -259,48 +257,49 @@ (js/console.groupCollapsed "Stack Trace") (js/console.info stack) (js/console.groupEnd "Stack Trace") - (js/console.error (with-out-str (expound/printer data))) (js/console.groupEnd message)) +;; This happens when the backed server fails to process the +;; request. This can be caused by an internal assertion or any other +;; uncontrolled error. +(defmethod ptk/handle-error :server-error + [{:keys [data] :as error}] + (ts/schedule + (st/emitf (dm/show + {:content "Something wrong has happened (on backend)." + :type :error + :timeout 3000}))) + (js/console.group "Internal Server Error:") + (js/console.error "hint:" (or (:hint data) (:message data))) + (js/console.info + (with-out-str + (pprint (dissoc data :explain)))) + (when-let [explain (:explain data)] + (js/console.error explain)) + (js/console.groupEnd "Internal Server Error:")) + (defmethod ptk/handle-error :default [error] (if (instance? ExceptionInfo error) (ptk/handle-error (ex-data error)) (do - (js/console.group "Generic Error") + (ts/schedule + (st/emitf (dm/show + {:content "Something wrong has happened." + :type :error + :timeout 3000}))) + + (js/console.group "Internal error:") (js/console.log "hint:" (or (ex-message error) (:hint error) (:message error))) (ex/ignoring (js/console.error "repr: " (pr-str error)) + (js/console.error "data: " (clj->js error)) (js/console.error "stack:" (.-stack error))) - (js/console.groupEnd "Generic error") - (ts/schedule (st/emitf (dm/show - {:content "Something wrong has happened." - :type :error - :timeout 3000})))))) + (js/console.groupEnd "Internal error:")))) -(defmethod ptk/handle-error :server-error - [{:keys [status] :as error}] - (cond - (= status 429) - (ts/schedule - (st/emitf (dm/show {:content "Too many requests, wait a little bit and retry." - :type :error - :timeout 5000}))) - - :else - (ts/schedule - (st/emitf (dm/show {:content "Unable to connect to backend, wait a little bit and refresh." - :type :error}))))) - - -(defmethod ptk/handle-error :not-found - [{:keys [status] :as error}] - (ts/schedule - (st/emitf (dm/show {:content "Resource not found." - :type :warning})))) (defonce uncaught-error-handler (letfn [(on-error [event] diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index 7e097168a..ac00e1e7e 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -11,27 +11,104 @@ (:require [cljs.spec.alpha :as s] [rumext.alpha :as mf] + [app.main.ui.context :as ctx] + [app.main.data.auth :as da] + [app.main.data.messages :as dm] + [app.main.store :as st] + [app.main.refs :as refs] + [cuerdas.core :as str] + [app.util.i18n :refer [tr]] + [app.util.router :as rt] [app.main.ui.icons :as i])) -(mf/defc not-found-page - [{:keys [error] :as props}] - [:section.not-found-layout - [:div.not-found-header i/logo] - [:div.not-found-content - [:div.message-container - [:div.error-img i/icon-empty] - [:div.main-message "404"] - [:div.desc-message "Oops! Page not found"] - [:a.btn-primary.btn-small "Go back"]]]]) +(defn- go-to-dashboard + [profile] + (let [team-id (:default-team-id profile)] + (st/emit! (rt/nav :dashboard-projects {:team-id team-id})))) -(mf/defc not-authorized-page +(mf/defc not-found [{:keys [error] :as props}] - [:section.not-found-layout - [:div.not-found-header i/logo] - [:div.not-found-content - [:div.message-container - [:div.error-img i/icon-lock] - [:div.main-message "403"] - [:div.desc-message "Sorry, you are not authorized to access this page."] - #_[:a.btn-primary.btn-small "Go back"]]]]) + (let [profile (mf/deref refs/profile)] + [:section.exception-layout + [:div.exception-header + {:on-click (partial go-to-dashboard profile)} + i/logo] + [:div.exception-content + [:div.container + [:div.image i/icon-empty] + [:div.main-message (tr "labels.not-found.main-message")] + [:div.desc-message (tr "labels.not-found.desc-message")] + [:div.sign-info + [:span (tr "labels.not-found.auth-info") " " [:b (:email profile)]] + [:a.btn-primary.btn-small + {:on-click (st/emitf (da/logout))} + (tr "labels.sign-out")]]]]])) + +(mf/defc bad-gateway + [{:keys [error] :as props}] + (let [profile (mf/deref refs/profile)] + [:section.exception-layout + [:div.exception-header + {:on-click (partial go-to-dashboard profile)} + i/logo] + [:div.exception-content + [:div.container + [:div.image i/icon-empty] + [:div.main-message (tr "labels.bad-gateway.main-message")] + [:div.desc-message (tr "labels.bad-gateway.desc-message")] + [:div.sign-info + [:a.btn-primary.btn-small + {:on-click (st/emitf #(dissoc % :exception))} + (tr "labels.retry")]]]]])) + +(mf/defc service-unavailable + [{:keys [error] :as props}] + (let [profile (mf/deref refs/profile)] + [:section.exception-layout + [:div.exception-header + {:on-click (partial go-to-dashboard profile)} + i/logo] + [:div.exception-content + [:div.container + [:div.image i/icon-empty] + [:div.main-message (tr "labels.service-unavailable.main-message")] + [:div.desc-message (tr "labels.service-unavailable.desc-message")] + [:div.sign-info + [:a.btn-primary.btn-small + {:on-click (st/emitf #(dissoc % :exception))} + (tr "labels.retry")]]]]])) + +(mf/defc internal-error + [props] + (let [profile (mf/deref refs/profile)] + [:section.exception-layout + [:div.exception-header + {:on-click (partial go-to-dashboard profile)} + i/logo] + [:div.exception-content + [:div.container + [:div.image i/icon-empty] + [:div.main-message "Internal Error"] + [:div.desc-message "Something bad happended on backend servers. Please retry the operation and if the problem persists, contact with support."] + [:div.sign-info + [:a.btn-primary.btn-small + {:on-click (st/emitf (dm/assign-exception nil))} + (tr "labels.retry")]]]]])) + +(mf/defc exception-page + [{:keys [data] :as props}] + (case (:type data) + :not-found + [:& not-found] + + :bad-gateway + [:& bad-gateway] + + :service-unavailable + [:& service-unavailable] + + :server-error + [:& internal-error] + + nil)) diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs index a96bb4f90..107f22e48 100644 --- a/frontend/src/app/util/router.cljs +++ b/frontend/src/app/util/router.cljs @@ -10,15 +10,15 @@ (ns app.util.router (:refer-clojure :exclude [resolve]) (:require + [app.common.data :as d] + [app.config :as cfg] + [app.util.browser-history :as bhistory] + [app.util.timers :as ts] [beicon.core :as rx] [cuerdas.core :as str] [goog.events :as e] [potok.core :as ptk] - [reitit.core :as r] - [app.common.data :as d] - [app.config :as cfg] - [app.util.browser-history :as bhistory] - [app.util.timers :as ts]) + [reitit.core :as r]) (:import goog.Uri goog.Uri.QueryData)) @@ -92,6 +92,10 @@ ;; --- Navigate (Event) (deftype Navigate [id params qparams replace] + ptk/UpdateEvent + (update [_ state] + (dissoc state :exception)) + ptk/EffectEvent (effect [_ state stream] (let [router (:router state)