0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-18 10:41:29 -05:00

🎉 Add team webhooks section

This commit is contained in:
Pablo Alba 2022-12-02 13:05:21 +01:00
parent 02d619ed48
commit cdbfec4f19
11 changed files with 677 additions and 5 deletions

View file

@ -12,7 +12,7 @@
[app.db :as db]
[app.http.client :as http]
[app.rpc.doc :as-alias doc]
[app.rpc.queries.teams :refer [check-edition-permissions!]]
[app.rpc.queries.teams :refer [check-edition-permissions! check-read-permissions!]]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as-alias wrk]
@ -49,7 +49,7 @@
(instance? javax.net.ssl.SSLHandshakeException exception)
(ex/raise :type :validation
:code :webhook-validation
:hint "ssl-validaton")
:hint "ssl-validation")
:else
(ex/raise :type :validation
@ -118,3 +118,19 @@
(check-edition-permissions! conn profile-id (:team-id whook))
(db/delete! conn :webhook {:id id})
nil)))
;; --- Query: Webhooks
(s/def ::team-id ::us/uuid)
(s/def ::get-webhooks
(s/keys :req-un [::profile-id ::team-id]))
(def sql:get-webhooks
"select id, uri, mtype, is_active, error_code, error_count
from webhook where team_id = ? order by uri")
(sv/defmethod ::get-webhooks
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id team-id)
(db/exec! conn [sql:get-webhooks team-id])))

View file

@ -93,7 +93,8 @@
}
.dashboard-team-members,
.dashboard-team-invitations {
.dashboard-team-invitations,
.dashboard-team-webhooks {
.empty-invitations {
height: 156px;
max-width: 1040px;
@ -197,6 +198,71 @@
}
}
}
&.uri,
&.active {
width: 48%;
min-width: 300px;
}
&.last-delivery {
display: flex;
justify-content: center;
width: 50px;
position: relative;
.success svg {
fill: $color-primary;
width: 16px;
height: 16px;
}
.failure svg {
fill: $color-warning;
width: 16px;
height: 16px;
}
.icon-container {
width: 16px;
height: 16px;
overflow-x: visible;
}
.icon {
padding: 0;
}
}
.tooltip {
display: none;
position: absolute;
top: -58px;
left: 50%;
transform: translate(-50%, 0);
text-align: center;
.label {
border-radius: 3px;
color: $color-white;
background-color: $color-black;
white-space: nowrap;
padding: 12px 20px;
}
.arrow-down {
margin: 0 auto;
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 8px solid $color-black;
}
}
.last-delivery-icon:hover {
.tooltip {
display: block;
}
}
}
.dropdown {
@ -380,3 +446,96 @@
}
}
}
.dashboard-team-webhooks {
display: flex;
flex-direction: column;
align-items: center;
.webhooks-hero-container {
max-width: 1000px;
width: 100%;
display: flex;
flex-direction: column;
.upload-button {
width: 100px;
}
.btn-secondary {
margin-left: 10px;
}
}
.webhooks-hero {
font-size: $fs14;
padding: $size-6;
background-color: $color-white;
margin-top: $size-6;
display: flex;
justify-content: space-between;
.banner {
background-color: unset;
display: flex;
.icon {
display: flex;
align-items: center;
padding-left: 0px;
padding-right: 10px;
svg {
fill: $color-info;
}
}
}
.desc {
h2 {
margin-bottom: $size-4;
color: $color-black;
}
width: 80%;
color: $color-gray-40;
p {
font-size: 16px;
}
}
.btn-primary {
flex-shrink: 0;
}
}
.webhooks-empty {
text-align: center;
max-width: 1000px;
width: 100%;
padding: $size-6;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1px dashed $color-gray-20;
color: $color-gray-40;
margin-top: 12px;
min-height: 136px;
}
}
.webhooks-modal {
.action-buttons {
gap: 10px;
}
.input-checkbox label {
font-size: 14px;
color: $color-black;
}
.explain {
font-size: 12px;
color: $color-gray-40;
}
}

View file

@ -149,6 +149,24 @@
(->> (rp/query! :team-invitations {:team-id team-id})
(rx/map team-invitations-fetched))))))
;; --- EVENT: fetch-team-webhooks
(defn team-webhooks-fetched
[webhooks]
(ptk/reify ::team-webhooks-fetched
ptk/UpdateEvent
(update [_ state]
(assoc state :dashboard-team-webhooks webhooks))))
(defn fetch-team-webhooks
[]
(ptk/reify ::fetch-team-webhooks
ptk/WatchEvent
(watch [_ state _]
(let [team-id (:current-team-id state)]
(->> (rp/command! :get-webhooks {:team-id team-id})
(rx/map team-webhooks-fetched))))))
;; --- EVENT: fetch-projects
(defn projects-fetched
@ -522,6 +540,61 @@
(rx/tap on-success)
(rx/catch on-error))))))
(defn delete-team-webhook
[{:keys [id] :as params}]
(us/assert ::us/uuid id)
(ptk/reify ::delete-team-webhook
ptk/WatchEvent
(watch [_ state _]
(let [team-id (:current-team-id state)
params (assoc params :team-id team-id)
{:keys [on-success on-error]
:or {on-success identity
on-error rx/throw}} (meta params)]
(->> (rp/command! :delete-webhook params)
(rx/tap on-success)
(rx/catch on-error))))))
(s/def ::mtype
#{"application/json"
"application/x-www-form-urlencoded"
"application/transit+json"})
(defn update-team-webhook
[{:keys [id uri mtype is-active] :as params}]
(us/assert ::us/uuid id)
(us/assert ::us/uri uri)
(us/assert ::mtype mtype)
(us/assert ::us/boolean is-active)
(ptk/reify ::update-team-webhook
ptk/WatchEvent
(watch [_ state _]
(let [team-id (:current-team-id state)
params (assoc params :team-id team-id)
{:keys [on-success on-error]
:or {on-success identity
on-error rx/throw}} (meta params)]
(->> (rp/command! :update-webhook params)
(rx/tap on-success)
(rx/catch on-error))))))
(defn create-team-webhook
[{:keys [uri mtype is-active] :as params}]
(us/assert ::us/uri uri)
(us/assert ::mtype mtype)
(us/assert ::us/boolean is-active)
(ptk/reify ::create-team-webhook
ptk/WatchEvent
(watch [_ state _]
(let [team-id (:current-team-id state)
params (assoc params :team-id team-id)
{:keys [on-success on-error]
:or {on-success identity
on-error rx/throw}} (meta params)]
(->> (rp/command! :create-webhook params)
(rx/tap on-success)
(rx/catch on-error))))))
;; --- EVENT: delete-team
(defn delete-team
@ -913,6 +986,14 @@
(let [team-id (:current-team-id state)]
(rx/of (rt/nav :dashboard-team-invitations {:team-id team-id}))))))
(defn go-to-team-webhooks
[]
(ptk/reify ::go-to-team-webhooks
ptk/WatchEvent
(watch [_ state _]
(let [team-id (:current-team-id state)]
(rx/of (rt/nav :dashboard-team-webhooks {:team-id team-id}))))))
(defn go-to-team-settings
[]
(ptk/reify ::go-to-team-settings

View file

@ -74,6 +74,9 @@
(def dashboard-team-invitations
(l/derived :dashboard-team-invitations st/state))
(def dashboard-team-webhooks
(l/derived :dashboard-team-webhooks st/state))
(def dashboard-selected-project
(l/derived (fn [state]
(dm/get-in state [:dashboard-local :selected-project]))

View file

@ -72,6 +72,7 @@
:dashboard-font-providers
:dashboard-team-members
:dashboard-team-invitations
:dashboard-team-webhooks
:dashboard-team-settings)
[:*

View file

@ -25,7 +25,7 @@
[app.main.ui.dashboard.projects :refer [projects-section]]
[app.main.ui.dashboard.search :refer [search-page]]
[app.main.ui.dashboard.sidebar :refer [sidebar]]
[app.main.ui.dashboard.team :refer [team-settings-page team-members-page team-invitations-page]]
[app.main.ui.dashboard.team :refer [team-settings-page team-members-page team-invitations-page team-webhooks-page]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
@ -236,6 +236,9 @@
:dashboard-team-invitations
[:& team-invitations-page {:team team}]
:dashboard-team-webhooks
[:& team-webhooks-page {:team team}]
:dashboard-team-settings
[:& team-settings-page {:team team :profile profile}]

View file

@ -245,6 +245,7 @@
[{:keys [team profile] :as props}]
(let [go-members #(st/emit! (dd/go-to-team-members))
go-invitations #(st/emit! (dd/go-to-team-invitations))
go-webhooks #(st/emit! (dd/go-to-team-webhooks))
go-settings #(st/emit! (dd/go-to-team-settings))
members-map (mf/deref refs/dashboard-team-members)
@ -323,6 +324,7 @@
[:ul.dropdown.options-dropdown
[:li {:on-click go-members :data-test "team-members"} (tr "labels.members")]
[:li {:on-click go-invitations :data-test "team-invitations"} (tr "labels.invitations")]
[:li {:on-click go-webhooks :data-test "team-webhooks"} (tr "labels.webhooks")]
[:li {:on-click go-settings :data-test "team-settings"} (tr "labels.settings")]
[:hr]
(when can-rename?

View file

@ -27,6 +27,7 @@
[app.util.i18n :as i18n :refer [tr]]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(mf/defc header
@ -35,13 +36,15 @@
(let [go-members (mf/use-fn #(st/emit! (dd/go-to-team-members)))
go-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings)))
go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations)))
invite-member (mf/use-fn
go-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks)))
invite-member (mf/use-fn
(mf/deps team)
#(st/emit! (modal/show {:type :invite-members :team team :origin :team})))
members-section? (= section :dashboard-team-members)
settings-section? (= section :dashboard-team-settings)
invitations-section? (= section :dashboard-team-invitations)
webhooks-section? (= section :dashboard-team-webhooks)
permissions (:permissions team)]
[:header.dashboard-header.team
@ -50,6 +53,7 @@
members-section? (tr "labels.members")
settings-section? (tr "labels.settings")
invitations-section? (tr "labels.invitations")
webhooks-section? (tr "labels.webhooks")
:else nil)]]
[:nav.dashboard-header-menu
[:ul.dashboard-header-options
@ -57,6 +61,8 @@
[:a {:on-click go-members} (tr "labels.members")]]
[:li {:class (when invitations-section? "active")}
[:a {:on-click go-invitations} (tr "labels.invitations")]]
[:li {:class (when webhooks-section? "active")}
[:a {:on-click go-webhooks} (tr "labels.webhooks")]]
[:li {:class (when settings-section? "active")}
[:a {:on-click go-settings} (tr "labels.settings")]]]]
[:div.dashboard-buttons
@ -571,6 +577,238 @@
[:& invitation-section {:team team
:invitations invitations}]]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; WEBHOOKS SECTION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::uri ::us/not-empty-string)
(s/def ::mtype ::us/not-empty-string)
(s/def ::webhook-form
(s/keys :req-un [::uri ::mtype]))
(mf/defc webhook-modal {::mf/register modal/components
::mf/register-as :webhook}
[{:keys [webhook] :as props}]
(let [initial (mf/use-memo (fn [] (or webhook {:is-active false :mtype "application/json"})))
form (fm/use-form :spec ::webhook-form
:initial initial)
mtypes [{:label "application/json" :value "application/json"}
{:label "application/x-www-form-urlencoded" :value "application/x-www-form-urlencoded"}
{:label "application/transit+json" :value "application/transit+json"}]
on-success
(fn [message]
(st/emit! (dd/fetch-team-webhooks)
(msg/success message)
(modal/hide)))
on-error
(fn [message {:keys [type code hint] :as error}]
(let [message (if (and (= type :validation) (= code :webhook-validation))
(str message " "
(case hint
"ssl-validation" (tr "errors.webhooks.ssl-validation")
"")) ;; TODO Add more error codes when back defines them
message)]
(rx/of (msg/error message))))
on-create-submit
(fn []
(let [mdata {:on-success #(on-success (tr "dashboard.webhooks.create.success"))
:on-error (partial on-error (tr "dashboard.webhooks.create.error"))}
webhook {:uri (get-in @form [:clean-data :uri])
:mtype (get-in @form [:clean-data :mtype])
:is-active (get-in @form [:clean-data :is-active])}]
(st/emit! (dd/create-team-webhook (with-meta webhook mdata)))))
on-update-submit
(fn []
(let [mdata {:on-success #(on-success (tr "dashboard.webhooks.update.success"))
:on-error (partial on-error (tr "dashboard.webhooks.update.error"))}
webhook (get @form :clean-data)]
(st/emit! (dd/update-team-webhook (with-meta webhook mdata)))))
on-submit
#(let [data (:clean-data @form)]
(if (:id data)
(on-update-submit)
(on-create-submit)))]
[:div.modal-overlay
[:div.modal-container.webhooks-modal
[:& fm/form {:form form :on-submit on-submit}
[:div.modal-header
[:div.modal-header-title
(if webhook
[:h2 (tr "modals.edit-webhook.title")]
[:h2 (tr "modals.create-webhook.title")])]
[:div.modal-close-button
{:on-click #(st/emit! (modal/hide))} i/close]]
[:div.modal-content.generic-form
[:div.fields-container
[:div.fields-row
[:& fm/input {:type "text"
:auto-focus? true
:form form
:name :uri
:label (tr "modals.create-webhook.url.label")
:placeholder (tr "modals.create-webhook.url.placeholder")}]]
[:div.fields-row
[:& fm/select {:options mtypes
:label (tr "dashboard.webhooks.content-type")
:default "application/json"
:name :mtype}]]]
[:div.fields-row
[:div.input-checkbox.check-primary
[:& fm/input {:type "checkbox"
:form form
:name :is-active
:label (tr "dashboard.webhooks.active")}]
]
[:div.explain (tr "dashboard.webhooks.active.explain")]]]
[:div.modal-footer
[:div.action-buttons
[:input.btn-gray.btn-large
{:type "button"
:value (tr "labels.cancel")
:on-click #(modal/hide!)}]
[:& fm/submit-button
{:label (if webhook
(tr "modals.edit-webhook.submit-label")
(tr "modals.create-webhook.submit-label"))}]]]]]]))
(mf/defc webhooks-hero
[]
[:div.banner
[:div.title (tr "labels.webhooks")
[:div.description (tr "dashboard.webhooks.description")]]
[:div.create-container
[:div.create (tr "dashboard.webhooks.create")]]]
[:div.webhooks-hero-container
[:div.webhooks-hero
[:div.desc
[:h2 (tr "labels.webhooks")]
[:& i18n/tr-html {:label "dashboard.webhooks.description"}]]
[:div.btn-primary
{:on-click #(st/emit! (modal/show :webhook {}))}
[:span (tr "dashboard.webhooks.create")]]]])
(mf/defc webhook-actions
[{:keys [on-edit on-delete] :as props}]
(let [show? (mf/use-state false)]
[:*
[:span.icon {:on-click #(reset! show? true)} [i/actions]]
[:& dropdown {:show @show?
:on-close #(reset! show? false)}
[:ul.dropdown.actions-dropdown
[:li {:on-click on-edit} (tr "labels.edit")]
[:li {:on-click on-delete} (tr "labels.delete")]]]]))
(mf/defc last-delivery-icon
[{:keys [success? text] :as props}]
[:div.last-delivery-icon
[:div.tooltip
[:div.label text]
[:div.arrow-down]]
(if success?
[:span.icon.success i/msg-success]
[:span.icon.failure i/msg-warning])])
(mf/defc webhook-item
{::mf/wrap [mf/memo]}
[{:keys [webhook] :as props}]
(let [on-edit #(st/emit! (modal/show :webhook {:webhook webhook}))
error-code (:error-code webhook)
extract-status
(fn [error-code]
(let [status (-> error-code
(str/split "-")
last
parse-long)]
(if (nil? status)
""
status)))
delete-fn
(fn []
(let [params {:id (:id webhook)}
mdata {:on-success #(st/emit! (dd/fetch-team-webhooks))}]
(st/emit! (dd/delete-team-webhook (with-meta params mdata)))))
on-delete #(st/emit! (modal/show
{:type :confirm
:title (tr "modals.delete-webhook.title")
:message (tr "modals.delete-webhook.message")
:accept-label (tr "modals.delete-webhook.accept")
:on-accept delete-fn}))
last-delivery-text (cond
(nil? error-code)
(tr "webhooks.last-delivery.success")
(= error-code "ssl-validation")
(str (tr "errors.webhooks.last-delivery") " " (tr "errors.webhooks.ssl-validation"))
(str/starts-with? error-code "unexpected-status")
(str (tr "errors.webhooks.last-delivery")
" "
(tr "errors.webhooks.unexpected-status" (extract-status error-code)))
:else
(tr "errors.webhooks.last-delivery"))]
[:div.table-row
[:div.table-field.last-delivery
[:div.icon-container
[:& last-delivery-icon {:success? (nil? error-code) :text last-delivery-text}]]]
[:div.table-field.uri
[:div (:uri webhook)]]
[:div.table-field.active
[:div (if (:is-active webhook) (tr "labels.active") (tr "labels.inactive"))]]
[:div.table-field.actions
[:& webhook-actions {:on-edit on-edit
:on-delete on-delete}]]]))
(mf/defc webhooks-list
[{:keys [webhooks] :as props}]
[:div.dashboard-table
[:div.table-rows
(for [webhook webhooks]
[:& webhook-item {:webhook webhook :key (:id webhook)}])]])
(mf/defc team-webhooks-page
[{:keys [team] :as props}]
(let [webhooks (mf/deref refs/dashboard-team-webhooks)]
(mf/with-effect [team]
(dom/set-html-title
(tr "title.team-webhooks"
(if (:is-default team)
(tr "dashboard.your-penpot")
(:name team)))))
(mf/with-effect [team]
(st/emit! (dd/fetch-team-webhooks)))
[:*
[:& header {:team team :section :dashboard-team-webhooks}]
[:section.dashboard-container.dashboard-team-webhooks
[:div
[:& webhooks-hero]
(if (empty? webhooks)
[:div.webhooks-empty
[:div (tr "dashboard.webhooks.empty.no-webhooks")]
[:div (tr "dashboard.webhooks.empty.add-one")]]
[:& webhooks-list {:webhooks webhooks}])]]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SETTINGS SECTION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -66,6 +66,7 @@
["/dashboard/team/:team-id"
["/members" :dashboard-team-members]
["/invitations" :dashboard-team-invitations]
["/webhooks" :dashboard-team-webhooks]
["/settings" :dashboard-team-settings]
["/projects" :dashboard-projects]
["/search" :dashboard-search]

View file

@ -669,6 +669,51 @@ msgstr "Your name"
msgid "dashboard.your-penpot"
msgstr "Your Penpot"
msgid "dashboard.webhooks.description"
msgstr "Webhooks are a simple way to allow other websites and apps to be notified when certain events happen at Penpot. Well send a POST request to each of the URLs you provide."
msgid "dashboard.webhooks.create"
msgstr "Create webhook"
msgid "dashboard.webhooks.empty.no-webhooks"
msgstr "No webhooks created so far."
msgid "dashboard.webhooks.empty.add-one"
msgstr "Press the button \"Add webhook\" to add one."
msgid "dashboard.webhooks.content-type"
msgstr "Content type"
msgid "dashboard.webhooks.active"
msgstr "Is active"
msgid "dashboard.webhooks.active.explain"
msgstr "When this hook is triggered event details will be delivered"
msgid "webhooks.last-delivery.success"
msgstr "Last delivery was successfull."
msgid "errors.webhooks.last-delivery"
msgstr "Last delivery was not successfull."
msgid "errors.webhooks.ssl-validation"
msgstr "Error on SSL validation."
msgid "errors.webhooks.unexpected-status"
msgstr "Unexpected status %s"
msgid "dashboard.webhooks.update.error"
msgstr "Error on updating webhook."
msgid "dashboard.webhooks.update.success"
msgstr "Webhook updated successfully."
msgid "dashboard.webhooks.create.error"
msgstr "Error on creating webhook."
msgid "dashboard.webhooks.create.success"
msgstr "Webhook created successfully."
#: src/app/main/ui/alert.cljs
msgid "ds.alert-ok"
msgstr "Ok"
@ -1468,6 +1513,15 @@ msgstr "(you)"
msgid "labels.your-account"
msgstr "Your account"
msgid "labels.webhooks"
msgstr "Webhooks"
msgid "labels.active"
msgstr "Active"
msgid "labels.inactive"
msgstr "Inactive"
#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs
msgid "media.loading"
msgstr "Loading image…"
@ -1682,6 +1736,33 @@ msgstr "Are you sure you want to delete this member from the team?"
msgid "modals.delete-team-member-confirm.title"
msgstr "Delete team member"
msgid "modals.delete-webhook.title"
msgstr "Deleting webhook"
msgid "modals.delete-webhook.message"
msgstr "Are you sure you want to delete this webhook?"
msgid "modals.delete-webhook.accept"
msgstr "Delete webhook"
msgid "modals.create-webhook.title"
msgstr "Create webhook"
msgid "modals.create-webhook.submit-label"
msgstr "Create webhook"
msgid "modals.create-webhook.url.label"
msgstr "Payload URL"
msgid "modals.create-webhook.url.placeholder"
msgstr "https://example.com/postreceive"
msgid "modals.edit-webhook.title"
msgstr "Edit webhook"
msgid "modals.edit-webhook.submit-label"
msgstr "Edit webhook"
#: src/app/main/ui/dashboard/team.cljs
msgid "modals.invite-member-confirm.accept"
msgstr "Send invitation"
@ -2489,6 +2570,9 @@ msgstr "Profile - Penpot"
msgid "title.team-invitations"
msgstr "Invitations - %s - Penpot"
msgid "title.team-webhooks"
msgstr "Webhooks - %s - Penpot"
#: src/app/main/ui/dashboard/team.cljs
msgid "title.team-members"
msgstr "Members - %s - Penpot"

View file

@ -713,6 +713,51 @@ msgstr "Tu nombre"
msgid "dashboard.your-penpot"
msgstr "Tu Penpot"
msgid "dashboard.webhooks.description"
msgstr "Los webhooks son una forma simple de permitir notificar a otros sitios web y aplicaciones cuando ocurren ciertos eventos en Penpot. Enviaremos una petición POST a cada una de las URLs que indiques."
msgid "dashboard.webhooks.create"
msgstr "Crear webhook"
msgid "dashboard.webhooks.empty.no-webhooks"
msgstr "No hay ningún webhook aún."
msgid "dashboard.webhooks.empty.add-one"
msgstr "Pulsa el botón \"Crear webhook\" para añadir uno."
msgid "dashboard.webhooks.content-type"
msgstr "Tipo de contenido"
msgid "dashboard.webhooks.active"
msgstr "Activo"
msgid "dashboard.webhooks.active.explain"
msgstr "Cuando se active este webhook se enviarán detalles del evento"
msgid "webhooks.last-delivery.success"
msgstr "El último envío fue correcto."
msgid "errors.webhooks.last-delivery"
msgstr "Hubo un problema en el último envío."
msgid "errors.webhooks.ssl-validation"
msgstr "Error en la validación SSL."
msgid "errors.webhooks.unexpected-status"
msgstr "Estado inesperado %s"
msgid "dashboard.webhooks.update.error"
msgstr "Error modificando el webhook"
msgid "dashboard.webhooks.update.success"
msgstr "Webhook modificado con éxito"
msgid "dashboard.webhooks.create.error"
msgstr "Error creando con éxito"
msgid "dashboard.webhooks.create.success"
msgstr "Webhook creado con éxito"
#: src/app/main/ui/alert.cljs
msgid "ds.alert-ok"
msgstr "Ok"
@ -1653,6 +1698,15 @@ msgstr "(tú)"
msgid "labels.your-account"
msgstr "Tu cuenta"
msgid "labels.webhooks"
msgstr "Webhooks"
msgid "labels.active"
msgstr "Activo"
msgid "labels.inactive"
msgstr "Inactivo"
#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs
msgid "media.loading"
msgstr "Cargando imagen…"
@ -1880,6 +1934,33 @@ msgstr "¿Seguro que quieres eliminar este integrante del equipo?"
msgid "modals.delete-team-member-confirm.title"
msgstr "Eliminar integrante del equipo"
msgid "modals.delete-webhook.title"
msgstr "Borrando webhook"
msgid "modals.delete-webhook.message"
msgstr "¿Seguro que quieres borrar este webhook?"
msgid "modals.delete-webhook.accept"
msgstr "Borrar webhook"
msgid "modals.create-webhook.title"
msgstr "Crear webhook"
msgid "modals.create-webhook.submit-label"
msgstr "Crear webhook"
msgid "modals.create-webhook.url.label"
msgstr "Payload URL"
msgid "modals.create-webhook.url.placeholder"
msgstr "https://example.com/postreceive"
msgid "modals.edit-webhook.title"
msgstr "Modificar webhook"
msgid "modals.edit-webhook.submit-label"
msgstr "Modificar webhook"
#: src/app/main/ui/dashboard/team.cljs
msgid "modals.invite-member-confirm.accept"
msgstr "Enviar invitacion"
@ -2818,6 +2899,9 @@ msgstr "Perfil - Penpot"
msgid "title.team-invitations"
msgstr "Invitaciones - %s - Penpot"
msgid "title.team-webhooks"
msgstr "Webhooks - %s - Penpot"
#: src/app/main/ui/dashboard/team.cljs
msgid "title.team-members"
msgstr "Integrantes - %s - Penpot"