♻️ Refactor error handling.

This commit is contained in:
Andrey Antukh 2021-01-22 14:33:18 +01:00 committed by Alonso Torres
parent b4ba9d4375
commit bea093e8da
17 changed files with 578 additions and 334 deletions

View file

@ -10,20 +10,25 @@
(ns app.http.errors (ns app.http.errors
"A errors handling for the http server." "A errors handling for the http server."
(:require (:require
[clojure.pprint :refer [pprint]]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
[cuerdas.core :as str] [cuerdas.core :as str]
[expound.alpha :as expound])) [expound.alpha :as expound]))
(defn get-context-string (defn get-context-string
[request edata] [request edata]
(str "=| uri: " (pr-str (:uri request)) "\n" (str "=| uri: " (pr-str (:uri request)) "\n"
"=| method: " (pr-str (:request-method 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) (when (map? edata)
(str "=| ex-data: " (pr-str edata) "\n")) (str "=| ex-data: \n"
(with-out-str
(pprint edata))))
"\n")) "\n"))
@ -33,69 +38,60 @@
(or (:type edata) (or (:type edata)
(class err))))) (class err)))))
(defmethod handle-exception :authorization
[err _]
{:status 403
:body (ex-data err)})
(defmethod handle-exception :authentication (defmethod handle-exception :authentication
[err _] [err _]
{:status 401 {:status 401 :body (ex-data err)})
:body (ex-data err)})
(defn- explain-error
[error]
(with-out-str
(expound/printer (:data error))))
(defmethod handle-exception :validation (defmethod handle-exception :validation
[err req] [err req]
(let [header (get-in req [:headers "accept"]) (let [header (get-in req [:headers "accept"])
edata (ex-data err)] edata (ex-data err)]
(cond (if (and (= :spec-validation (:code edata))
(= :spec-validation (:code edata)) (str/starts-with? header "text/html"))
(if (str/starts-with? header "text/html")
{:status 400
:headers {"content-type" "text/html"}
:body (str "<pre style='font-size:16px'>"
(with-out-str
(expound/printer (:data edata)))
"</pre>\n")}
{:status 400
:body (assoc edata :explain (with-out-str (expound/printer (:data edata))))})
:else
{:status 400 {:status 400
:body edata}))) :headers {"content-type" "text/html"}
:body (str "<pre style='font-size:16px'>"
(explain-error edata)
"</pre>\n")}
{:status 400
:body (cond-> edata
(map? (:data edata))
(-> (assoc :explain (explain-error edata))
(dissoc :data)))})))
(defmethod handle-exception :assertion (defmethod handle-exception :assertion
[error request] [error request]
(let [edata (ex-data error)] (let [edata (ex-data error)]
(log/error error (log/error error
(str "Assertion error\n" (str "Internal error: assertion\n"
(get-context-string request edata) (get-context-string request edata)
(with-out-str (expound/printer (:data edata))))) (explain-error edata)))
{:status 500 {: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 (defmethod handle-exception :not-found
[err _] [err _]
(let [response (ex-data err)] {:status 404 :body (ex-data err)})
{:status 404
:body response}))
(defmethod handle-exception :service-error
[err req]
(handle-exception (.getCause ^Throwable err) req))
(defmethod handle-exception :default (defmethod handle-exception :default
[error request] [error request]
(let [edata (ex-data error)] (let [edata (ex-data error)]
(log/error error (log/error error
(str "Internal Error\n" (str "Internal Error: "
(get-context-string request edata))) (ex-message error)
(if (nil? edata) (get-context-string request edata)))
{:status 500 {:status 500
:body {:type :server-error :body {:type :server-error
:hint (ex-message error)}} :hint (ex-message error)
{:status 500 :data edata}}))
:body (dissoc edata :data)})))
(defn handle (defn handle
[error req] [error req]

View file

@ -108,7 +108,8 @@
(proj/check-edition-permissions! conn profile-id id) (proj/check-edition-permissions! conn profile-id id)
(db/update! conn :project (db/update! conn :project
{:name name} {:name name}
{:id id}))) {:id id})
nil))
;; --- Mutation: Delete Project ;; --- Mutation: Delete Project

View file

@ -71,8 +71,8 @@
[conn file-id page-id token] [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"] (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])) (when-not (:exists (db/exec-one! conn [sql file-id page-id token]))
(ex/raise :type :authorization (ex/raise :type :not-found
:code :unauthorized-token)))) :code :object-not-found))))
(defn retrieve-shared-token (defn retrieve-shared-token
[conn file-id page-id] [conn file-id page-id]

View file

@ -140,13 +140,14 @@
[spec x message context] [spec x message context]
(if (s/valid? spec x) (if (s/valid? spec x)
x x
(let [data (s/explain-data spec x) (let [data (s/explain-data spec x)
hint (with-out-str (s/explain-out data))] explain (with-out-str (s/explain-out data))]
(ex/raise :type :assertion (ex/raise :type :assertion
:code :spec-validation
:hint message
:data data :data data
:hint hint :explain explain
:context context :context context
:message message
#?@(:cljs [:stack (.-stack (ex-info message {}))]))))) #?@(:cljs [:stack (.-stack (ex-info message {}))])))))
@ -181,13 +182,13 @@
[spec data] [spec data]
(let [result (s/conform spec data)] (let [result (s/conform spec data)]
(when (= result ::s/invalid) (when (= result ::s/invalid)
(let [data (s/explain-data spec data) (let [data (s/explain-data spec data)
hint (with-out-str explain (with-out-str
(s/explain-out data))] (s/explain-out data))]
(throw (ex/error :type :validation (throw (ex/error :type :validation
:code :spec-validation :code :spec-validation
:data data :explain explain
:hint hint)))) :data data))))
result)) result))
(defmacro instrument! (defmacro instrument!

View file

@ -18,7 +18,7 @@
} }
}, },
"auth.create-demo-account" : { "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" : { "translations" : {
"en" : "Create demo account", "en" : "Create demo account",
"fr" : "Vous voulez juste essayer?", "fr" : "Vous voulez juste essayer?",
@ -27,7 +27,7 @@
} }
}, },
"auth.create-demo-profile" : { "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" : { "translations" : {
"en" : "Just wanna try it?", "en" : "Just wanna try it?",
"fr" : "Vous voulez juste essayer?", "fr" : "Vous voulez juste essayer?",
@ -45,7 +45,7 @@
} }
}, },
"auth.email" : { "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" : { "translations" : {
"en" : "Email", "en" : "Email",
"fr" : "Adresse email", "fr" : "Adresse email",
@ -196,7 +196,7 @@
} }
}, },
"auth.password" : { "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" : { "translations" : {
"en" : "Password", "en" : "Password",
"fr" : "Mot de passe", "fr" : "Mot de passe",
@ -259,7 +259,7 @@
} }
}, },
"auth.register-submit" : { "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" : { "translations" : {
"en" : "Create an account", "en" : "Create an account",
"fr" : "Créer un compte", "fr" : "Créer un compte",
@ -295,7 +295,7 @@
} }
}, },
"dashboard.add-shared" : { "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" : { "translations" : {
"en" : "Add as Shared Library", "en" : "Add as Shared Library",
"fr" : "", "fr" : "",
@ -343,7 +343,7 @@
} }
}, },
"dashboard.empty-files" : { "dashboard.empty-files" : {
"used-in" : [ "src/app/main/ui/dashboard/grid.cljs:187" ], "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:189" ],
"translations" : { "translations" : {
"en" : "You still have no files here", "en" : "You still have no files here",
"fr" : "Vous n'avez encore aucun fichier ici", "fr" : "Vous n'avez encore aucun fichier ici",
@ -517,7 +517,7 @@
} }
}, },
"dashboard.num-of-members" : { "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" : { "translations" : {
"en" : "%s members", "en" : "%s members",
"es" : "%s integrantes" "es" : "%s integrantes"
@ -542,7 +542,7 @@
} }
}, },
"dashboard.promote-to-owner" : { "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" : { "translations" : {
"en" : "Promote to owner", "en" : "Promote to owner",
"es" : "Promover a dueño" "es" : "Promover a dueño"
@ -558,7 +558,7 @@
} }
}, },
"dashboard.remove-shared" : { "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" : { "translations" : {
"en" : "Remove as Shared Library", "en" : "Remove as Shared Library",
"fr" : "", "fr" : "",
@ -603,7 +603,7 @@
} }
}, },
"dashboard.show-all-files" : { "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" : { "translations" : {
"en" : "Show all files", "en" : "Show all files",
"es" : "Ver todos los ficheros" "es" : "Ver todos los ficheros"
@ -626,21 +626,21 @@
} }
}, },
"dashboard.team-info" : { "dashboard.team-info" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:282" ], "used-in" : [ "src/app/main/ui/dashboard/team.cljs:284" ],
"translations" : { "translations" : {
"en" : "Team info", "en" : "Team info",
"es" : "Información del equipo" "es" : "Información del equipo"
} }
}, },
"dashboard.team-members" : { "dashboard.team-members" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:293" ], "used-in" : [ "src/app/main/ui/dashboard/team.cljs:295" ],
"translations" : { "translations" : {
"en" : "Team members", "en" : "Team members",
"es" : "Integrantes del equipo" "es" : "Integrantes del equipo"
} }
}, },
"dashboard.team-projects" : { "dashboard.team-projects" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:302" ], "used-in" : [ "src/app/main/ui/dashboard/team.cljs:304" ],
"translations" : { "translations" : {
"en" : "Team projects", "en" : "Team projects",
"es" : "Proyectos del equipo" "es" : "Proyectos del equipo"
@ -674,7 +674,7 @@
} }
}, },
"dashboard.update-settings" : { "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" : { "translations" : {
"en" : "Update settings", "en" : "Update settings",
"fr" : "Mettre à jour les paramètres", "fr" : "Mettre à jour les paramètres",
@ -796,7 +796,7 @@
} }
}, },
"errors.clipboard-not-implemented" : { "errors.clipboard-not-implemented" : {
"used-in" : [ "src/app/main/data/workspace.cljs:1251" ], "used-in" : [ "src/app/main/data/workspace.cljs:1375" ],
"translations" : { "translations" : {
"en" : "Your browser cannot do this operation, please use Ctrl-V", "en" : "Your browser cannot do this operation, please use Ctrl-V",
"fr" : "", "fr" : "",
@ -805,7 +805,7 @@
} }
}, },
"errors.email-already-exists" : { "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" : { "translations" : {
"en" : "Email already used", "en" : "Email already used",
"fr" : "Adresse e-mail déjà utilisée", "fr" : "Adresse e-mail déjà utilisée",
@ -832,7 +832,7 @@
} }
}, },
"errors.generic" : { "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" : { "translations" : {
"en" : "Something wrong has happened.", "en" : "Something wrong has happened.",
"fr" : "Quelque chose c'est mal passé.", "fr" : "Quelque chose c'est mal passé.",
@ -859,7 +859,7 @@
} }
}, },
"errors.media-type-mismatch" : { "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" : { "translations" : {
"en" : "Seems that the contents of the image does not match the file extension.", "en" : "Seems that the contents of the image does not match the file extension.",
"fr" : "", "fr" : "",
@ -868,7 +868,7 @@
} }
}, },
"errors.media-type-not-allowed" : { "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" : { "translations" : {
"en" : "Seems that this is not a valid image.", "en" : "Seems that this is not a valid image.",
"fr" : "", "fr" : "",
@ -913,7 +913,7 @@
} }
}, },
"errors.unexpected-error" : { "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" : { "translations" : {
"en" : "An unexpected error occurred.", "en" : "An unexpected error occurred.",
"fr" : "Une erreur inattendue c'est produite", "fr" : "Une erreur inattendue c'est produite",
@ -986,21 +986,21 @@
} }
}, },
"handoff.attributes.image.download" : { "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" : { "translations" : {
"en" : "Dowload source image", "en" : "Dowload source image",
"es" : "Descargar imagen original" "es" : "Descargar imagen original"
} }
}, },
"handoff.attributes.image.height" : { "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" : { "translations" : {
"en" : "Height", "en" : "Height",
"es" : "Altura" "es" : "Altura"
} }
}, },
"handoff.attributes.image.width" : { "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" : { "translations" : {
"en" : "Width", "en" : "Width",
"es" : "Ancho" "es" : "Ancho"
@ -1368,7 +1368,7 @@
"unused" : true "unused" : true
}, },
"labels.admin" : { "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" : { "translations" : {
"en" : "Admin", "en" : "Admin",
"es" : "Administración" "es" : "Administración"
@ -1381,6 +1381,18 @@
"es" : "Todo" "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" : { "labels.cancel" : {
"used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:201" ], "used-in" : [ "src/app/main/ui/dashboard/sidebar.cljs:201" ],
"translations" : { "translations" : {
@ -1414,7 +1426,7 @@
} }
}, },
"labels.delete" : { "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" : { "translations" : {
"en" : "Delete", "en" : "Delete",
"fr" : "Supprimer", "fr" : "Supprimer",
@ -1453,14 +1465,14 @@
} }
}, },
"labels.editor" : { "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" : { "translations" : {
"en" : "Editor", "en" : "Editor",
"es" : "Editor" "es" : "Editor"
} }
}, },
"labels.email" : { "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" : { "translations" : {
"en" : "Email", "en" : "Email",
"fr" : "Adresse email", "fr" : "Adresse email",
@ -1478,7 +1490,7 @@
} }
}, },
"labels.hide-resolved-comments" : { "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" : { "translations" : {
"en" : "Hide resolved comments", "en" : "Hide resolved comments",
"es" : "Ocultar comentarios resueltos" "es" : "Ocultar comentarios resueltos"
@ -1503,14 +1515,14 @@
} }
}, },
"labels.members" : { "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" : { "translations" : {
"en" : "Members", "en" : "Members",
"es" : "Integrantes" "es" : "Integrantes"
} }
}, },
"labels.name" : { "labels.name" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:214" ], "used-in" : [ "src/app/main/ui/dashboard/team.cljs:216" ],
"translations" : { "translations" : {
"en" : "Name", "en" : "Name",
"fr" : "Nom", "fr" : "Nom",
@ -1534,15 +1546,33 @@
"es" : "No tienes notificaciones de comentarios pendientes" "es" : "No tienes notificaciones de comentarios pendientes"
} }
}, },
"labels.not-found.auth-info" : {
"used-in" : [ "src/app/main/ui/static.cljs:42" ],
"translations" : {
"en" : "Youre 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 dont 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" : { "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" : { "translations" : {
"en" : [ "1 file", "%s files" ], "en" : [ "1 file", "%s files" ],
"es" : [ "1 archivo", "%s archivos" ] "es" : [ "1 archivo", "%s archivos" ]
} }
}, },
"labels.num-of-projects" : { "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" : { "translations" : {
"en" : [ "1 project", "%s projects" ], "en" : [ "1 project", "%s projects" ],
"es" : [ "1 proyecto", "%s proyectos" ] "es" : [ "1 proyecto", "%s proyectos" ]
@ -1565,7 +1595,7 @@
} }
}, },
"labels.owner" : { "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" : { "translations" : {
"en" : "Owner", "en" : "Owner",
"es" : "Dueño" "es" : "Dueño"
@ -1581,7 +1611,7 @@
} }
}, },
"labels.permissions" : { "labels.permissions" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:216" ], "used-in" : [ "src/app/main/ui/dashboard/team.cljs:218" ],
"translations" : { "translations" : {
"en" : "Permissions", "en" : "Permissions",
"es" : "Permisos" "es" : "Permisos"
@ -1606,7 +1636,7 @@
} }
}, },
"labels.remove" : { "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" : { "translations" : {
"en" : "Remove", "en" : "Remove",
"fr" : "", "fr" : "",
@ -1615,12 +1645,18 @@
} }
}, },
"labels.rename" : { "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" : { "translations" : {
"en" : "Rename", "en" : "Rename",
"es" : "Renombrar" "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" : { "labels.role" : {
"used-in" : [ "src/app/main/ui/dashboard/team.cljs:84" ], "used-in" : [ "src/app/main/ui/dashboard/team.cljs:84" ],
"translations" : { "translations" : {
@ -1628,8 +1664,20 @@
"es" : "Cargo" "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" : { "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" : { "translations" : {
"en" : "Settings", "en" : "Settings",
"fr" : "Settings", "fr" : "Settings",
@ -1647,19 +1695,28 @@
} }
}, },
"labels.show-all-comments" : { "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" : { "translations" : {
"en" : "Show all comments", "en" : "Show all comments",
"es" : "Mostrar todos los comentarios" "es" : "Mostrar todos los comentarios"
} }
}, },
"labels.show-your-comments" : { "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" : { "translations" : {
"en" : "Show only yours comments", "en" : "Show only yours comments",
"es" : "Mostrar sólo tus comentarios" "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" : { "labels.update" : {
"used-in" : [ "src/app/main/ui/settings/profile.cljs:104" ], "used-in" : [ "src/app/main/ui/settings/profile.cljs:104" ],
"translations" : { "translations" : {
@ -1670,7 +1727,7 @@
} }
}, },
"labels.viewer" : { "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" : { "translations" : {
"en" : "Viewer", "en" : "Viewer",
"es" : "Visualizador" "es" : "Visualizador"
@ -1684,7 +1741,7 @@
} }
}, },
"media.loading" : { "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" : { "translations" : {
"en" : "Loading image...", "en" : "Loading image...",
"fr" : "Chargement de l'image...", "fr" : "Chargement de l'image...",
@ -1852,13 +1909,13 @@
} }
}, },
"modals.delete-page.body" : { "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" : { "translations" : {
"en" : "Are you sure you want to delete this page?" "en" : "Are you sure you want to delete this page?"
} }
}, },
"modals.delete-page.title" : { "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" : { "translations" : {
"en" : "Delete page" "en" : "Delete page"
} }
@ -1906,28 +1963,28 @@
} }
}, },
"modals.delete-team-member-confirm.accept" : { "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" : { "translations" : {
"en" : "Delete member", "en" : "Delete member",
"es" : "Eliminando miembro" "es" : "Eliminando miembro"
} }
}, },
"modals.delete-team-member-confirm.message" : { "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" : { "translations" : {
"en" : "Are you sure you want to delete this member from the team?", "en" : "Are you sure you want to delete this member from the team?",
"es" : "¿Seguro que quieres eliminar este integrante del equipo?" "es" : "¿Seguro que quieres eliminar este integrante del equipo?"
} }
}, },
"modals.delete-team-member-confirm.title" : { "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" : { "translations" : {
"en" : "Delete team member", "en" : "Delete team member",
"es" : "Eliminar integrante del equipo" "es" : "Eliminar integrante del equipo"
} }
}, },
"modals.invite-member.title" : { "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" : { "translations" : {
"en" : "Invite to join the team", "en" : "Invite to join the team",
"es" : "Invitar a unirse al equipo" "es" : "Invitar a unirse al equipo"
@ -1990,21 +2047,21 @@
} }
}, },
"modals.promote-owner-confirm.accept" : { "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" : { "translations" : {
"en" : "Promote", "en" : "Promote",
"es" : "Promocionar" "es" : "Promocionar"
} }
}, },
"modals.promote-owner-confirm.message" : { "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" : { "translations" : {
"en" : "Are you sure you want to promote this user to owner?", "en" : "Are you sure you want to promote this user to owner?",
"es" : "¿Seguro que quieres promocionar este usuario a dueño?" "es" : "¿Seguro que quieres promocionar este usuario a dueño?"
} }
}, },
"modals.promote-owner-confirm.title" : { "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" : { "translations" : {
"en" : "Promote to owner", "en" : "Promote to owner",
"es" : "Promocionar a dueño" "es" : "Promocionar a dueño"
@ -2047,7 +2104,7 @@
} }
}, },
"notifications.profile-saved" : { "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" : { "translations" : {
"en" : "Profile saved successfully!", "en" : "Profile saved successfully!",
"fr" : "Profil enregistré avec succès!", "fr" : "Profil enregistré avec succès!",
@ -2056,7 +2113,7 @@
} }
}, },
"notifications.validation-email-sent" : { "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" : { "translations" : {
"en" : "Verification email sent to %s. Check your email!", "en" : "Verification email sent to %s. Check your email!",
"es" : "Verificación de email enviada a %s. Comprueba tu correo." "es" : "Verificación de email enviada a %s. Comprueba tu correo."
@ -2072,7 +2129,7 @@
} }
}, },
"settings.multiple" : { "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" : { "translations" : {
"en" : "Mixed", "en" : "Mixed",
"fr" : null, "fr" : null,
@ -2351,7 +2408,7 @@
} }
}, },
"workspace.assets.delete" : { "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" : { "translations" : {
"en" : "Delete", "en" : "Delete",
"fr" : "", "fr" : "",
@ -2360,7 +2417,7 @@
} }
}, },
"workspace.assets.duplicate" : { "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" : { "translations" : {
"en" : "Duplicate", "en" : "Duplicate",
"fr" : "", "fr" : "",
@ -2414,7 +2471,7 @@
} }
}, },
"workspace.assets.rename" : { "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" : { "translations" : {
"en" : "Rename", "en" : "Rename",
"fr" : "", "fr" : "",
@ -2502,14 +2559,14 @@
} }
}, },
"workspace.gradients.linear" : { "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" : { "translations" : {
"en" : "Linear gradient", "en" : "Linear gradient",
"es" : "Degradado lineal" "es" : "Degradado lineal"
} }
}, },
"workspace.gradients.radial" : { "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" : { "translations" : {
"en" : "Radial gradient", "en" : "Radial gradient",
"es" : "Degradado radial" "es" : "Degradado radial"
@ -2706,21 +2763,21 @@
} }
}, },
"workspace.libraries.colors.big-thumbnails" : { "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" : { "translations" : {
"en" : "Big thumbnails", "en" : "Big thumbnails",
"es" : "Miniaturas grandes" "es" : "Miniaturas grandes"
} }
}, },
"workspace.libraries.colors.file-library" : { "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" : { "translations" : {
"en" : "File library", "en" : "File library",
"es" : "Biblioteca del archivo" "es" : "Biblioteca del archivo"
} }
}, },
"workspace.libraries.colors.recent-colors" : { "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" : { "translations" : {
"en" : "Recent colors", "en" : "Recent colors",
"es" : "Colores recientes" "es" : "Colores recientes"
@ -2734,7 +2791,7 @@
} }
}, },
"workspace.libraries.colors.small-thumbnails" : { "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" : { "translations" : {
"en" : "Small thumbnails", "en" : "Small thumbnails",
"es" : "Miniaturas pequeñas" "es" : "Miniaturas pequeñas"
@ -3251,7 +3308,7 @@
} }
}, },
"workspace.options.position" : { "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" : { "translations" : {
"en" : "Position", "en" : "Position",
"fr" : "Position", "fr" : "Position",
@ -3384,7 +3441,7 @@
} }
}, },
"workspace.options.size" : { "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" : { "translations" : {
"en" : "Size", "en" : "Size",
"fr" : "Taille", "fr" : "Taille",
@ -3594,7 +3651,7 @@
} }
}, },
"workspace.options.text-options.none" : { "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" : { "translations" : {
"en" : "None", "en" : "None",
"fr" : "Aucune", "fr" : "Aucune",
@ -3733,7 +3790,7 @@
} }
}, },
"workspace.shape.menu.detach-instance" : { "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" : { "translations" : {
"en" : "Detach instance", "en" : "Detach instance",
"es" : "Desacoplar instancia" "es" : "Desacoplar instancia"
@ -3761,7 +3818,7 @@
} }
}, },
"workspace.shape.menu.go-master" : { "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" : { "translations" : {
"en" : "Go to master component file", "en" : "Go to master component file",
"es" : "Ir al archivo del componente maestro" "es" : "Ir al archivo del componente maestro"
@ -3803,7 +3860,7 @@
} }
}, },
"workspace.shape.menu.reset-overrides" : { "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" : { "translations" : {
"en" : "Reset overrides", "en" : "Reset overrides",
"es" : "Deshacer modificaciones" "es" : "Deshacer modificaciones"
@ -3817,7 +3874,7 @@
} }
}, },
"workspace.shape.menu.show-master" : { "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" : { "translations" : {
"en" : "Show master component", "en" : "Show master component",
"es" : "Ver componente maestro" "es" : "Ver componente maestro"
@ -3845,7 +3902,7 @@
} }
}, },
"workspace.shape.menu.update-master" : { "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" : { "translations" : {
"en" : "Update master component", "en" : "Update master component",
"es" : "Actualizar componente maestro" "es" : "Actualizar componente maestro"
@ -3861,7 +3918,7 @@
"unused" : true "unused" : true
}, },
"workspace.sidebar.sitemap" : { "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" : { "translations" : {
"en" : "Pages", "en" : "Pages",
"fr" : "Pages", "fr" : "Pages",
@ -4244,7 +4301,7 @@
} }
}, },
"workspace.updates.dismiss" : { "workspace.updates.dismiss" : {
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:649" ], "used-in" : [ "src/app/main/data/workspace/libraries.cljs:697" ],
"translations" : { "translations" : {
"en" : "Dismiss", "en" : "Dismiss",
"fr" : "", "fr" : "",
@ -4253,7 +4310,7 @@
} }
}, },
"workspace.updates.there-are-updates" : { "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" : { "translations" : {
"en" : "There are updates in shared libraries", "en" : "There are updates in shared libraries",
"fr" : "", "fr" : "",
@ -4262,7 +4319,7 @@
} }
}, },
"workspace.updates.update" : { "workspace.updates.update" : {
"used-in" : [ "src/app/main/data/workspace/libraries.cljs:647" ], "used-in" : [ "src/app/main/data/workspace/libraries.cljs:695" ],
"translations" : { "translations" : {
"en" : "Update", "en" : "Update",
"fr" : "", "fr" : "",

View file

@ -12,22 +12,22 @@
// //
//################################################# //#################################################
@import 'common/dependencies/colors'; @import "common/dependencies/colors";
@import 'common/dependencies/helpers'; @import "common/dependencies/helpers";
@import 'common/dependencies/mixin'; @import "common/dependencies/mixin";
@import 'common/dependencies/fonts'; @import "common/dependencies/fonts";
@import 'common/dependencies/reset'; @import "common/dependencies/reset";
@import 'common/dependencies/animations'; @import "common/dependencies/animations";
@import 'common/dependencies/z-index'; @import "common/dependencies/z-index";
@import 'common/dependencies/highlightjs-theme'; @import "common/dependencies/highlightjs-theme";
//################################################# //#################################################
// Layouts // Layouts
//################################################# //#################################################
@import 'common/base'; @import "common/base";
@import 'main/layouts/login'; @import "main/layouts/login";
@import 'main/layouts/main-layout'; @import "main/layouts/main-layout";
@import "main/layouts/not-found"; @import "main/layouts/not-found";
@import "main/layouts/viewer"; @import "main/layouts/viewer";
@import "main/layouts/handoff"; @import "main/layouts/handoff";
@ -36,12 +36,12 @@
// Commons // Commons
//################################################# //#################################################
@import 'common/framework'; @import "common/framework";
@import 'main/partials/modal'; @import "main/partials/modal";
@import 'main/partials/forms'; @import "main/partials/forms";
@import "main/partials/texts"; @import "main/partials/texts";
@import 'main/partials/context-menu'; @import "main/partials/context-menu";
@import 'main/partials/dropdown'; @import "main/partials/dropdown";
//################################################# //#################################################
// Partials // Partials
@ -51,35 +51,36 @@
@import "main/partials/viewer-header"; @import "main/partials/viewer-header";
@import "main/partials/viewer-thumbnails"; @import "main/partials/viewer-thumbnails";
@import "main/partials/zoom-widget"; @import "main/partials/zoom-widget";
@import 'main/partials/activity-bar'; @import "main/partials/activity-bar";
@import 'main/partials/color-palette'; @import "main/partials/color-palette";
@import 'main/partials/colorpicker'; @import "main/partials/colorpicker";
@import 'main/partials/dashboard'; @import "main/partials/dashboard";
@import 'main/partials/dashboard-header'; @import "main/partials/dashboard-header";
@import 'main/partials/dashboard-grid'; @import "main/partials/dashboard-grid";
@import 'main/partials/dashboard-sidebar'; @import "main/partials/dashboard-sidebar";
@import 'main/partials/dashboard-team'; @import "main/partials/dashboard-team";
@import 'main/partials/dashboard-settings'; @import "main/partials/dashboard-settings";
@import 'main/partials/debug-icons-preview'; @import "main/partials/debug-icons-preview";
@import 'main/partials/editable-label'; @import "main/partials/editable-label";
@import 'main/partials/left-toolbar'; @import "main/partials/left-toolbar";
@import 'main/partials/loader'; @import "main/partials/loader";
@import 'main/partials/project-bar'; @import "main/partials/project-bar";
@import 'main/partials/sidebar'; @import "main/partials/sidebar";
@import 'main/partials/sidebar-align-options'; @import "main/partials/sidebar-align-options";
@import 'main/partials/sidebar-assets'; @import "main/partials/sidebar-assets";
@import 'main/partials/sidebar-document-history'; @import "main/partials/sidebar-document-history";
@import 'main/partials/sidebar-element-options'; @import "main/partials/sidebar-element-options";
@import 'main/partials/sidebar-icons'; @import "main/partials/sidebar-icons";
@import 'main/partials/sidebar-interactions'; @import "main/partials/sidebar-interactions";
@import 'main/partials/sidebar-layers'; @import "main/partials/sidebar-layers";
@import 'main/partials/sidebar-sitemap'; @import "main/partials/sidebar-sitemap";
@import 'main/partials/sidebar-tools'; @import "main/partials/sidebar-tools";
@import 'main/partials/tab-container'; @import "main/partials/tab-container";
@import 'main/partials/tool-bar'; @import "main/partials/tool-bar";
@import 'main/partials/user-settings'; @import "main/partials/user-settings";
@import 'main/partials/workspace'; @import "main/partials/workspace";
@import 'main/partials/workspace-header'; @import "main/partials/workspace-header";
@import 'main/partials/comments'; @import "main/partials/comments";
@import 'main/partials/color-bullet'; @import "main/partials/color-bullet";
@import "main/partials/handoff"; @import "main/partials/handoff";
@import "main/partials/exception-page";

View file

@ -29,34 +29,55 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
.error-img {
.container {
max-width: 600px;
}
.image {
align-items: center; align-items: center;
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-bottom: 2rem; margin-bottom: 2rem;
svg { svg {
height: 320px; height: 220px;
width: 200px; width: 220px;
} }
} }
.main-message { .main-message {
color: $color-black; color: $color-black;
font-size: 11rem; font-size: 5rem;
line-height: 200px; line-height: 150px;
text-align: center; text-align: center;
} }
.desc-message { .desc-message {
color: $color-black; color: $color-black;
font-size: 2.2rem; font-size: 1.6rem;
font-weight: 300; font-weight: 300;
text-align: center; text-align: center;
} }
.btn-primary { .sign-info {
margin-top: $x-big; 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;
}
} }
} }

View file

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

View file

@ -81,7 +81,7 @@
(rt/initialize-history on-navigate)) (rt/initialize-history on-navigate))
(st/emit! udu/fetch-profile) (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"))) (mf/mount (mf/element modal) (dom/get-element "modal")))
(defn ^:export init (defn ^:export init

View file

@ -129,3 +129,13 @@
:actions actions :actions actions
:tag tag}))) :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)))))

View file

@ -130,13 +130,11 @@
(rx/map #(shapes-changes-persisted file-id %)))))) (rx/map #(shapes-changes-persisted file-id %))))))
on-error on-error
(fn [{:keys [type status] :as error}] (fn [{:keys [type] :as error}]
(if (and (= :server-error type) (if (or (= :bad-gateway type)
(= 502 status)) (= :service-unavailable type))
(rx/of (update-persistence-status {:status :error :reason type})) (rx/of (update-persistence-status {:status :error :reason type}))
(rx/of update-persistence-queue (rx/throw error)))]
(update-persistence-status {:status :error :reason type}))))]
(when (= file-id (:id file)) (when (= file-id (:id file))
(->> (rp/mutation :update-file params) (->> (rp/mutation :update-file params)
@ -219,18 +217,7 @@
(rp/query :project {:id project-id}) (rp/query :project {:id project-id})
(rp/query :file-libraries {:file-id file-id})) (rp/query :file-libraries {:file-id file-id}))
(rx/first) (rx/first)
(rx/map (fn [bundle] (apply bundle-fetched bundle))) (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))))))))
(defn- bundle-fetched (defn- bundle-fetched
[file users project libraries] [file users project libraries]

View file

@ -31,6 +31,9 @@
(def profile (def profile
(l/derived :profile st/state)) (l/derived :profile st/state))
(def exception
(l/derived :exception st/state))
;; ---- Dashboard refs ;; ---- Dashboard refs
(def dashboard-local (def dashboard-local

View file

@ -15,34 +15,32 @@
[app.util.http-api :as http])) [app.util.http-api :as http]))
(defn- handle-response (defn- handle-response
[response] [{:keys [status body] :as response}]
(cond (cond
(http/success? response) (= 204 status)
(rx/of (:body response)) (rx/empty)
(= (:status response) 400) (= 502 status)
(rx/throw (:body response)) (rx/throw {:type :bad-gateway})
(= (:status response) 401) (= 503 status)
(rx/throw {:type :authentication (rx/throw {:type :service-unavailable})
:code :not-authenticated})
(= (:status response) 403)
(rx/throw {:type :authorization
:code :not-authorized})
(= (:status response) 404)
(rx/throw (:body response))
(= 0 (:status response)) (= 0 (:status response))
(rx/throw {:type :offline}) (rx/throw {:type :offline})
(and (= 200 status)
(coll? body))
(rx/of body)
(and (>= status 400)
(map? body))
(rx/throw body)
:else :else
(rx/throw (merge {:type :server-error (rx/throw {:type :unexpected-error
:status (:status response)} :status status
(:body response))))) :data body})))
(defn send-query! (defn send-query!
[id params] [id params]

View file

@ -26,6 +26,12 @@
(defonce state (ptk/store {:resolve ptk/resolve})) (defonce state (ptk/store {:resolve ptk/resolve}))
(defonce stream (ptk/input-stream state)) (defonce stream (ptk/input-stream state))
(defn ^boolean is-logged?
[pdata]
(and (some? pdata)
(uuid? (:id pdata))
(not= uuid/zero (:id pdata))))
(when *assert* (when *assert*
(defonce debug-subscription (defonce debug-subscription
(->> stream (->> stream

View file

@ -28,7 +28,7 @@
[app.main.ui.messages :as msgs] [app.main.ui.messages :as msgs]
[app.main.ui.render :as render] [app.main.ui.render :as render]
[app.main.ui.settings :as settings] [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.viewer :refer [viewer-page]]
[app.main.ui.handoff :refer [handoff]] [app.main.ui.handoff :refer [handoff]]
[app.main.ui.workspace :as workspace] [app.main.ui.workspace :as workspace]
@ -37,6 +37,7 @@
[app.util.router :as rt] [app.util.router :as rt]
[cuerdas.core :as str] [cuerdas.core :as str]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[cljs.pprint :refer [pprint]]
[expound.alpha :as expound] [expound.alpha :as expound]
[potok.core :as ptk] [potok.core :as ptk]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
@ -81,9 +82,6 @@
:conform {:path-params ::viewer-path-params :conform {:path-params ::viewer-path-params
:query-params ::viewer-query-params}}] :query-params ::viewer-query-params}}]
["/not-found" :not-found]
["/not-authorized" :not-authorized]
(when *assert* (when *assert*
["/debug/icons-preview" :debug-icons-preview]) ["/debug/icons-preview" :debug-icons-preview])
@ -100,19 +98,15 @@
["/workspace/:project-id/:file-id" :workspace]]) ["/workspace/:project-id/:file-id" :workspace]])
(mf/defc app-error (mf/defc on-main-error
[{:keys [error] :as props}] [{:keys [error] :as props}]
(let [data (ex-data error)] (let [data (ex-data error)]
(case (:type data) (ptk/handle-error error)
:not-found [:& not-found-page {:error data}] [:span "Internal application errror"]))
(do
(ptk/handle-error error)
[:span "Internal application errror"]))))
(mf/defc app (mf/defc main-page
{::mf/wrap [#(mf/catch % {:fallback app-error})]} {::mf/wrap [#(mf/catch % {:fallback on-main-error})]}
[{:keys [route] :as props}] [{:keys [route] :as props}]
[:& (mf/provider ctx/current-route) {:value route} [:& (mf/provider ctx/current-route) {:value route}
(case (get-in route [:data :name]) (case (get-in route [:data :name])
(:auth-login (:auth-login
@ -189,67 +183,71 @@
:page-id page-id :page-id page-id
:layout-name (keyword layout-name) :layout-name (keyword layout-name)
:key file-id}]) :key file-id}])
:not-authorized
[:& not-authorized-page]
:not-found
[:& not-found-page]
nil)]) nil)])
(mf/defc app-wrapper (mf/defc app
[] []
(let [route (mf/deref refs/route)] (let [route (mf/deref refs/route)
[:* edata (mf/deref refs/exception)]
[:& msgs/notifications] [:& (mf/provider ctx/current-route) {:value route}
(when route (if edata
[:& app {:route route}])])) [:& static/exception-page {:data edata}]
[:*
[:& msgs/notifications]
(when route
[:& main-page {:route route}])])]))
;; --- Error Handling ;; --- 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 (defmethod ptk/handle-error :validation
[error] [error]
(ts/schedule (ts/schedule
(st/emitf (dm/show {:content "Unexpected validation error (server side)." (st/emitf
:type :error (dm/show {:content "Unexpected validation error (server side)."
:timeout 5000}))) :type :error
(when-let [explain (:hint-verbose error)] :timeout 3000})))
(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 :spec-validation ;; Print to the console some debug info.
[error] (js/console.group "Server Error")
(ts/schedule (js/console.info
(st/emitf (dm/show {:content "Unexpected validation error (server side)." (with-out-str
:type :error (pprint (dissoc error :explain))))
:timeout 5000})))
(when-let [explain (:explain error)] (when-let [explain (:explain error)]
(js/console.group "Server Error") (js/console.error explain))
(js/console.error (if (map? error) (pr-str error) error)) (js/console.endGroup "Server 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}))))
;; 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 (defmethod ptk/handle-error :assertion
[{:keys [data stack message context] :as error}] [{:keys [data stack message context] :as error}]
(ts/schedule (ts/schedule
(st/emitf (dm/show {:content "Internal assertion error." (st/emitf (dm/show {:content "Internal error: assertion."
:type :error :type :error
:timeout 2000}))) :timeout 3000})))
;; Print to the console some debugging info
(js/console.group message) (js/console.group message)
(js/console.info (str/format "ns: '%s'\nname: '%s'\nfile: '%s:%s'" (js/console.info (str/format "ns: '%s'\nname: '%s'\nfile: '%s:%s'"
(:ns context) (:ns context)
@ -259,48 +257,49 @@
(js/console.groupCollapsed "Stack Trace") (js/console.groupCollapsed "Stack Trace")
(js/console.info stack) (js/console.info stack)
(js/console.groupEnd "Stack Trace") (js/console.groupEnd "Stack Trace")
(js/console.error (with-out-str (expound/printer data))) (js/console.error (with-out-str (expound/printer data)))
(js/console.groupEnd message)) (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 (defmethod ptk/handle-error :default
[error] [error]
(if (instance? ExceptionInfo error) (if (instance? ExceptionInfo error)
(ptk/handle-error (ex-data error)) (ptk/handle-error (ex-data error))
(do (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) (js/console.log "hint:" (or (ex-message error)
(:hint error) (:hint error)
(:message error))) (:message error)))
(ex/ignoring (ex/ignoring
(js/console.error "repr: " (pr-str error)) (js/console.error "repr: " (pr-str error))
(js/console.error "data: " (clj->js error))
(js/console.error "stack:" (.-stack error))) (js/console.error "stack:" (.-stack error)))
(js/console.groupEnd "Generic error") (js/console.groupEnd "Internal error:"))))
(ts/schedule (st/emitf (dm/show
{:content "Something wrong has happened."
:type :error
:timeout 3000}))))))
(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 (defonce uncaught-error-handler
(letfn [(on-error [event] (letfn [(on-error [event]

View file

@ -11,27 +11,104 @@
(:require (:require
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[rumext.alpha :as mf] [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])) [app.main.ui.icons :as i]))
(mf/defc not-found-page (defn- go-to-dashboard
[{:keys [error] :as props}] [profile]
[:section.not-found-layout (let [team-id (:default-team-id profile)]
[:div.not-found-header i/logo] (st/emit! (rt/nav :dashboard-projects {:team-id team-id}))))
[: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"]]]])
(mf/defc not-authorized-page (mf/defc not-found
[{:keys [error] :as props}] [{:keys [error] :as props}]
[:section.not-found-layout (let [profile (mf/deref refs/profile)]
[:div.not-found-header i/logo] [:section.exception-layout
[:div.not-found-content [:div.exception-header
[:div.message-container {:on-click (partial go-to-dashboard profile)}
[:div.error-img i/icon-lock] i/logo]
[:div.main-message "403"] [:div.exception-content
[:div.desc-message "Sorry, you are not authorized to access this page."] [:div.container
#_[:a.btn-primary.btn-small "Go back"]]]]) [: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))

View file

@ -10,15 +10,15 @@
(ns app.util.router (ns app.util.router
(:refer-clojure :exclude [resolve]) (:refer-clojure :exclude [resolve])
(:require (: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] [beicon.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[goog.events :as e] [goog.events :as e]
[potok.core :as ptk] [potok.core :as ptk]
[reitit.core :as r] [reitit.core :as r])
[app.common.data :as d]
[app.config :as cfg]
[app.util.browser-history :as bhistory]
[app.util.timers :as ts])
(:import (:import
goog.Uri goog.Uri
goog.Uri.QueryData)) goog.Uri.QueryData))
@ -92,6 +92,10 @@
;; --- Navigate (Event) ;; --- Navigate (Event)
(deftype Navigate [id params qparams replace] (deftype Navigate [id params qparams replace]
ptk/UpdateEvent
(update [_ state]
(dissoc state :exception))
ptk/EffectEvent ptk/EffectEvent
(effect [_ state stream] (effect [_ state stream]
(let [router (:router state) (let [router (:router state)