Compare commits

..

10 Commits

Author SHA1 Message Date
0d56f6adf1 feat(calcom): route Cal.com egress through host OutboundHTTP (ADR 0023)
Under wasm the guest has no network, so every Cal.com v2 call was dying at
"dial tcp: lookup api.cal.com". Thread deps.OutboundHTTP (http.RoundTripper)
from NewCalcomRouter onto CalcomHandler.rt and into NewCalcomClient, building
the client with Transport: rt. A nil rt (native/DESCRIBE/test) falls back to
the default transport, so bundled/httptest paths are unchanged.

Declare allowed_hosts = ["api.cal.com"] (deny-by-default; admin-granted at
install per the Phase 3 consent gate) and bump pluginsdk v0.2.4 -> v0.2.5 for
CoreServices.OutboundHTTP. Bump 2.0.5 -> 2.0.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 08:57:10 +08:00
f597fa8b52 chore(web): rebuild dist bundle for v2.0.5 captchaEnabled editor
The v2.0.5 captchaEnabled editor toggle (fe5d117) changed web/src but never
rebuilt the committed web/dist. Rebuild so the shipped bundle matches source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 08:57:02 +08:00
fe5d11754a feat(editor): expose the captchaEnabled toggle — v2.0.5
The schema declares captchaEnabled (x-editor checkbox) but the custom
MF editor replaces schema-driven rendering and never drew a control for
it, so the captcha gate could not be enabled through the visual editor
at all. Add the switch beside Show Timezone. pnpm build-script approval
for esbuild (package.json onlyBuiltDependencies) so the editor bundle
rebuilds non-interactively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 00:15:30 +08:00
3967fa955a fix: rebuild admin claims from trusted headers — v2.0.4
Context does not cross the wasm ABI, so requireAdmin's
auth.GetUserFromContext only sees claims if the guest rebuilds them from
the host-stamped X-Bn-Verified-* headers. 2.0.3 never mounted
auth.TrustedHeaderMiddleware, so every admin endpoint (settings save,
event-types, test, rotate) returned 401 for real admins — the settings
panel could not store the Cal.com API key. Mount the middleware on the
router and pin the contract with a header-driven test (viewer headers
must reach requireAdmin and 403, never 401): the existing admin tests
injected claims straight into context, which is exactly the path wasm
does not have.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 00:03:28 +08:00
7c7bceb9d7 fix!: absolute routes + live captcha requirement — v2.0.3
Two wasm-era regressions fixed:

Routes: the wasm host forwards the FULL request path with no prefix
strip, but every route was registered relative — the plugin's entire
HTTP surface (slots/book/cancel/date-grid/admin) 404'd at runtime on
every instance since 2.0.0. All routes now register under
/api/plugins/calcomblock (testplugin's httpBase convention), pinned by
a route-matching test.

Captcha requirement: blockConfig was hardcoded nil (no capability
exposed published block content), so bookingRequiresCaptcha always
reported false — the widget never rendered and the gate never ran.
publishedBlockResolver now answers it via pluginsdk v0.2.4's
content.published_block_configs: any currently-published calcom:booking
block matching the posted username+eventTypeSlug with
captchaEnabled=true demands the host-stamped X-Bn-Verified-Captcha
header; an empty config username applies to any posted username.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 23:47:59 +08:00
c1f8fe8a2c chore: v2.0.2 — host-verified captcha via X-Bn-Verified-Captcha
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 23:18:09 +08:00
7e9265c6a7 feat!: captcha via host-stamped X-Bn-Verified-Captcha; drop block/core entirely
SetCaptchaServer was .so-era injection — nothing wasm-side ever called
it, so the verifier was permanently nil and captcha-enabled booking
blocks failed closed on every submission. A guest can't hold the host's
stateful captcha server; the host now verifies+consumes the cap-token
before dispatch and stamps the unforgeable X-Bn-Verified-Captcha
trusted header (pluginsdk v0.2.2 auth.CaptchaVerified). Fail-closed
semantics preserved: no stamp = reject.

Deletes the last block/core import (captcha) and the test-side PoW
solver; go.mod no longer requires core.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 23:00:10 +08:00
28b50128d9 chore: bump pluginsdk to v0.2.1
Picks up the render package move-in (BlockNote renderer now reads
embed-resolver/human-proof context keys from pluginsdk/blocks) and
keeps the check-safety 2c fleet version anchor satisfied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:34:04 +08:00
d5b158e0bf chore: pluginsdk v0.2.0 (P3 host-composed chrome)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:56:28 +08:00
324a2e6fe0 chore: migrate from block/core to block/pluginsdk (keep core for captcha)
Rewrite imports for plugin, blocks, templates, auth, settings, crypto, and
rbac packages from git.dev.alexdunmow.com/block/core/* to
git.dev.alexdunmow.com/block/pluginsdk/* across all Go files. The captcha
package stays on block/core, so the repo keeps both requires.

go.mod: add pluginsdk v0.1.0; bump core v0.20.2 -> v0.20.3 (satisfies the
check-safety core-version gate); transitive churn pgx v5.9.2 -> v5.10.0 and
x/mod v0.34.0 -> v0.37.0 from go mod tidy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:03:57 +08:00
40 changed files with 132008 additions and 143009 deletions

View File

@ -5,8 +5,8 @@ import (
"context" "context"
"time" "time"
"git.dev.alexdunmow.com/block/core/blocks" "git.dev.alexdunmow.com/block/pluginsdk/blocks"
"git.dev.alexdunmow.com/block/core/settings" "git.dev.alexdunmow.com/block/pluginsdk/settings"
) )
// renderSettings is the SettingsManager available to CalcomBookingFunc, which // renderSettings is the SettingsManager available to CalcomBookingFunc, which

64
block_resolver.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"context"
"strings"
)
// httpBase is the prefix the host mounts the plugin HTTP handler at; the wasm
// host forwards full paths, so every route registers under it.
const httpBase = "/api/plugins/calcomblock"
// publishedConfigsSource is the slice of content.Content the resolver needs
// (satisfied by deps.Content).
type publishedConfigsSource interface {
PublishedBlockConfigs(ctx context.Context, blockKey string) ([]map[string]any, error)
}
// publishedBlockResolver answers CalcomBookingRequiresCaptcha from published
// block content served by the host (content.published_block_configs) — the
// server-authoritative source, never the POST body/blockId. A config with an
// empty username applies to any posted username (it falls back to the
// plugin's default account), erring toward requiring captcha.
type publishedBlockResolver struct {
source publishedConfigsSource
}
func (r publishedBlockResolver) CalcomBookingRequiresCaptcha(ctx context.Context, username, eventTypeSlug string) (bool, error) {
configs, err := r.source.PublishedBlockConfigs(ctx, "calcom:booking")
if err != nil {
return false, err
}
for _, cfg := range configs {
if !configBool(cfg["captchaEnabled"]) {
continue
}
if !configFieldMatches(cfg["eventTypeSlug"], eventTypeSlug, false) {
continue
}
if configFieldMatches(cfg["username"], username, true) {
return true, nil
}
}
return false, nil
}
func configBool(v any) bool {
switch b := v.(type) {
case bool:
return b
case string:
return strings.EqualFold(strings.TrimSpace(b), "true")
default:
return false
}
}
func configFieldMatches(cfgValue any, posted string, emptyMatchesAny bool) bool {
s, _ := cfgValue.(string)
s = strings.TrimSpace(s)
if s == "" {
return emptyMatchesAny
}
return strings.EqualFold(s, strings.TrimSpace(posted))
}

69
block_resolver_test.go Normal file
View File

@ -0,0 +1,69 @@
package main
import (
"context"
"errors"
"testing"
)
type fakeConfigsSource struct {
configs []map[string]any
err error
gotKey string
}
func (f *fakeConfigsSource) PublishedBlockConfigs(_ context.Context, blockKey string) ([]map[string]any, error) {
f.gotKey = blockKey
return f.configs, f.err
}
func TestPublishedBlockResolverRequiresCaptchaOnMatch(t *testing.T) {
src := &fakeConfigsSource{configs: []map[string]any{
{"username": "other", "eventTypeSlug": "30min", "captchaEnabled": true},
{"username": "Alice", "eventTypeSlug": "30min", "captchaEnabled": true},
}}
r := publishedBlockResolver{source: src}
got, err := r.CalcomBookingRequiresCaptcha(context.Background(), "alice", "30min")
if err != nil || !got {
t.Fatalf("RequiresCaptcha = %v, %v; want true (case-insensitive username match)", got, err)
}
if src.gotKey != "calcom:booking" {
t.Fatalf("queried block key %q, want calcom:booking", src.gotKey)
}
}
func TestPublishedBlockResolverNoRequirementWhenDisabledOrUnmatched(t *testing.T) {
src := &fakeConfigsSource{configs: []map[string]any{
{"username": "alice", "eventTypeSlug": "30min", "captchaEnabled": false},
{"username": "alice", "eventTypeSlug": "60min", "captchaEnabled": true},
}}
r := publishedBlockResolver{source: src}
if got, _ := r.CalcomBookingRequiresCaptcha(context.Background(), "alice", "30min"); got {
t.Fatal("RequiresCaptcha = true; want false (disabled for 30min, 60min doesn't match)")
}
}
func TestPublishedBlockResolverEmptyConfigUsernameMatchesAny(t *testing.T) {
// A block may omit username (falls back to the plugin's default account);
// the requirement then applies to the event type for any posted username —
// erring toward requiring captcha, never stripping it.
src := &fakeConfigsSource{configs: []map[string]any{
{"eventTypeSlug": "30min", "captchaEnabled": true},
}}
r := publishedBlockResolver{source: src}
if got, _ := r.CalcomBookingRequiresCaptcha(context.Background(), "whoever", "30min"); !got {
t.Fatal("RequiresCaptcha = false; want true (config without username applies to any)")
}
}
func TestPublishedBlockResolverPropagatesError(t *testing.T) {
src := &fakeConfigsSource{err: errors.New("hostcall failed")}
r := publishedBlockResolver{source: src}
if _, err := r.CalcomBookingRequiresCaptcha(context.Background(), "alice", "30min"); err == nil {
t.Fatal("expected error to propagate (caller decides fail-open policy)")
}
}

View File

@ -38,10 +38,14 @@ type CalcomClient struct {
baseURL string baseURL string
} }
// NewCalcomClient builds a client with the standard 30s HTTP timeout. // NewCalcomClient builds a client with the standard 30s HTTP timeout. rt is the
func NewCalcomClient(apiKey string) *CalcomClient { // host-mediated egress transport (deps.OutboundHTTP, ADR 0023) — under wasm the
// guest has no network, so every Cal.com request must ride it. A nil rt falls
// back to http.DefaultTransport, keeping native/DESCRIBE and httptest paths
// working (tests reach their fake server by overriding calcomBaseURL).
func NewCalcomClient(apiKey string, rt http.RoundTripper) *CalcomClient {
return &CalcomClient{ return &CalcomClient{
http: &http.Client{Timeout: 30 * time.Second}, http: &http.Client{Transport: rt, Timeout: 30 * time.Second},
apiKey: apiKey, apiKey: apiKey,
baseURL: calcomBaseURL, baseURL: calcomBaseURL,
} }

9
go.mod
View File

@ -3,11 +3,11 @@ module git.dev.alexdunmow.com/block/calcomblock
go 1.26.4 go 1.26.4
require ( require (
git.dev.alexdunmow.com/block/core v0.20.2 git.dev.alexdunmow.com/block/pluginsdk v0.2.5
github.com/a-h/templ v0.3.1020 github.com/a-h/templ v0.3.1020
github.com/go-chi/chi/v5 v5.3.0 github.com/go-chi/chi/v5 v5.3.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.9.2 github.com/jackc/pgx/v5 v5.10.0
github.com/nyaruka/phonenumbers v1.8.0 github.com/nyaruka/phonenumbers v1.8.0
) )
@ -16,7 +16,8 @@ require (
github.com/BurntSushi/toml v1.6.0 // indirect github.com/BurntSushi/toml v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
golang.org/x/mod v0.34.0 // indirect golang.org/x/mod v0.37.0 // indirect
golang.org/x/text v0.36.0 // indirect golang.org/x/net v0.56.0 // indirect
golang.org/x/text v0.38.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
) )

26
go.sum
View File

@ -1,7 +1,7 @@
connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
git.dev.alexdunmow.com/block/core v0.20.2 h1:OVh5J7sYsjMDq6UBUpm/6FoUcfeuTr+YUPp0rQiYDuo= git.dev.alexdunmow.com/block/pluginsdk v0.2.5 h1:01d+5stAywSENScafnNKyMc2ZM5GGFt+hCBKuKTFJW4=
git.dev.alexdunmow.com/block/core v0.20.2/go.mod h1:n/0y+8g/zabjwRNseNjme3w5J8thKqdjkIE9YJCzGow= git.dev.alexdunmow.com/block/pluginsdk v0.2.5/go.mod h1:Z+eG+WZxAP0jfreLqlGcc0kkWKt8RWevzWyWn8d+dhM=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw= github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw=
@ -19,8 +19,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/nyaruka/phonenumbers v1.8.0 h1:TrXNJmbwcAHajzDqin3mLWw57vqLUA6ZjVdeNds0heQ= github.com/nyaruka/phonenumbers v1.8.0 h1:TrXNJmbwcAHajzDqin3mLWw57vqLUA6ZjVdeNds0heQ=
@ -34,14 +34,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU= github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0= github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -15,30 +15,14 @@ import (
"sync" "sync"
"time" "time"
"git.dev.alexdunmow.com/block/core/auth" "git.dev.alexdunmow.com/block/pluginsdk/auth"
"git.dev.alexdunmow.com/block/core/captcha" "git.dev.alexdunmow.com/block/pluginsdk/plugin"
"git.dev.alexdunmow.com/block/core/plugin" "git.dev.alexdunmow.com/block/pluginsdk/rbac"
"git.dev.alexdunmow.com/block/core/rbac"
"git.dev.alexdunmow.com/block/calcomblock/internal/helpers" "git.dev.alexdunmow.com/block/calcomblock/internal/helpers"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/nyaruka/phonenumbers" "github.com/nyaruka/phonenumbers"
) )
// captchaVerifier is the shared Cap (trycap.dev) proof-of-work verifier, built
// once from the per-instance secret and injected at server startup via
// SetCaptchaServer — mirroring SetNotifyFn. It is nil when the instance captcha
// secret failed to load: an enabled booking block then FAILS CLOSED (rejects
// rather than allowing an unverified booking through). Read directly by
// HandleCreateBooking; the mailing-list / block-form handlers hold the same
// server on their struct, but the plugin HTTP mount is generic so a package
// seam is the established injection point here.
var captchaVerifier *captcha.Server
// SetCaptchaServer wires the CMS's shared captcha server into the Cal.com
// booking handler. Called once at server startup from server.go. Safe to pass
// nil (captcha then unavailable → enabled blocks fail closed).
func SetCaptchaServer(s *captcha.Server) { captchaVerifier = s }
// blockContentResolver answers the server-authoritative question "does a // blockContentResolver answers the server-authoritative question "does a
// booking for this Cal.com username + event-type slug require a captcha token?" // booking for this Cal.com username + event-type slug require a captcha token?"
// by scanning currently-published block content — NEVER a client-supplied // by scanning currently-published block content — NEVER a client-supplied
@ -267,6 +251,10 @@ type CalcomHandler struct {
// trusting the POST body. nil in editor-preview / direct-handler unit tests, // trusting the POST body. nil in editor-preview / direct-handler unit tests,
// where blockCaptchaEnabled reports false. // where blockCaptchaEnabled reports false.
blockConfig blockContentResolver blockConfig blockContentResolver
// rt is the host-mediated egress transport (deps.OutboundHTTP, ADR 0023)
// every Cal.com client is built with. nil in native/DESCRIBE/test builds,
// where NewCalcomClient falls back to the default transport.
rt http.RoundTripper
} }
// NewCalcomHandler creates a new Cal.com handler. appURL is the externally // NewCalcomHandler creates a new Cal.com handler. appURL is the externally
@ -281,6 +269,11 @@ func NewCalcomHandler(settings *SettingsManager, limiter *RateLimiter, appURL st
// and encrypt the Cal.com API key + webhook secret. // and encrypt the Cal.com API key + webhook secret.
func NewCalcomRouter(deps plugin.CoreServices) http.Handler { func NewCalcomRouter(deps plugin.CoreServices) http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
// Context does not cross the wasm ABI: requireAdmin's claims exist only if
// the guest rebuilds them from the host-stamped X-Bn-Verified-* headers.
// Without this middleware every admin endpoint 401s for real admins (the
// settings panel could not save the API key — 2.0.3 regression).
r.Use(auth.TrustedHeaderMiddleware)
// Settings persistence goes through the SDK settings capabilities: under the // Settings persistence goes through the SDK settings capabilities: under the
// wasm sandbox the plugin's Postgres role cannot touch the CMS `settings` // wasm sandbox the plugin's Postgres role cannot touch the CMS `settings`
// table (public schema) directly, so the host performs the DB work. // table (public schema) directly, so the host performs the DB work.
@ -293,38 +286,48 @@ func NewCalcomRouter(deps plugin.CoreServices) http.Handler {
limiter := NewRateLimiter(bookingsPerHourPerIP) limiter := NewRateLimiter(bookingsPerHourPerIP)
helpers.StartCleanupLoop(context.Background(), 15*time.Minute, limiter.SweepStale) helpers.StartCleanupLoop(context.Background(), 15*time.Minute, limiter.SweepStale)
h := NewCalcomHandler(settings, limiter, deps.AppURL) h := NewCalcomHandler(settings, limiter, deps.AppURL)
// blockConfig scans published block content for the server-authoritative // Route every Cal.com call through the host-mediated egress transport. nil
// captcha requirement (CalcomBookingRequiresCaptcha over page_block_snapshots // under native/DESCRIBE builds (deps has no host); the client then uses the
// in the public schema). No wasm-ABI capability exposes that today, and the // default transport, which only the non-wasm paths can reach.
// sandboxed plugin role cannot read it via SQL, so it is left nil: h.rt = deps.OutboundHTTP
// bookingRequiresCaptcha then reports false and the baseline honeypot + // blockConfig resolves the server-authoritative captcha requirement from
// per-IP rate limit still apply. Restoring server-authoritative captcha // published block content via the content.published_block_configs
// enforcement needs a future core capability that exposes published-block // capability (pluginsdk >= v0.2.4) — the sandboxed plugin role cannot read
// content across the wasm ABI (tracked as a follow-up). // page_block_snapshots itself. deps.Content is nil in native/DESCRIBE
h.blockConfig = nil // builds; blockConfig then stays nil and bookingRequiresCaptcha reports
// false (honeypot + per-IP rate limit remain the baseline).
if deps.Content != nil {
h.blockConfig = publishedBlockResolver{source: deps.Content}
}
// Public booking endpoints — reachable by anonymous visitors. // Routes are registered ABSOLUTE: the wasm host forwards the full request
r.Get("/slots", h.HandleGetSlots) // path (no prefix strip), so relative registrations never match — the
r.Get("/form", h.HandleGetForm) // 2.0.02.0.2 regression that 404'd the plugin's entire HTTP surface.
r.Post("/book", h.HandleCreateBooking) // Mirrors testplugin's httpBase convention.
r.Post("/cancel", h.HandleCancelBooking) r.Route(httpBase, func(r chi.Router) {
r.Get("/reset", h.HandleReset) // Public booking endpoints — reachable by anonymous visitors.
r.Get("/date-grid", h.HandleGetDateGrid) r.Get("/slots", h.HandleGetSlots)
r.Get("/form", h.HandleGetForm)
r.Post("/book", h.HandleCreateBooking)
r.Post("/cancel", h.HandleCancelBooking)
r.Get("/reset", h.HandleReset)
r.Get("/date-grid", h.HandleGetDateGrid)
// Webhook receiver — HMAC-verified inside the handler, so no admin gate. // Webhook receiver — HMAC-verified inside the handler, so no admin gate.
wh := NewWebhookHandler(settings, slog.Default()) wh := NewWebhookHandler(settings, slog.Default())
r.Post("/webhook", wh.Handle) r.Post("/webhook", wh.Handle)
// Admin endpoints — gated by requireAdmin. The plugin HTTP mount has no // Admin endpoints — gated by requireAdmin. The plugin HTTP mount has no
// outer admin middleware, so this is the only line keeping the API key // outer admin middleware, so this is the only line keeping the API key
// + webhook secret + Cal.com proxy out of attackers' hands. // + webhook secret + Cal.com proxy out of attackers' hands.
r.Group(func(admin chi.Router) { r.Group(func(admin chi.Router) {
admin.Use(requireAdmin) admin.Use(requireAdmin)
admin.Get("/settings", h.HandleGetSettings) admin.Get("/settings", h.HandleGetSettings)
admin.Post("/settings", h.HandleSaveSettings) admin.Post("/settings", h.HandleSaveSettings)
admin.Post("/settings/rotate-webhook-secret", h.HandleRotateWebhookSecret) admin.Post("/settings/rotate-webhook-secret", h.HandleRotateWebhookSecret)
admin.Post("/test", h.HandleTestConnection) admin.Post("/test", h.HandleTestConnection)
admin.Get("/event-types", h.HandleListEventTypes) admin.Get("/event-types", h.HandleListEventTypes)
})
}) })
return r return r
@ -380,8 +383,8 @@ func resetEventTZCache() {
// type's pinned availability schedule when one is set, else the account's // type's pinned availability schedule when one is set, else the account's
// default schedule. Returns "" when any hop fails — callers then fall down // default schedule. Returns "" when any hop fails — callers then fall down
// the site-setting ladder rather than guessing. // the site-setting ladder rather than guessing.
func fetchEventTimezone(ctx context.Context, apiKey, username, eventType string) string { func fetchEventTimezone(ctx context.Context, rt http.RoundTripper, apiKey, username, eventType string) string {
client := NewCalcomClient(apiKey) client := NewCalcomClient(apiKey, rt)
schedules, err := client.ListSchedules(ctx) schedules, err := client.ListSchedules(ctx)
if err != nil || len(schedules) == 0 { if err != nil || len(schedules) == 0 {
log.Printf("calcom: list schedules for %s/%s: %v", username, eventType, err) log.Printf("calcom: list schedules for %s/%s: %v", username, eventType, err)
@ -416,7 +419,7 @@ func fetchEventTimezone(ctx context.Context, apiKey, username, eventType string)
func (h *CalcomHandler) eventLocation(ctx context.Context, apiKey, username, eventType string) (*time.Location, string) { func (h *CalcomHandler) eventLocation(ctx context.Context, apiKey, username, eventType string) (*time.Location, string) {
tz := peekEventTZ(username, eventType) tz := peekEventTZ(username, eventType)
if tz == "" && apiKey != "" && username != "" && eventType != "" { if tz == "" && apiKey != "" && username != "" && eventType != "" {
if tz = fetchEventTimezone(ctx, apiKey, username, eventType); tz != "" { if tz = fetchEventTimezone(ctx, h.rt, apiKey, username, eventType); tz != "" {
storeEventTZ(username, eventType, tz) storeEventTZ(username, eventType, tz)
} }
} }
@ -463,7 +466,7 @@ func (h *CalcomHandler) HandleGetSlots(w http.ResponseWriter, r *http.Request) {
} }
dayEnd := dayStart.AddDate(0, 0, 1) dayEnd := dayStart.AddDate(0, 0, 1)
client := NewCalcomClient(apiKey) client := NewCalcomClient(apiKey, h.rt)
slotsResp, err := client.GetSlots( slotsResp, err := client.GetSlots(
r.Context(), r.Context(),
username, username,
@ -559,7 +562,7 @@ func (h *CalcomHandler) HandleGetForm(w http.ResponseWriter, r *http.Request) {
var bookingFields []BookingField var bookingFields []BookingField
if apiKeyErr == nil && apiKey != "" && username != "" && eventType != "" { if apiKeyErr == nil && apiKey != "" && username != "" && eventType != "" {
client := NewCalcomClient(apiKey) client := NewCalcomClient(apiKey, h.rt)
et, etErr := client.GetEventType(r.Context(), username, eventType) et, etErr := client.GetEventType(r.Context(), username, eventType)
if etErr != nil { if etErr != nil {
// Fall back to default fields so a transient Cal.com error doesn't // Fall back to default fields so a transient Cal.com error doesn't
@ -697,13 +700,16 @@ func (h *CalcomHandler) HandleCreateBooking(w http.ResponseWriter, r *http.Reque
// authoritative source is whether any currently-published calcom:booking // authoritative source is whether any currently-published calcom:booking
// block for this username+eventType has captchaEnabled=true — resolved from // block for this username+eventType has captchaEnabled=true — resolved from
// published content, never the POST body/blockId — so a bot cannot strip the // published content, never the POST body/blockId — so a bot cannot strip the
// requirement by omitting or forging blockId. When captcha IS required we // requirement by omitting or forging blockId. Verification is HOST-side: a
// fail CLOSED: a nil shared captcha server (instance secret failed at // guest cannot hold the host's stateful captcha server, so the host consumes
// startup) rejects rather than allows. On failure we render the same error // the cap-token and stamps the unforgeable X-Bn-Verified-Captcha trusted
// partial as other validation failures (not a 500), so the visitor can // header (pluginsdk/auth). When captcha IS required we fail CLOSED: no
// re-solve and retry. // stamp (invalid token, or the instance captcha server failed at startup)
// rejects rather than allows. On failure we render the same error partial
// as other validation failures (not a 500), so the visitor can re-solve
// and retry.
if h.bookingRequiresCaptcha(r.Context(), username, eventType) { if h.bookingRequiresCaptcha(r.Context(), username, eventType) {
if captchaVerifier == nil || !captchaVerifier.VerifyRequest(r) { if !auth.CaptchaVerified(r.Header) {
h.renderError(w, "Captcha check failed, please try again.", blockID, false) h.renderError(w, "Captcha check failed, please try again.", blockID, false)
return return
} }
@ -721,7 +727,7 @@ func (h *CalcomHandler) HandleCreateBooking(w http.ResponseWriter, r *http.Reque
} }
payload := routeBookingForm(r.Form, region) payload := routeBookingForm(r.Form, region)
client := NewCalcomClient(apiKey) client := NewCalcomClient(apiKey, h.rt)
br := BookingRequest{ br := BookingRequest{
EventTypeSlug: eventType, EventTypeSlug: eventType,
Username: username, Username: username,
@ -834,7 +840,7 @@ func (h *CalcomHandler) HandleCancelBooking(w http.ResponseWriter, r *http.Reque
return return
} }
client := NewCalcomClient(apiKey) client := NewCalcomClient(apiKey, h.rt)
if err := client.CancelBooking(r.Context(), uid, "Cancelled by attendee"); err != nil { if err := client.CancelBooking(r.Context(), uid, "Cancelled by attendee"); err != nil {
// Full technical cause stays server-side; the visitor sees curated copy. // Full technical cause stays server-side; the visitor sees curated copy.
log.Printf("calcom: cancel booking %q: %v", uid, err) log.Printf("calcom: cancel booking %q: %v", uid, err)
@ -950,7 +956,7 @@ func (h *CalcomHandler) HandleGetSettings(w http.ResponseWriter, r *http.Request
// have no cached username. Fire /me once on the first settings load and // have no cached username. Fire /me once on the first settings load and
// persist the result so subsequent loads are cache-hits. // persist the result so subsequent loads are cache-hits.
if username == "" && apiKey != "" { if username == "" && apiKey != "" {
client := NewCalcomClient(apiKey) client := NewCalcomClient(apiKey, h.rt)
if me, err := client.GetMe(ctx); err != nil { if me, err := client.GetMe(ctx); err != nil {
log.Printf("calcom: backfill /me in HandleGetSettings: %v", err) log.Printf("calcom: backfill /me in HandleGetSettings: %v", err)
} else { } else {
@ -1012,7 +1018,7 @@ func (h *CalcomHandler) HandleSaveSettings(w http.ResponseWriter, r *http.Reques
// ask the admin to type their own username. Best-effort: if /me is // ask the admin to type their own username. Best-effort: if /me is
// down or rate-limited, the save still succeeds and the admin can // down or rate-limited, the save still succeeds and the admin can
// refresh later via Test Connection. // refresh later via Test Connection.
client := NewCalcomClient(req.APIKey) client := NewCalcomClient(req.APIKey, h.rt)
if me, err := client.GetMe(ctx); err != nil { if me, err := client.GetMe(ctx); err != nil {
log.Printf("calcom: /me lookup after save: %v", err) log.Printf("calcom: /me lookup after save: %v", err)
} else if err := h.settings.SetUsername(ctx, me.Username); err != nil { } else if err := h.settings.SetUsername(ctx, me.Username); err != nil {
@ -1076,7 +1082,7 @@ func (h *CalcomHandler) HandleListEventTypes(w http.ResponseWriter, r *http.Requ
return return
} }
username := r.URL.Query().Get("username") username := r.URL.Query().Get("username")
client := NewCalcomClient(apiKey) client := NewCalcomClient(apiKey, h.rt)
ets, err := client.ListEventTypes(r.Context(), username) ets, err := client.ListEventTypes(r.Context(), username)
if err != nil { if err != nil {
log.Printf("calcom: list event types: %v", err) log.Printf("calcom: list event types: %v", err)
@ -1099,7 +1105,7 @@ func (h *CalcomHandler) HandleTestConnection(w http.ResponseWriter, r *http.Requ
return return
} }
client := NewCalcomClient(apiKey) client := NewCalcomClient(apiKey, h.rt)
ets, err := client.ListEventTypes(r.Context(), "") ets, err := client.ListEventTypes(r.Context(), "")
if err != nil { if err != nil {
log.Printf("calcom: test connection: %v", err) log.Printf("calcom: test connection: %v", err)

View File

@ -2,22 +2,18 @@ package main
import ( import (
"context" "context"
"crypto/sha256"
"encoding/hex"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"strconv"
"strings" "strings"
"sync/atomic" "sync/atomic"
"testing" "testing"
"github.com/google/uuid" "github.com/google/uuid"
"git.dev.alexdunmow.com/block/core/captcha" "git.dev.alexdunmow.com/block/pluginsdk/auth"
) )
// fakeBlockResolver is an in-memory blockContentResolver so the booking handler // fakeBlockResolver is an in-memory blockContentResolver so the booking handler
@ -46,7 +42,7 @@ func (f fakeBlockResolver) CalcomBookingRequiresCaptcha(_ context.Context, usern
// block for username "alice" / eventType "30min" carries the given // block for username "alice" / eventType "30min" carries the given
// captchaEnabled flag, plus a persisted API key so the booking flow reaches the // captchaEnabled flag, plus a persisted API key so the booking flow reaches the
// Cal.com call. The requirement is keyed on username+eventType, NOT a blockId — // Cal.com call. The requirement is keyed on username+eventType, NOT a blockId —
// so the booking form may post any blockId (or none) without affecting it. // see fakeBlockResolver.
func newCalcomCaptchaHandler(t *testing.T, captchaEnabled bool) *CalcomHandler { func newCalcomCaptchaHandler(t *testing.T, captchaEnabled bool) *CalcomHandler {
t.Helper() t.Helper()
h, _ := newTestHandler() h, _ := newTestHandler()
@ -59,12 +55,6 @@ func newCalcomCaptchaHandler(t *testing.T, captchaEnabled bool) *CalcomHandler {
return h return h
} }
// newCalcomTestCaptchaServer builds a captcha.Server with a low PoW difficulty
// so the challenge solves quickly in tests.
func newCalcomTestCaptchaServer() *captcha.Server {
return captcha.New([]byte("calcom-captcha-test-secret"), captcha.WithChallenge(4, 8, 2))
}
// captchaBookingForm builds a booking POST body that passes every cheap // captchaBookingForm builds a booking POST body that passes every cheap
// validation (name+email present, valid email) so the only thing standing // validation (name+email present, valid email) so the only thing standing
// between the request and the Cal.com call is the captcha check. blockID is // between the request and the Cal.com call is the captcha check. blockID is
@ -84,10 +74,18 @@ func captchaBookingForm(blockID string) url.Values {
return v return v
} }
func postCaptchaBooking(h *CalcomHandler, form url.Values) *httptest.ResponseRecorder { // postCaptchaBooking submits the form. captchaHeader is the raw value of the
// host-stamped X-Bn-Verified-Captcha trusted header ("" = host stamped
// nothing: no valid cap-token crossed the host, or the instance captcha
// server was unavailable). Guests never see raw tokens — the host verifies
// and consumes them before dispatch (pluginsdk/auth/trustedheaders.go).
func postCaptchaBooking(h *CalcomHandler, form url.Values, captchaHeader string) *httptest.ResponseRecorder {
req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("X-Forwarded-For", "198.51.100.7") // TEST-NET-2, isolated bucket req.Header.Set("X-Forwarded-For", "198.51.100.7") // TEST-NET-2, isolated bucket
if captchaHeader != "" {
req.Header.Set(auth.HeaderVerifiedCaptcha, captchaHeader)
}
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
h.HandleCreateBooking(rec, req) h.HandleCreateBooking(rec, req)
return rec return rec
@ -108,60 +106,55 @@ func fakeCalcomServer(t *testing.T) (*httptest.Server, *atomic.Int32) {
return srv, &bookings return srv, &bookings
} }
func TestCalcomBooking_CaptchaEnabled_RejectsWithoutToken(t *testing.T) { func TestCalcomBooking_CaptchaEnabled_RejectsWithoutVerifiedHeader(t *testing.T) {
fake, bookings := fakeCalcomServer(t) fake, bookings := fakeCalcomServer(t)
withCalcomBaseURL(t, fake.URL) withCalcomBaseURL(t, fake.URL)
h := newCalcomCaptchaHandler(t, true) h := newCalcomCaptchaHandler(t, true)
SetCaptchaServer(newCalcomTestCaptchaServer())
t.Cleanup(func() { SetCaptchaServer(nil) })
rec := postCaptchaBooking(h, captchaBookingForm(uuid.NewString())) // no cap-token rec := postCaptchaBooking(h, captchaBookingForm(uuid.NewString()), "") // host stamped nothing
if got := bookings.Load(); got != 0 { if got := bookings.Load(); got != 0 {
t.Fatalf("Cal.com booking created %d times; want 0 (missing captcha token must be rejected)", got) t.Fatalf("Cal.com booking created %d times; want 0 (missing verified-captcha header must be rejected)", got)
} }
if !strings.Contains(rec.Body.String(), "aptcha") { if !strings.Contains(rec.Body.String(), "aptcha") {
t.Fatalf("expected captcha error fragment, got: %s", rec.Body.String()) t.Fatalf("expected captcha error fragment, got: %s", rec.Body.String())
} }
} }
func TestCalcomBooking_CaptchaEnabled_RejectsInvalidToken(t *testing.T) { func TestCalcomBooking_CaptchaEnabled_RejectsNonCanonicalHeaderValue(t *testing.T) {
fake, bookings := fakeCalcomServer(t) fake, bookings := fakeCalcomServer(t)
withCalcomBaseURL(t, fake.URL) withCalcomBaseURL(t, fake.URL)
h := newCalcomCaptchaHandler(t, true) h := newCalcomCaptchaHandler(t, true)
SetCaptchaServer(newCalcomTestCaptchaServer())
t.Cleanup(func() { SetCaptchaServer(nil) })
form := captchaBookingForm(uuid.NewString()) // Only the exact host-stamped value "1" counts; anything else reads as
form.Set("cap-token", "not-a-real-token") // unverified (auth.CaptchaVerified).
rec := postCaptchaBooking(h, form) rec := postCaptchaBooking(h, captchaBookingForm(uuid.NewString()), "true")
if got := bookings.Load(); got != 0 { if got := bookings.Load(); got != 0 {
t.Fatalf("Cal.com booking created %d times; want 0 (invalid token rejected)", got) t.Fatalf("Cal.com booking created %d times; want 0 (non-canonical header value rejected)", got)
} }
if !strings.Contains(rec.Body.String(), "aptcha") { if !strings.Contains(rec.Body.String(), "aptcha") {
t.Fatalf("expected captcha error fragment, got: %s", rec.Body.String()) t.Fatalf("expected captcha error fragment, got: %s", rec.Body.String())
} }
} }
func TestCalcomBooking_CaptchaEnabled_ProceedsWithValidToken(t *testing.T) { func TestCalcomBooking_CaptchaEnabled_ProceedsWithVerifiedHeader(t *testing.T) {
fake, bookings := fakeCalcomServer(t) fake, bookings := fakeCalcomServer(t)
withCalcomBaseURL(t, fake.URL) withCalcomBaseURL(t, fake.URL)
srv := newCalcomTestCaptchaServer()
h := newCalcomCaptchaHandler(t, true) h := newCalcomCaptchaHandler(t, true)
SetCaptchaServer(srv)
t.Cleanup(func() { SetCaptchaServer(nil) })
token := mintCalcomCaptchaToken(t, srv) // A real widget still posts the cap-token field; the HOST consumes it and
// stamps the header. The guest must trust the header and never forward the
// raw token to Cal.com.
form := captchaBookingForm(uuid.NewString()) form := captchaBookingForm(uuid.NewString())
form.Set("cap-token", token) form.Set("cap-token", "host-already-consumed-this")
rec := postCaptchaBooking(h, form) rec := postCaptchaBooking(h, form, "1")
if got := bookings.Load(); got != 1 { if got := bookings.Load(); got != 1 {
t.Fatalf("Cal.com booking created %d times; want 1 (valid token must pass captcha)", got) t.Fatalf("Cal.com booking created %d times; want 1 (host-verified captcha must pass)", got)
} }
if rec.Code != http.StatusOK { if rec.Code != http.StatusOK {
t.Fatalf("status = %d; want 200 (body=%s)", rec.Code, rec.Body.String()) t.Fatalf("status = %d; want 200 (body=%s)", rec.Code, rec.Body.String())
@ -172,16 +165,13 @@ func TestCalcomBooking_CaptchaEnabled_ProceedsWithValidToken(t *testing.T) {
} }
} }
func TestCalcomBooking_CaptchaDisabled_TokenNotRequired(t *testing.T) { func TestCalcomBooking_CaptchaDisabled_HeaderNotRequired(t *testing.T) {
fake, bookings := fakeCalcomServer(t) fake, bookings := fakeCalcomServer(t)
withCalcomBaseURL(t, fake.URL) withCalcomBaseURL(t, fake.URL)
h := newCalcomCaptchaHandler(t, false) h := newCalcomCaptchaHandler(t, false)
// A non-nil server would still reject if enforcement wrongly kicked in; nil
// proves the disabled path never touches the verifier.
SetCaptchaServer(nil)
rec := postCaptchaBooking(h, captchaBookingForm(uuid.NewString())) // no cap-token rec := postCaptchaBooking(h, captchaBookingForm(uuid.NewString()), "") // no header
if got := bookings.Load(); got != 1 { if got := bookings.Load(); got != 1 {
t.Fatalf("Cal.com booking created %d times; want 1 (captcha disabled)", got) t.Fatalf("Cal.com booking created %d times; want 1 (captcha disabled)", got)
@ -194,21 +184,22 @@ func TestCalcomBooking_CaptchaDisabled_TokenNotRequired(t *testing.T) {
} }
} }
func TestCalcomBooking_CaptchaEnabled_FailsClosedWhenServerNil(t *testing.T) { // TestCalcomBooking_CaptchaEnabled_FailsClosedWithoutHostStamp pins the
// fail-closed contract: a request that carries a cap-token but NO host stamp
// (instance captcha server down, or the token was invalid/replayed host-side)
// must be rejected — the guest never verifies tokens itself.
func TestCalcomBooking_CaptchaEnabled_FailsClosedWithoutHostStamp(t *testing.T) {
fake, bookings := fakeCalcomServer(t) fake, bookings := fakeCalcomServer(t)
withCalcomBaseURL(t, fake.URL) withCalcomBaseURL(t, fake.URL)
h := newCalcomCaptchaHandler(t, true) h := newCalcomCaptchaHandler(t, true)
// nil captcha server: the instance secret failed at startup. An enabled
// block must reject rather than allow the booking through unverified.
SetCaptchaServer(nil)
form := captchaBookingForm(uuid.NewString()) form := captchaBookingForm(uuid.NewString())
form.Set("cap-token", "anything") form.Set("cap-token", "anything")
rec := postCaptchaBooking(h, form) rec := postCaptchaBooking(h, form, "")
if got := bookings.Load(); got != 0 { if got := bookings.Load(); got != 0 {
t.Fatalf("Cal.com booking created %d times; want 0 (fail closed on nil server)", got) t.Fatalf("Cal.com booking created %d times; want 0 (fail closed without host stamp)", got)
} }
if !strings.Contains(rec.Body.String(), "aptcha") { if !strings.Contains(rec.Body.String(), "aptcha") {
t.Fatalf("expected captcha error fragment, got: %s", rec.Body.String()) t.Fatalf("expected captcha error fragment, got: %s", rec.Body.String())
@ -232,11 +223,9 @@ func TestCalcomBooking_CaptchaBypass_BlockIdCannotStripRequirement(t *testing.T)
withCalcomBaseURL(t, fake.URL) withCalcomBaseURL(t, fake.URL)
h := newCalcomCaptchaHandler(t, true) // published block requires captcha h := newCalcomCaptchaHandler(t, true) // published block requires captcha
SetCaptchaServer(newCalcomTestCaptchaServer())
t.Cleanup(func() { SetCaptchaServer(nil) })
// No valid cap-token; the requirement must not depend on blockId. // No host stamp; the requirement must not depend on blockId.
rec := postCaptchaBooking(h, captchaBookingForm(tc.blockID)) rec := postCaptchaBooking(h, captchaBookingForm(tc.blockID), "")
if got := bookings.Load(); got != 0 { if got := bookings.Load(); got != 0 {
t.Fatalf("Cal.com booking created %d times; want 0 (captcha requirement must not depend on blockId)", got) t.Fatalf("Cal.com booking created %d times; want 0 (captcha requirement must not depend on blockId)", got)
@ -252,7 +241,7 @@ func TestCalcomBooking_CaptchaBypass_BlockIdCannotStripRequirement(t *testing.T)
// deliberate choice for the requirement lookup: a resolver (DB) error is logged // deliberate choice for the requirement lookup: a resolver (DB) error is logged
// and treated as "not required", so a transient DB blip cannot block every // and treated as "not required", so a transient DB blip cannot block every
// booking site-wide. The honeypot + per-IP rate limit remain the baseline // booking site-wide. The honeypot + per-IP rate limit remain the baseline
// protection. (This is distinct from the nil-captcha-server path, which fails // protection. (This is distinct from the missing-host-stamp path, which fails
// CLOSED when a block IS known to require captcha.) // CLOSED when a block IS known to require captcha.)
func TestCalcomBooking_CaptchaRequirement_ResolverErrorFailsOpen(t *testing.T) { func TestCalcomBooking_CaptchaRequirement_ResolverErrorFailsOpen(t *testing.T) {
fake, bookings := fakeCalcomServer(t) fake, bookings := fakeCalcomServer(t)
@ -263,10 +252,8 @@ func TestCalcomBooking_CaptchaRequirement_ResolverErrorFailsOpen(t *testing.T) {
t.Fatalf("SetAPIKey: %v", err) t.Fatalf("SetAPIKey: %v", err)
} }
h.blockConfig = fakeBlockResolver{err: errors.New("db unavailable")} h.blockConfig = fakeBlockResolver{err: errors.New("db unavailable")}
SetCaptchaServer(newCalcomTestCaptchaServer())
t.Cleanup(func() { SetCaptchaServer(nil) })
rec := postCaptchaBooking(h, captchaBookingForm("")) // no cap-token rec := postCaptchaBooking(h, captchaBookingForm(""), "") // no host stamp
if got := bookings.Load(); got != 1 { if got := bookings.Load(); got != 1 {
t.Fatalf("Cal.com booking created %d times; want 1 (resolver error → captcha not enforced; honeypot + rate limit still apply)", got) t.Fatalf("Cal.com booking created %d times; want 1 (resolver error → captcha not enforced; honeypot + rate limit still apply)", got)
} }
@ -274,67 +261,3 @@ func TestCalcomBooking_CaptchaRequirement_ResolverErrorFailsOpen(t *testing.T) {
t.Fatalf("status = %d; want 200 (body=%s)", rec.Code, rec.Body.String()) t.Fatalf("status = %d; want 200 (body=%s)", rec.Code, rec.Body.String())
} }
} }
// ---------------------------------------------------------------------------
// captcha PoW helpers — local copies of the (unexported) core/captcha PRNG so
// the test can solve a challenge and mint a real verification token. Mirrors
// the helpers in internal/handlers/captcha_challenge_test.go.
// ---------------------------------------------------------------------------
func mintCalcomCaptchaToken(t *testing.T, srv *captcha.Server) string {
t.Helper()
ch, err := srv.CreateChallenge()
if err != nil {
t.Fatalf("CreateChallenge: %v", err)
}
intSols := solveCalcomChallenge(ch.Token, ch.Challenge.C, ch.Challenge.S, ch.Challenge.D)
sols := make([]string, len(intSols))
for i, n := range intSols {
sols[i] = strconv.Itoa(n)
}
rd := srv.Redeem(ch.Token, sols)
if !rd.Success || rd.Token == "" {
t.Fatalf("Redeem failed: %+v", rd)
}
return rd.Token
}
func solveCalcomChallenge(token string, c, s, d int) []int {
sols := make([]int, c)
for i := 1; i <= c; i++ {
salt := calcomPRNG(token+strconv.Itoa(i), s)
target := calcomPRNG(token+strconv.Itoa(i)+"d", d)
for n := 0; ; n++ {
sum := sha256.Sum256([]byte(salt + strconv.Itoa(n)))
h := hex.EncodeToString(sum[:])
if len(h) >= len(target) && h[:len(target)] == target {
sols[i-1] = n
break
}
}
}
return sols
}
func calcomPRNG(seed string, length int) string {
const mask = 0xFFFFFFFF
state := calcomFNV1a(seed)
var b strings.Builder
for b.Len() < length {
state ^= (state << 13) & mask
state ^= state >> 17
state ^= (state << 5) & mask
state &= mask
fmt.Fprintf(&b, "%08x", state)
}
return b.String()[:length]
}
func calcomFNV1a(s string) uint32 {
var h uint32 = 2166136261
for _, ch := range s {
h ^= uint32(ch)
h = (h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) & 0xFFFFFFFF
}
return h
}

View File

@ -15,7 +15,7 @@ import (
"testing" "testing"
"time" "time"
"git.dev.alexdunmow.com/block/core/auth" "git.dev.alexdunmow.com/block/pluginsdk/auth"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/google/uuid" "github.com/google/uuid"
) )

View File

@ -6,7 +6,7 @@
// init (hence wasmguest.Serve) before any ABI hook call. main is never called. // init (hence wasmguest.Serve) before any ABI hook call. main is never called.
package main package main
import "git.dev.alexdunmow.com/block/core/plugin/wasmguest" import "git.dev.alexdunmow.com/block/pluginsdk/plugin/wasmguest"
func init() { wasmguest.Serve(Registration) } func init() { wasmguest.Serve(Registration) }

View File

@ -2,8 +2,9 @@
name = "calcomblock" name = "calcomblock"
display_name = "Cal.com Booking" display_name = "Cal.com Booking"
scope = "@ninja" scope = "@ninja"
version = "2.0.1" version = "2.0.6"
description = "Embeddable Cal.com booking calendar block with custom styling, timezone-aware slot windowing, honeypot + captcha + rate-limited public booking endpoints, and webhook receiver." description = "Embeddable Cal.com booking calendar block with custom styling, timezone-aware slot windowing, honeypot + captcha + rate-limited public booking endpoints, and webhook receiver."
kind = "plugin" kind = "plugin"
categories = ["forms"] categories = ["forms"]
tags = ["calcom", "booking", "calendar", "scheduling", "appointments"] tags = ["calcom", "booking", "calendar", "scheduling", "appointments"]
allowed_hosts = ["api.cal.com"]

View File

@ -5,9 +5,9 @@ import (
"io/fs" "io/fs"
"net/http" "net/http"
"git.dev.alexdunmow.com/block/core/blocks" "git.dev.alexdunmow.com/block/pluginsdk/blocks"
"git.dev.alexdunmow.com/block/core/plugin" "git.dev.alexdunmow.com/block/pluginsdk/plugin"
"git.dev.alexdunmow.com/block/core/templates" "git.dev.alexdunmow.com/block/pluginsdk/templates"
) )
//go:embed all:web/dist/assets //go:embed all:web/dist/assets

View File

@ -4,9 +4,9 @@ import (
"io/fs" "io/fs"
"net/http" "net/http"
"git.dev.alexdunmow.com/block/core/blocks" "git.dev.alexdunmow.com/block/pluginsdk/blocks"
"git.dev.alexdunmow.com/block/core/plugin" "git.dev.alexdunmow.com/block/pluginsdk/plugin"
"git.dev.alexdunmow.com/block/core/templates" "git.dev.alexdunmow.com/block/pluginsdk/templates"
) )
// Registration is the compile-time plugin registration for the Cal.com Booking block. // Registration is the compile-time plugin registration for the Cal.com Booking block.

63
router_paths_test.go Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"net/http"
"net/http/httptest"
"testing"
"git.dev.alexdunmow.com/block/pluginsdk/auth"
"git.dev.alexdunmow.com/block/pluginsdk/plugin"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
)
// TestRouterMatchesAbsolutePluginPaths pins the wasm routing contract: the
// host forwards the FULL request path (/api/plugins/calcomblock/...) to the
// guest with no prefix strip, so every route must be registered absolute.
// Relative registrations 404 the plugin's entire HTTP surface at runtime —
// exactly the regression that shipped in 2.0.02.0.2.
func TestRouterMatchesAbsolutePluginPaths(t *testing.T) {
router, ok := NewCalcomRouter(plugin.CoreServices{}).(chi.Routes)
if !ok {
t.Fatal("NewCalcomRouter no longer returns a chi router")
}
for _, tc := range []struct{ method, path string }{
{http.MethodGet, "/api/plugins/calcomblock/reset"},
{http.MethodGet, "/api/plugins/calcomblock/slots"},
{http.MethodGet, "/api/plugins/calcomblock/date-grid"},
{http.MethodPost, "/api/plugins/calcomblock/book"},
{http.MethodPost, "/api/plugins/calcomblock/cancel"},
{http.MethodGet, "/api/plugins/calcomblock/event-types"},
} {
rctx := chi.NewRouteContext()
if !router.Match(rctx, tc.method, tc.path) {
t.Errorf("%s %s does not match any route — must be registered at the absolute plugin path", tc.method, tc.path)
}
}
}
// TestRouterRebuildsClaimsFromTrustedHeaders pins the wasm identity contract:
// context does not cross the ABI, so the ONLY way requireAdmin can see the
// caller is for the router to rebuild claims from the host-stamped
// X-Bn-Verified-* headers (auth.TrustedHeaderMiddleware). 2.0.3 shipped
// without it — every admin endpoint 401'd for real admins. A viewer-role
// header must reach requireAdmin and be rejected 403 (claims seen), never
// 401 (claims lost); no handler runs, so empty CoreServices is safe.
func TestRouterRebuildsClaimsFromTrustedHeaders(t *testing.T) {
router := NewCalcomRouter(plugin.CoreServices{})
req := httptest.NewRequest(http.MethodGet, "/api/plugins/calcomblock/settings", nil)
req.Header.Set(auth.HeaderVerifiedUserID, uuid.NewString())
req.Header.Set(auth.HeaderVerifiedEmail, "viewer@example.test")
req.Header.Set(auth.HeaderVerifiedRole, "viewer")
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code == http.StatusUnauthorized {
t.Fatalf("viewer trusted headers yielded 401 — TrustedHeaderMiddleware is not mounted, claims never reach requireAdmin")
}
if rec.Code != http.StatusForbidden {
t.Errorf("viewer trusted headers: expected 403 from requireAdmin, got %d (body %q)", rec.Code, rec.Body.String())
}
}

View File

@ -8,8 +8,8 @@ import (
"strings" "strings"
"time" "time"
"git.dev.alexdunmow.com/block/core/crypto" "git.dev.alexdunmow.com/block/pluginsdk/crypto"
"git.dev.alexdunmow.com/block/core/settings" "git.dev.alexdunmow.com/block/pluginsdk/settings"
"git.dev.alexdunmow.com/block/calcomblock/internal/db" "git.dev.alexdunmow.com/block/calcomblock/internal/db"
"git.dev.alexdunmow.com/block/calcomblock/internal/helpers" "git.dev.alexdunmow.com/block/calcomblock/internal/helpers"

View File

@ -1,550 +0,0 @@
import { importShared } from "./__federation_fn_import-hlt2XzeI.js";
import { j as jsxRuntimeExports } from "./jsx-runtime-CvJTHeKY.js";
const { useCallback, useEffect, useState } = await importShared("react");
const {
Alert,
AlertDescription,
Button,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
Input,
Label,
Textarea,
Switch,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Settings,
Edit3,
CheckCircle2,
XCircle,
Loader2,
Key,
AlertCircle,
ExternalLink,
RefreshCw,
t,
} = await importShared("@block-ninja/ui");
function renderConnectedAccount(apiKeyConfigured, calcomUsername) {
if (apiKeyConfigured === false) {
return /* @__PURE__ */ jsxRuntimeExports.jsx("p", {
className: "text-xs text-muted-foreground",
children: t("calcom.editor.connectedAs.noKey", "Configure the API key in plugin settings to connect an account."),
});
}
if (calcomUsername) {
return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-mono", children: calcomUsername });
}
return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.connectedAs.resolving", "Resolving account from API key…") });
}
function CalcomBlockEditor({ content, onChange }) {
const [activeTab, setActiveTab] = useState("config");
const [apiKeyConfigured, setApiKeyConfigured] = useState(null);
const [calcomUsername, setCalcomUsername] = useState("");
const [eventTypes, setEventTypes] = useState([]);
const [loadingEventTypes, setLoadingEventTypes] = useState(false);
const [fetchFailed, setFetchFailed] = useState(false);
const [retryCounter, setRetryCounter] = useState(0);
const handleFieldChange = useCallback(
(field, value) => {
onChange({ ...content, [field]: value });
},
[content, onChange],
);
const getString = (field, defaultValue = "") => {
return content[field] || defaultValue;
};
const getNumber = (field, defaultValue = 0) => {
const val = content[field];
if (typeof val === "number") return val;
if (typeof val === "string") return parseInt(val, 10) || defaultValue;
return defaultValue;
};
const getBool = (field, defaultValue = false) => {
const val = content[field];
if (typeof val === "boolean") return val;
return defaultValue;
};
const eventTypeSlug = getString("eventTypeSlug");
useEffect(() => {
fetch(`/api/plugins/calcomblock/settings`)
.then((r) => r.json())
.then((d) => {
setApiKeyConfigured(!!d.api_key_configured);
setCalcomUsername(d.username ?? "");
})
.catch(() => setApiKeyConfigured(false));
}, []);
useEffect(() => {
if (calcomUsername && content.username !== calcomUsername) {
onChange({ ...content, username: calcomUsername });
}
}, [calcomUsername, content, onChange]);
useEffect(() => {
if (!apiKeyConfigured) {
setEventTypes([]);
setFetchFailed(false);
return;
}
const timer = setTimeout(() => {
setLoadingEventTypes(true);
setFetchFailed(false);
fetch(`/api/plugins/calcomblock/event-types?username=${encodeURIComponent(calcomUsername)}`)
.then((r) => r.json())
.then((d) => {
if (d.success) {
setEventTypes(d.event_types ?? []);
} else {
setEventTypes([]);
setFetchFailed(true);
}
})
.catch(() => {
setEventTypes([]);
setFetchFailed(true);
})
.finally(() => setLoadingEventTypes(false));
}, 300);
return () => clearTimeout(timer);
}, [apiKeyConfigured, calcomUsername, retryCounter]);
const handleRetry = useCallback(() => {
setRetryCounter((n) => n + 1);
}, []);
const selectPlaceholder = (() => {
if (apiKeyConfigured === false) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", {
className: "inline-flex items-center gap-1.5 text-muted-foreground",
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Key, { className: "h-3 w-3" }), t("calcom.editor.eventType.placeholder.noApiKey", "Configure API key first")],
});
}
if (loadingEventTypes) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", {
className: "inline-flex items-center gap-1.5 text-muted-foreground",
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-3 w-3 animate-spin" }), t("calcom.editor.eventType.placeholder.loading", "Loading…")],
});
}
if (!calcomUsername) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", {
className: "inline-flex items-center gap-1.5 text-muted-foreground",
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-3 w-3 animate-spin" }), t("calcom.editor.eventType.placeholder.resolvingUser", "Resolving account…")],
});
}
if (eventTypes.length === 0) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", {
className: "inline-flex items-center gap-1.5 text-muted-foreground",
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-3 w-3" }), t("calcom.editor.eventType.placeholder.noEventTypes", "No event types found")],
});
}
return t("calcom.editor.eventType.placeholder.select", "Select event type");
})();
const apiKeyReady = apiKeyConfigured === true;
const usernameReady = calcomUsername.length > 0;
const eventTypeReady = eventTypeSlug.length > 0;
const canOpenInCalcom = usernameReady && eventTypeReady;
const renderStatusRow = (ok, label, action) =>
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex items-center gap-2 text-sm",
children: [
ok ?
/* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { "className": "h-4 w-4 text-primary", "aria-hidden": "true" })
: /* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { "className": "h-4 w-4 text-destructive", "aria-hidden": "true" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: ok ? "text-foreground" : "text-muted-foreground", children: label }),
action ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-auto", children: action }) : null,
],
});
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", {
className: "space-y-4",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, {
value: activeTab,
onValueChange: setActiveTab,
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, {
className: "grid w-full grid-cols-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, {
value: "config",
className: "gap-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { className: "h-4 w-4" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("calcom.editor.tab.configuration", "Configuration") }),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, {
value: "content",
className: "gap-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Edit3, { className: "h-4 w-4" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("calcom.editor.tab.content", "Content") }),
],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsContent, {
value: "config",
className: "space-y-4 mt-4",
children: [
apiKeyConfigured === false &&
/* @__PURE__ */ jsxRuntimeExports.jsx(Alert, {
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(AlertDescription, {
children: [
t("calcom.editor.alert.apiKeyMissing.prefix", "Configure your Cal.com API key in"),
" ",
/* @__PURE__ */ jsxRuntimeExports.jsx("a", {
href: "/admin/plugins?plugin=calcomblock",
className: "text-primary hover:underline",
children: t("calcom.editor.alert.apiKeyMissing.linkLabel", "plugin settings"),
}),
" ",
t("calcom.editor.alert.apiKeyMissing.suffix", "to enable the event-type dropdown."),
],
}),
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.editor.section.config.title", "Cal.com Settings") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, {
children: t("calcom.editor.section.config.description", "Connect to your Cal.com account and select an event type"),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, {
className: "space-y-4",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "space-y-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: t("calcom.editor.connectedAs.label", "Connected Account") }),
renderConnectedAccount(apiKeyConfigured, calcomUsername),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "space-y-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(Label, {
htmlFor: "eventTypeSlug",
children: [
t("calcom.editor.eventType.label", "Event Type"),
" ",
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" }),
],
}),
fetchFailed ?
/* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex items-center gap-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Input, {
id: "eventTypeSlug",
value: eventTypeSlug,
onChange: (e) => handleFieldChange("eventTypeSlug", e.target.value),
placeholder: t("calcom.editor.eventType.fallbackPlaceholder", "30min"),
className: "flex-1",
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Button, {
"type": "button",
"size": "sm",
"variant": "outline",
"action": "retry",
"entity": "event-types",
"onClick": handleRetry,
"disabled": loadingEventTypes,
"aria-label": t("calcom.editor.eventType.retryAria", "Retry loading event types"),
"children": [
/* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, {
"className": `h-4 w-4 ${loadingEventTypes ? "animate-spin" : ""}`,
"aria-hidden": "true",
}),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", {
className: "ml-1",
children: t("calcom.editor.eventType.retryLabel", "Retry"),
}),
],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Alert, {
variant: "destructive",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertCircle, { "className": "h-4 w-4", "aria-hidden": "true" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertDescription, {
children: t("calcom.editor.eventType.fetchFailed", "Couldn't load event types from Cal.com. Enter the slug manually or retry."),
}),
],
}),
],
})
: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(Select, {
value: eventTypeSlug,
onValueChange: (v) => handleFieldChange("eventTypeSlug", v),
disabled: loadingEventTypes || !apiKeyConfigured || eventTypes.length === 0,
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, {
"aria-label": t("calcom.editor.eventType.label", "Event Type"),
"children": /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: selectPlaceholder }),
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(SelectContent, {
children: eventTypes.map((et) =>
/* @__PURE__ */ jsxRuntimeExports.jsxs(
SelectItem,
{
value: et.slug,
children: [
et.title,
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", {
className: "text-muted-foreground",
children: [" ", "(", et.slug, " · ", et.lengthInMinutes, "m)"],
}),
],
},
et.id,
),
),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", {
className: "text-xs text-muted-foreground",
children: t("calcom.editor.eventType.help", "Fetched from your Cal.com account"),
}),
],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "space-y-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "weeksToShow", children: t("calcom.editor.weeksToShow.label", "Weeks to Display") }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Select, {
value: String(getNumber("weeksToShow", 2)),
onValueChange: (v) => handleFieldChange("weeksToShow", parseInt(v, 10)),
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, {
"aria-label": t("calcom.editor.weeksToShow.label", "Weeks to Display"),
"children": /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: t("calcom.editor.weeksToShow.placeholder", "Select weeks") }),
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(SelectContent, {
children: [1, 2, 3, 4, 5, 6, 7, 8].map((n) =>
/* @__PURE__ */ jsxRuntimeExports.jsxs(
SelectItem,
{
value: String(n),
children: [
n,
" ",
n > 1 ? t("calcom.editor.weeksToShow.unitPlural", "weeks") : t("calcom.editor.weeksToShow.unitSingular", "week"),
],
},
n,
),
),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", {
className: "text-xs text-muted-foreground",
children: t("calcom.editor.weeksToShow.help", "How many weeks of dates to show in the calendar"),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex items-center justify-between rounded-lg border p-3",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "space-y-0.5",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: t("calcom.editor.showTimezone.label", "Show Timezone Selector") }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", {
className: "text-xs text-muted-foreground",
children: t("calcom.editor.showTimezone.help", "Show a label above the date grid indicating which timezone times are displayed in."),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(Switch, {
"checked": getBool("showTimezone", true),
"onCheckedChange": (v) => handleFieldChange("showTimezone", v),
"aria-label": t("calcom.editor.showTimezone.label", "Show Timezone Selector"),
}),
],
}),
],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.editor.status.title", "Configuration status") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, {
children: t("calcom.editor.status.description", "Quick check of the values needed before this block renders bookings."),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, {
className: "space-y-2",
children: [
renderStatusRow(
apiKeyReady,
t("calcom.editor.status.apiKey", "API key configured"),
!apiKeyReady ?
/* @__PURE__ */ jsxRuntimeExports.jsx("a", {
href: "/admin/plugins?plugin=calcomblock",
className: "text-xs text-primary hover:underline",
children: t("calcom.editor.status.apiKey.configureLink", "Configure"),
})
: null,
),
renderStatusRow(usernameReady, t("calcom.editor.status.username", "Account resolved")),
renderStatusRow(eventTypeReady, t("calcom.editor.status.eventType", "Event type selected")),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex items-center gap-2 text-sm",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-flex h-4 w-4 items-center justify-center text-muted-foreground", children: "·" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", {
className: "text-muted-foreground",
children: t("calcom.editor.status.eventTypesAvailable", "{count} event types available").replace("{count}", String(eventTypes.length)),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", {
className: "pt-2",
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button, {
"type": "button",
"size": "sm",
"variant": "outline",
"action": "open",
"entity": "calcom-page",
"asChild": canOpenInCalcom,
"disabled": !canOpenInCalcom,
"aria-label": t("calcom.editor.status.openExternalAria", "Open Cal.com booking page in a new tab"),
"children":
canOpenInCalcom ?
/* @__PURE__ */ jsxRuntimeExports.jsxs("a", {
href: `https://cal.com/${calcomUsername}/${eventTypeSlug}`,
target: "_blank",
rel: "noopener noreferrer",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { "className": "h-4 w-4", "aria-hidden": "true" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", {
className: "ml-1",
children: t("calcom.editor.status.openExternal", "Open in Cal.com"),
}),
],
})
: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { "className": "h-4 w-4", "aria-hidden": "true" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", {
className: "ml-1",
children: t("calcom.editor.status.openExternal", "Open in Cal.com"),
}),
],
}),
}),
}),
],
}),
],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, {
value: "content",
className: "space-y-4 mt-4",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.editor.section.content.title", "Widget Content") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, {
children: t("calcom.editor.section.content.description", "Customize the text shown in the booking widget"),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, {
className: "space-y-4",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "space-y-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "title", children: t("calcom.editor.title.label", "Title") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(Input, {
id: "title",
value: getString("title"),
onChange: (e) => handleFieldChange("title", e.target.value),
placeholder: t("calcom.editor.title.placeholder", "Book a Meeting"),
}),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", {
className: "text-xs text-muted-foreground",
children: t("calcom.editor.title.help", "Main heading shown above the calendar"),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "space-y-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "description", children: t("calcom.editor.description.label", "Description") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(Textarea, {
id: "description",
value: getString("description"),
onChange: (e) => handleFieldChange("description", e.target.value),
placeholder: t("calcom.editor.description.placeholder", "Choose a convenient time for your consultation..."),
rows: 3,
}),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", {
className: "text-xs text-muted-foreground",
children: t("calcom.editor.description.help", "Optional text shown below the title"),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "space-y-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, {
htmlFor: "unavailableMessage",
children: t("calcom.editor.unavailableMessage.label", "Booking unavailable message"),
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(Textarea, {
id: "unavailableMessage",
value: getString("unavailableMessage"),
onChange: (e) => handleFieldChange("unavailableMessage", e.target.value),
placeholder: t(
"calcom.editor.unavailableMessage.placeholder",
"Sorry — we couldn't complete your booking online. Please use our contact form and we'll get you booked in.",
),
rows: 3,
}),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", {
className: "text-xs text-muted-foreground",
children: t("calcom.editor.unavailableMessage.help", "Shown if an online booking can't be completed. Leave blank to use the default."),
}),
],
}),
],
}),
],
}),
}),
],
}),
});
}
export { CalcomBlockEditor as default };

View File

@ -0,0 +1,351 @@
import { importShared } from './__federation_fn_import-hlt2XzeI.js';
import { j as jsxRuntimeExports } from './jsx-runtime-CvJTHeKY.js';
const {useCallback,useEffect,useState} = await importShared('react');
const {Alert,AlertDescription,Button,Card,CardContent,CardDescription,CardHeader,CardTitle,Tabs,TabsContent,TabsList,TabsTrigger,Input,Label,Textarea,Switch,Select,SelectContent,SelectItem,SelectTrigger,SelectValue,Settings,Edit3,CheckCircle2,XCircle,Loader2,Key,AlertCircle,ExternalLink,RefreshCw,t} = await importShared('@block-ninja/ui');
function renderConnectedAccount(apiKeyConfigured, calcomUsername) {
if (apiKeyConfigured === false) {
return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.connectedAs.noKey", "Configure the API key in plugin settings to connect an account.") });
}
if (calcomUsername) {
return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-mono", children: calcomUsername });
}
return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.connectedAs.resolving", "Resolving account from API key…") });
}
function CalcomBlockEditor({ content, onChange }) {
const [activeTab, setActiveTab] = useState("config");
const [apiKeyConfigured, setApiKeyConfigured] = useState(null);
const [calcomUsername, setCalcomUsername] = useState("");
const [eventTypes, setEventTypes] = useState([]);
const [loadingEventTypes, setLoadingEventTypes] = useState(false);
const [fetchFailed, setFetchFailed] = useState(false);
const [retryCounter, setRetryCounter] = useState(0);
const handleFieldChange = useCallback(
(field, value) => {
onChange({ ...content, [field]: value });
},
[content, onChange]
);
const getString = (field, defaultValue = "") => {
return content[field] || defaultValue;
};
const getNumber = (field, defaultValue = 0) => {
const val = content[field];
if (typeof val === "number") return val;
if (typeof val === "string") return parseInt(val, 10) || defaultValue;
return defaultValue;
};
const getBool = (field, defaultValue = false) => {
const val = content[field];
if (typeof val === "boolean") return val;
return defaultValue;
};
const eventTypeSlug = getString("eventTypeSlug");
useEffect(() => {
fetch(`/api/plugins/calcomblock/settings`).then((r) => r.json()).then((d) => {
setApiKeyConfigured(!!d.api_key_configured);
setCalcomUsername(d.username ?? "");
}).catch(() => setApiKeyConfigured(false));
}, []);
useEffect(() => {
if (calcomUsername && content.username !== calcomUsername) {
onChange({ ...content, username: calcomUsername });
}
}, [calcomUsername, content, onChange]);
useEffect(() => {
if (!apiKeyConfigured) {
setEventTypes([]);
setFetchFailed(false);
return;
}
const timer = setTimeout(() => {
setLoadingEventTypes(true);
setFetchFailed(false);
fetch(`/api/plugins/calcomblock/event-types?username=${encodeURIComponent(calcomUsername)}`).then((r) => r.json()).then((d) => {
if (d.success) {
setEventTypes(d.event_types ?? []);
} else {
setEventTypes([]);
setFetchFailed(true);
}
}).catch(() => {
setEventTypes([]);
setFetchFailed(true);
}).finally(() => setLoadingEventTypes(false));
}, 300);
return () => clearTimeout(timer);
}, [apiKeyConfigured, calcomUsername, retryCounter]);
const handleRetry = useCallback(() => {
setRetryCounter((n) => n + 1);
}, []);
const selectPlaceholder = (() => {
if (apiKeyConfigured === false) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Key, { className: "h-3 w-3" }),
t("calcom.editor.eventType.placeholder.noApiKey", "Configure API key first")
] });
}
if (loadingEventTypes) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-3 w-3 animate-spin" }),
t("calcom.editor.eventType.placeholder.loading", "Loading…")
] });
}
if (!calcomUsername) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-3 w-3 animate-spin" }),
t("calcom.editor.eventType.placeholder.resolvingUser", "Resolving account…")
] });
}
if (eventTypes.length === 0) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-3 w-3" }),
t("calcom.editor.eventType.placeholder.noEventTypes", "No event types found")
] });
}
return t("calcom.editor.eventType.placeholder.select", "Select event type");
})();
const apiKeyReady = apiKeyConfigured === true;
const usernameReady = calcomUsername.length > 0;
const eventTypeReady = eventTypeSlug.length > 0;
const canOpenInCalcom = usernameReady && eventTypeReady;
const renderStatusRow = (ok, label, action) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
ok ? /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-4 w-4 text-primary", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-4 w-4 text-destructive", "aria-hidden": "true" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: ok ? "text-foreground" : "text-muted-foreground", children: label }),
action ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-auto", children: action }) : null
] });
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { className: "grid w-full grid-cols-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, { value: "config", className: "gap-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { className: "h-4 w-4" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("calcom.editor.tab.configuration", "Configuration") })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, { value: "content", className: "gap-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Edit3, { className: "h-4 w-4" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("calcom.editor.tab.content", "Content") })
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsContent, { value: "config", className: "space-y-4 mt-4", children: [
apiKeyConfigured === false && /* @__PURE__ */ jsxRuntimeExports.jsx(Alert, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(AlertDescription, { children: [
t("calcom.editor.alert.apiKeyMissing.prefix", "Configure your Cal.com API key in"),
" ",
/* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "/admin/plugins?plugin=calcomblock", className: "text-primary hover:underline", children: t("calcom.editor.alert.apiKeyMissing.linkLabel", "plugin settings") }),
" ",
t("calcom.editor.alert.apiKeyMissing.suffix", "to enable the event-type dropdown.")
] }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.editor.section.config.title", "Cal.com Settings") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.editor.section.config.description", "Connect to your Cal.com account and select an event type") })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: t("calcom.editor.connectedAs.label", "Connected Account") }),
renderConnectedAccount(apiKeyConfigured, calcomUsername)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: "eventTypeSlug", children: [
t("calcom.editor.eventType.label", "Event Type"),
" ",
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
] }),
fetchFailed ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
Input,
{
id: "eventTypeSlug",
value: eventTypeSlug,
onChange: (e) => handleFieldChange("eventTypeSlug", e.target.value),
placeholder: t("calcom.editor.eventType.fallbackPlaceholder", "30min"),
className: "flex-1"
}
),
/* @__PURE__ */ jsxRuntimeExports.jsxs(
Button,
{
type: "button",
size: "sm",
variant: "outline",
action: "retry",
entity: "event-types",
onClick: handleRetry,
disabled: loadingEventTypes,
"aria-label": t("calcom.editor.eventType.retryAria", "Retry loading event types"),
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: `h-4 w-4 ${loadingEventTypes ? "animate-spin" : ""}`, "aria-hidden": "true" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1", children: t("calcom.editor.eventType.retryLabel", "Retry") })
]
}
)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Alert, { variant: "destructive", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertCircle, { className: "h-4 w-4", "aria-hidden": "true" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertDescription, { children: t("calcom.editor.eventType.fetchFailed", "Couldn't load event types from Cal.com. Enter the slug manually or retry.") })
] })
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(
Select,
{
value: eventTypeSlug,
onValueChange: (v) => handleFieldChange("eventTypeSlug", v),
disabled: loadingEventTypes || !apiKeyConfigured || eventTypes.length === 0,
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { "aria-label": t("calcom.editor.eventType.label", "Event Type"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: selectPlaceholder }) }),
/* @__PURE__ */ jsxRuntimeExports.jsx(SelectContent, { children: eventTypes.map((et) => /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectItem, { value: et.slug, children: [
et.title,
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground", children: [
" ",
"(",
et.slug,
" · ",
et.lengthInMinutes,
"m)"
] })
] }, et.id)) })
]
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.eventType.help", "Fetched from your Cal.com account") })
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "weeksToShow", children: t("calcom.editor.weeksToShow.label", "Weeks to Display") }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: String(getNumber("weeksToShow", 2)), onValueChange: (v) => handleFieldChange("weeksToShow", parseInt(v, 10)), children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { "aria-label": t("calcom.editor.weeksToShow.label", "Weeks to Display"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: t("calcom.editor.weeksToShow.placeholder", "Select weeks") }) }),
/* @__PURE__ */ jsxRuntimeExports.jsx(SelectContent, { children: [1, 2, 3, 4, 5, 6, 7, 8].map((n) => /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectItem, { value: String(n), children: [
n,
" ",
n > 1 ? t("calcom.editor.weeksToShow.unitPlural", "weeks") : t("calcom.editor.weeksToShow.unitSingular", "week")
] }, n)) })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.weeksToShow.help", "How many weeks of dates to show in the calendar") })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between rounded-lg border p-3", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-0.5", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: t("calcom.editor.showTimezone.label", "Show Timezone Selector") }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.showTimezone.help", "Show a label above the date grid indicating which timezone times are displayed in.") })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
Switch,
{
checked: getBool("showTimezone", true),
onCheckedChange: (v) => handleFieldChange("showTimezone", v),
"aria-label": t("calcom.editor.showTimezone.label", "Show Timezone Selector")
}
)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between rounded-lg border p-3", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-0.5", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: t("calcom.editor.captchaEnabled.label", "Enable captcha") }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.captchaEnabled.help", "Require visitors to solve a privacy-friendly proof-of-work captcha before booking (spam protection).") })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
Switch,
{
checked: getBool("captchaEnabled", false),
onCheckedChange: (v) => handleFieldChange("captchaEnabled", v),
"aria-label": t("calcom.editor.captchaEnabled.label", "Enable captcha")
}
)
] })
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.editor.status.title", "Configuration status") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.editor.status.description", "Quick check of the values needed before this block renders bookings.") })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-2", children: [
renderStatusRow(
apiKeyReady,
t("calcom.editor.status.apiKey", "API key configured"),
!apiKeyReady ? /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "/admin/plugins?plugin=calcomblock", className: "text-xs text-primary hover:underline", children: t("calcom.editor.status.apiKey.configureLink", "Configure") }) : null
),
renderStatusRow(usernameReady, t("calcom.editor.status.username", "Account resolved")),
renderStatusRow(eventTypeReady, t("calcom.editor.status.eventType", "Event type selected")),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-flex h-4 w-4 items-center justify-center text-muted-foreground", children: "·" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground", children: t("calcom.editor.status.eventTypesAvailable", "{count} event types available").replace("{count}", String(eventTypes.length)) })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
Button,
{
type: "button",
size: "sm",
variant: "outline",
action: "open",
entity: "calcom-page",
asChild: canOpenInCalcom,
disabled: !canOpenInCalcom,
"aria-label": t("calcom.editor.status.openExternalAria", "Open Cal.com booking page in a new tab"),
children: canOpenInCalcom ? /* @__PURE__ */ jsxRuntimeExports.jsxs("a", { href: `https://cal.com/${calcomUsername}/${eventTypeSlug}`, target: "_blank", rel: "noopener noreferrer", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "h-4 w-4", "aria-hidden": "true" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1", children: t("calcom.editor.status.openExternal", "Open in Cal.com") })
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "h-4 w-4", "aria-hidden": "true" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1", children: t("calcom.editor.status.openExternal", "Open in Cal.com") })
] })
}
) })
] })
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "content", className: "space-y-4 mt-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.editor.section.content.title", "Widget Content") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.editor.section.content.description", "Customize the text shown in the booking widget") })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "title", children: t("calcom.editor.title.label", "Title") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
Input,
{
id: "title",
value: getString("title"),
onChange: (e) => handleFieldChange("title", e.target.value),
placeholder: t("calcom.editor.title.placeholder", "Book a Meeting")
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.title.help", "Main heading shown above the calendar") })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "description", children: t("calcom.editor.description.label", "Description") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
Textarea,
{
id: "description",
value: getString("description"),
onChange: (e) => handleFieldChange("description", e.target.value),
placeholder: t("calcom.editor.description.placeholder", "Choose a convenient time for your consultation..."),
rows: 3
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.description.help", "Optional text shown below the title") })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "unavailableMessage", children: t("calcom.editor.unavailableMessage.label", "Booking unavailable message") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
Textarea,
{
id: "unavailableMessage",
value: getString("unavailableMessage"),
onChange: (e) => handleFieldChange("unavailableMessage", e.target.value),
placeholder: t(
"calcom.editor.unavailableMessage.placeholder",
"Sorry — we couldn't complete your booking online. Please use our contact form and we'll get you booked in."
),
rows: 3
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.unavailableMessage.help", "Shown if an online booking can't be completed. Leave blank to use the default.") })
] })
] })
] }) })
] }) });
}
export { CalcomBlockEditor as default };

View File

@ -1,619 +1,384 @@
import { importShared } from "./__federation_fn_import-hlt2XzeI.js"; import { importShared } from './__federation_fn_import-hlt2XzeI.js';
import { j as jsxRuntimeExports } from "./jsx-runtime-CvJTHeKY.js"; import { j as jsxRuntimeExports } from './jsx-runtime-CvJTHeKY.js';
const { useState, useCallback, useEffect } = await importShared("react"); const {useState,useCallback,useEffect} = await importShared('react');
const { const {Card,CardContent,CardDescription,CardHeader,CardTitle,Input,Label,Button,Alert,AlertDescription,Badge,ScrollArea,Loader2,CheckCircle2,XCircle,AlertCircle,Eye,EyeOff,Key,Zap,Settings,Send,Copy,RefreshCw,toast,t,useAlertDialog} = await importShared('@block-ninja/ui');
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Input,
Label,
Button,
Alert,
AlertDescription,
Badge,
ScrollArea,
Loader2,
CheckCircle2,
XCircle,
AlertCircle,
Eye,
EyeOff,
Key,
Zap,
Settings,
Send,
Copy,
RefreshCw,
toast,
t,
useAlertDialog,
} = await importShared("@block-ninja/ui");
const copyToClipboard = async (text) => { const copyToClipboard = async (text) => {
if (!text) return; if (!text) return;
try { try {
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(text);
toast.success(t("calcom.settings.copied", "Copied to clipboard")); toast.success(t("calcom.settings.copied", "Copied to clipboard"));
} catch (_error) { } catch (_error) {
toast.error(t("calcom.settings.copyFailed", "Could not copy to clipboard")); toast.error(t("calcom.settings.copyFailed", "Could not copy to clipboard"));
} }
}; };
const formatRelativeTime = (iso) => { const formatRelativeTime = (iso) => {
const parsed = Date.parse(iso); const parsed = Date.parse(iso);
if (Number.isNaN(parsed)) return iso; if (Number.isNaN(parsed)) return iso;
const diffMs = Date.now() - parsed; const diffMs = Date.now() - parsed;
const minutes = Math.round(diffMs / 6e4); const minutes = Math.round(diffMs / 6e4);
if (minutes < 1) return t("calcom.settings.relative.justNow", "just now"); if (minutes < 1) return t("calcom.settings.relative.justNow", "just now");
if (minutes < 60) { if (minutes < 60) {
return minutes === 1 ? t("calcom.settings.relative.minute", "1 minute ago") : t("calcom.settings.relative.minutes", "{count} minutes ago").replace("{count}", String(minutes)); return minutes === 1 ? t("calcom.settings.relative.minute", "1 minute ago") : t("calcom.settings.relative.minutes", "{count} minutes ago").replace("{count}", String(minutes));
} }
const hours = Math.round(minutes / 60); const hours = Math.round(minutes / 60);
if (hours < 24) { if (hours < 24) {
return hours === 1 ? t("calcom.settings.relative.hour", "1 hour ago") : t("calcom.settings.relative.hours", "{count} hours ago").replace("{count}", String(hours)); return hours === 1 ? t("calcom.settings.relative.hour", "1 hour ago") : t("calcom.settings.relative.hours", "{count} hours ago").replace("{count}", String(hours));
} }
const days = Math.round(hours / 24); const days = Math.round(hours / 24);
return days === 1 ? t("calcom.settings.relative.day", "1 day ago") : t("calcom.settings.relative.days", "{count} days ago").replace("{count}", String(days)); return days === 1 ? t("calcom.settings.relative.day", "1 day ago") : t("calcom.settings.relative.days", "{count} days ago").replace("{count}", String(days));
}; };
function CalcomSettings({ pluginName }) { function CalcomSettings({ pluginName }) {
const { confirm } = useAlertDialog(); const { confirm } = useAlertDialog();
const [apiKey, setApiKey] = useState(""); const [apiKey, setApiKey] = useState("");
const [showApiKey, setShowApiKey] = useState(false); const [showApiKey, setShowApiKey] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [testing, setTesting] = useState(false); const [testing, setTesting] = useState(false);
const [rotating, setRotating] = useState(false); const [rotating, setRotating] = useState(false);
const [showSecret, setShowSecret] = useState(false); const [showSecret, setShowSecret] = useState(false);
const [settings, setSettings] = useState(null); const [settings, setSettings] = useState(null);
const [testResult, setTestResult] = useState(null); const [testResult, setTestResult] = useState(null);
const [saveMessage, setSaveMessage] = useState(null); const [saveMessage, setSaveMessage] = useState(null);
const baseUrl = `/api/plugins/${pluginName}`; const baseUrl = `/api/plugins/${pluginName}`;
const loadSettings = useCallback(async () => { const loadSettings = useCallback(async () => {
try { try {
const res = await fetch(`${baseUrl}/settings`); const res = await fetch(`${baseUrl}/settings`);
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
setSettings(data); setSettings(data);
} }
} catch (_error) { } catch (_error) {
console.error("Failed to load settings"); console.error("Failed to load settings");
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [baseUrl]); }, [baseUrl]);
useEffect(() => { useEffect(() => {
loadSettings(); loadSettings();
}, [loadSettings]); }, [loadSettings]);
const handleSave = async () => { const handleSave = async () => {
if (!apiKey.trim()) { if (!apiKey.trim()) {
setSaveMessage({ type: "error", text: t("calcom.settings.apiKey.errorEmpty", "Please enter an API key") }); setSaveMessage({ type: "error", text: t("calcom.settings.apiKey.errorEmpty", "Please enter an API key") });
return; return;
} }
setSaving(true); setSaving(true);
setSaveMessage(null); setSaveMessage(null);
setTestResult(null); setTestResult(null);
try { try {
const res = await fetch(`${baseUrl}/settings`, { const res = await fetch(`${baseUrl}/settings`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_key: apiKey }), body: JSON.stringify({ api_key: apiKey })
}); });
if (res.ok) { if (res.ok) {
await loadSettings(); await loadSettings();
setApiKey(""); setApiKey("");
setSaveMessage({ type: "success", text: t("calcom.settings.apiKey.saved", "API key saved successfully") }); setSaveMessage({ type: "success", text: t("calcom.settings.apiKey.saved", "API key saved successfully") });
} else { } else {
const text = await res.text(); const text = await res.text();
setSaveMessage({ type: "error", text: text || t("calcom.settings.apiKey.saveFailed", "Failed to save API key") }); setSaveMessage({ type: "error", text: text || t("calcom.settings.apiKey.saveFailed", "Failed to save API key") });
} }
} catch (_error) { } catch (_error) {
setSaveMessage({ type: "error", text: t("calcom.settings.serverError", "Failed to connect to server") }); setSaveMessage({ type: "error", text: t("calcom.settings.serverError", "Failed to connect to server") });
} finally { } finally {
setSaving(false); setSaving(false);
} }
}; };
const handleTest = async () => { const handleTest = async () => {
setTesting(true); setTesting(true);
setTestResult(null); setTestResult(null);
try { try {
const res = await fetch(`${baseUrl}/test`, { method: "POST" }); const res = await fetch(`${baseUrl}/test`, { method: "POST" });
const data = await res.json(); const data = await res.json();
setTestResult(data); setTestResult(data);
} catch (_error) { } catch (_error) {
setTestResult({ success: false, error: t("calcom.settings.serverError", "Failed to connect to server") }); setTestResult({ success: false, error: t("calcom.settings.serverError", "Failed to connect to server") });
} finally { } finally {
setTesting(false); setTesting(false);
} }
}; };
const handleClear = async () => { const handleClear = async () => {
setSaving(true); setSaving(true);
setSaveMessage(null); setSaveMessage(null);
try { try {
const res = await fetch(`${baseUrl}/settings`, { const res = await fetch(`${baseUrl}/settings`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_key: "" }), body: JSON.stringify({ api_key: "" })
}); });
if (res.ok) { if (res.ok) {
await loadSettings(); await loadSettings();
setTestResult(null); setTestResult(null);
setSaveMessage({ type: "success", text: t("calcom.settings.apiKey.removed", "API key removed") }); setSaveMessage({ type: "success", text: t("calcom.settings.apiKey.removed", "API key removed") });
} }
} catch (_error) { } catch (_error) {
setSaveMessage({ type: "error", text: t("calcom.settings.apiKey.clearFailed", "Failed to clear API key") }); setSaveMessage({ type: "error", text: t("calcom.settings.apiKey.clearFailed", "Failed to clear API key") });
} finally { } finally {
setSaving(false); setSaving(false);
} }
}; };
const handleRotate = async () => { const handleRotate = async () => {
const confirmed = await confirm({ const confirmed = await confirm({
title: t("calcom.settings.webhook.rotateConfirmTitle", "Rotate webhook secret?"), title: t("calcom.settings.webhook.rotateConfirmTitle", "Rotate webhook secret?"),
message: t("calcom.settings.webhook.rotateConfirmMessage", "This invalidates the existing secret immediately. You'll need to update Cal.com to keep webhooks flowing."), message: t("calcom.settings.webhook.rotateConfirmMessage", "This invalidates the existing secret immediately. You'll need to update Cal.com to keep webhooks flowing."),
confirmLabel: t("calcom.settings.webhook.rotateConfirmLabel", "Rotate"), confirmLabel: t("calcom.settings.webhook.rotateConfirmLabel", "Rotate"),
variant: "destructive", variant: "destructive"
}); });
if (!confirmed) return; if (!confirmed) return;
setRotating(true); setRotating(true);
setSaveMessage(null); setSaveMessage(null);
try { try {
const res = await fetch(`${baseUrl}/settings/rotate-webhook-secret`, { method: "POST" }); const res = await fetch(`${baseUrl}/settings/rotate-webhook-secret`, { method: "POST" });
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
setSettings((prev) => (prev ? { ...prev, webhook_secret_masked: data.webhook_secret_masked, webhook_secret_configured: true } : prev)); setSettings((prev) => prev ? { ...prev, webhook_secret_masked: data.webhook_secret_masked, webhook_secret_configured: true } : prev);
const successMsg = t("calcom.settings.webhook.rotated", "Webhook secret rotated. Update it in Cal.com."); const successMsg = t("calcom.settings.webhook.rotated", "Webhook secret rotated. Update it in Cal.com.");
setSaveMessage({ type: "success", text: successMsg }); setSaveMessage({ type: "success", text: successMsg });
toast.success(successMsg); toast.success(successMsg);
} else { } else {
setSaveMessage({ type: "error", text: t("calcom.settings.webhook.rotateFailed", "Failed to rotate webhook secret") }); setSaveMessage({ type: "error", text: t("calcom.settings.webhook.rotateFailed", "Failed to rotate webhook secret") });
} }
} catch (_error) { } catch (_error) {
setSaveMessage({ type: "error", text: t("calcom.settings.serverError", "Failed to connect to server") }); setSaveMessage({ type: "error", text: t("calcom.settings.serverError", "Failed to connect to server") });
} finally { } finally {
setRotating(false); setRotating(false);
} }
}; };
if (loading) { if (loading) {
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }) });
className: "flex items-center justify-center py-8", }
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }), const apiKeyVisibilityLabel = showApiKey ? t("calcom.settings.toggleVisibility.hide", "Hide") : t("calcom.settings.toggleVisibility.show", "Show");
}); const secretVisibilityLabel = showSecret ? t("calcom.settings.toggleVisibility.hide", "Hide") : t("calcom.settings.toggleVisibility.show", "Show");
} return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
const apiKeyVisibilityLabel = showApiKey ? t("calcom.settings.toggleVisibility.hide", "Hide") : t("calcom.settings.toggleVisibility.show", "Show"); /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
const secretVisibilityLabel = showSecret ? t("calcom.settings.toggleVisibility.hide", "Hide") : t("calcom.settings.toggleVisibility.show", "Show"); /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-semibold flex items-center gap-2", children: [
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { className: "h-5 w-5" }),
className: "space-y-6", t("calcom.settings.heading", "Cal.com Integration")
children: [ ] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground", children: t("calcom.settings.headingDescription", "Configure your Cal.com API key to enable booking functionality") })
children: [ ] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
className: "text-xl font-semibold flex items-center gap-2", /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { className: "h-5 w-5" }), t("calcom.settings.heading", "Cal.com Integration")], /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [
}), /* @__PURE__ */ jsxRuntimeExports.jsx(Key, { className: "h-4 w-4" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { t("calcom.settings.apiKey.title", "API Key Status")
className: "text-sm text-muted-foreground", ] }),
children: t("calcom.settings.headingDescription", "Configure your Cal.com API key to enable booking functionality"), /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.settings.apiKey.description", "Your Cal.com API key is required for the booking widget to function") })
}), ] }),
], /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
}), settings?.api_key_configured ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between rounded-lg border bg-muted/30 p-3", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
children: [ /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "default", className: "gap-1.5", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-3.5 w-3.5" }),
children: [ t("calcom.settings.apiKey.configuredLabel", "Configured")
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { ] }),
className: "text-base flex items-center gap-2", /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground font-mono", children: settings.api_key_masked || t("calcom.settings.apiKey.maskedFallback", "***configured***") })
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Key, { className: "h-4 w-4" }), t("calcom.settings.apiKey.title", "API Key Status")], ] }),
}), /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { action: "delete", entity: "plugin-setting", variant: "ghost", size: "sm", onClick: handleClear, disabled: saving, children: t("calcom.settings.apiKey.remove", "Remove") })
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 rounded-lg border bg-muted/30 p-3", children: [
children: t("calcom.settings.apiKey.description", "Your Cal.com API key is required for the booking widget to function"), /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "gap-1.5", children: [
}), /* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-3.5 w-3.5" }),
], t("calcom.settings.apiKey.notConfiguredLabel", "Not configured")
}), ] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.settings.apiKey.notConfiguredHelp", "Enter your Cal.com API key below to enable booking") })
className: "space-y-4", ] }),
children: [ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
settings?.api_key_configured ? /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "apiKey", children: settings?.api_key_configured ? t("calcom.settings.apiKey.updateLabel", "Update API Key") : t("calcom.settings.apiKey.label", "API Key") }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
className: "flex items-center justify-between rounded-lg border bg-muted/30 p-3", /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex-1", children: [
children: [ /* @__PURE__ */ jsxRuntimeExports.jsx(
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { Input,
className: "flex items-center gap-3", {
children: [ id: "apiKey",
/* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { type: showApiKey ? "text" : "password",
variant: "default", value: apiKey,
className: "gap-1.5", onChange: (e) => setApiKey(e.target.value),
children: [ placeholder: t("calcom.settings.apiKey.placeholder", "cal_live_..."),
/* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-3.5 w-3.5" }), className: "pr-10"
t("calcom.settings.apiKey.configuredLabel", "Configured"), }
], ),
}), /* @__PURE__ */ jsxRuntimeExports.jsx(
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { Button,
className: "text-xs text-muted-foreground font-mono", {
children: settings.api_key_masked || t("calcom.settings.apiKey.maskedFallback", "***configured***"), type: "button",
}), variant: "ghost",
], action: "toggle",
}), entity: "api-key-visibility",
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: () => setShowApiKey(!showApiKey),
action: "delete", "aria-label": apiKeyVisibilityLabel,
entity: "plugin-setting", className: "absolute right-3 top-1/2 -translate-y-1/2 h-auto p-0 text-muted-foreground hover:bg-transparent hover:text-foreground",
variant: "ghost", children: showApiKey ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "h-4 w-4" })
size: "sm", }
onClick: handleClear, )
disabled: saving, ] }),
children: t("calcom.settings.apiKey.remove", "Remove"), /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { action: "save", entity: "plugin-setting", onClick: handleSave, disabled: saving || !apiKey.trim(), children: saving ? /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : t("calcom.settings.apiKey.save", "Save") })
}), ] }),
], /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-muted-foreground", children: [
}) t("calcom.settings.apiKey.helpPrefix", "Get your API key from"),
: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { " ",
className: "flex items-center gap-3 rounded-lg border bg-muted/30 p-3", /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "https://app.cal.com/settings/developer/api-keys", target: "_blank", rel: "noopener noreferrer", className: "text-primary hover:underline", children: t("calcom.settings.apiKey.helpLink", "Cal.com API Settings") })
children: [ ] })
/* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { ] }),
variant: "outline", saveMessage && /* @__PURE__ */ jsxRuntimeExports.jsx(Alert, { variant: saveMessage.type === "error" ? "destructive" : "default", children: /* @__PURE__ */ jsxRuntimeExports.jsx(AlertDescription, { children: saveMessage.text }) })
className: "gap-1.5", ] })
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-3.5 w-3.5" }), t("calcom.settings.apiKey.notConfiguredLabel", "Not configured")], ] }),
}), settings?.api_key_configured && /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
className: "text-xs text-muted-foreground", /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [
children: t("calcom.settings.apiKey.notConfiguredHelp", "Enter your Cal.com API key below to enable booking"), /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "h-4 w-4" }),
}), t("calcom.settings.test.title", "Test Connection")
], ] }),
}), /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.settings.test.description", "Verify your API key works and see available event types") })
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ] }),
className: "space-y-2", /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
children: [ /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { action: "settings", entity: "plugin-connection", onClick: handleTest, disabled: testing, children: testing ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
htmlFor: "apiKey", t("calcom.settings.test.testing", "Testing...")
children: settings?.api_key_configured ? t("calcom.settings.apiKey.updateLabel", "Update API Key") : t("calcom.settings.apiKey.label", "API Key"), ] }) : t("calcom.settings.test.button", "Test Connection") }),
}), testResult && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: testResult.success ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { /* @__PURE__ */ jsxRuntimeExports.jsxs(Alert, { children: [
className: "flex gap-2", /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-4 w-4 text-success" }),
children: [ /* @__PURE__ */ jsxRuntimeExports.jsxs(AlertDescription, { className: "ml-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { t("calcom.settings.test.successPrefix", "Connection successful! Found"),
className: "relative flex-1", " ",
children: [ testResult.event_types?.length || 0,
/* @__PURE__ */ jsxRuntimeExports.jsx(Input, { " ",
id: "apiKey", t("calcom.settings.test.successSuffix", "event types.")
type: showApiKey ? "text" : "password", ] })
value: apiKey, ] }),
onChange: (e) => setApiKey(e.target.value), testResult.event_types && testResult.event_types.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "max-h-96 rounded-lg border", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-sm", children: [
placeholder: t("calcom.settings.apiKey.placeholder", "cal_live_..."), /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { className: "bg-muted", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
className: "pr-10", /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "text-left px-3 py-2 font-medium", children: t("calcom.settings.test.tableEventType", "Event Type") }),
}), /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "text-left px-3 py-2 font-medium", children: t("calcom.settings.test.tableSlug", "Slug") })
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { ] }) }),
"type": "button", /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: testResult.event_types.map((et) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-t", children: [
"variant": "ghost", /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2", children: et.title }),
"action": "toggle", /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2 font-mono text-xs", children: et.slug })
"entity": "api-key-visibility", ] }, et.id)) })
"onClick": () => setShowApiKey(!showApiKey), ] }) })
"aria-label": apiKeyVisibilityLabel, ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(Alert, { variant: "destructive", children: [
"className": "absolute right-3 top-1/2 -translate-y-1/2 h-auto p-0 text-muted-foreground hover:bg-transparent hover:text-foreground", /* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-4 w-4" }),
"children": /* @__PURE__ */ jsxRuntimeExports.jsx(AlertDescription, { className: "ml-2", children: testResult.error || t("calcom.settings.test.failure", "Connection failed") })
showApiKey ? ] }) })
/* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "h-4 w-4" }) ] })
: /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "h-4 w-4" }), ] }),
}), settings?.api_key_configured && /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
], /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
}), /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { /* @__PURE__ */ jsxRuntimeExports.jsx(Send, { className: "h-4 w-4" }),
action: "save", t("calcom.settings.webhook.title", "Webhook")
entity: "plugin-setting", ] }),
onClick: handleSave, /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.settings.webhook.description", "Configure this URL in Cal.com → Settings → Developer → Webhooks to receive booking events.") })
disabled: saving || !apiKey.trim(), ] }),
children: saving ? /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : t("calcom.settings.apiKey.save", "Save"), /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
}), /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
], /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "webhookUrl", children: t("calcom.settings.webhook.urlLabel", "Webhook URL") }),
}), /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { /* @__PURE__ */ jsxRuntimeExports.jsx(Input, { id: "webhookUrl", readOnly: true, value: settings?.webhook_url || "", className: "flex-1 font-mono text-xs" }),
className: "text-xs text-muted-foreground", /* @__PURE__ */ jsxRuntimeExports.jsx(
children: [ Button,
t("calcom.settings.apiKey.helpPrefix", "Get your API key from"), {
" ", action: "copy",
/* @__PURE__ */ jsxRuntimeExports.jsx("a", { entity: "webhook-url",
href: "https://app.cal.com/settings/developer/api-keys", variant: "outline",
target: "_blank", size: "sm",
rel: "noopener noreferrer", "aria-label": t("calcom.settings.webhook.copyUrl", "Copy webhook URL"),
className: "text-primary hover:underline", onClick: () => copyToClipboard(settings?.webhook_url || ""),
children: t("calcom.settings.apiKey.helpLink", "Cal.com API Settings"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "h-4 w-4" })
}), }
], )
}), ] })
], ] }),
}), /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
saveMessage && /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "webhookSecret", children: t("calcom.settings.webhook.secretLabel", "Webhook Secret") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(Alert, { /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
variant: saveMessage.type === "error" ? "destructive" : "default", /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex-1", children: [
children: /* @__PURE__ */ jsxRuntimeExports.jsx(AlertDescription, { children: saveMessage.text }), /* @__PURE__ */ jsxRuntimeExports.jsx(
}), Input,
], {
}), id: "webhookSecret",
], type: showSecret ? "text" : "password",
}), readOnly: true,
settings?.api_key_configured && value: settings?.webhook_secret_masked || t("calcom.settings.webhook.secretNotConfigured", "Not configured"),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { className: "pr-10 font-mono text-xs"
children: [ }
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { ),
children: [ /* @__PURE__ */ jsxRuntimeExports.jsx(
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { Button,
className: "text-base flex items-center gap-2", {
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "h-4 w-4" }), t("calcom.settings.test.title", "Test Connection")], type: "button",
}), variant: "ghost",
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.settings.test.description", "Verify your API key works and see available event types") }), action: "toggle",
], entity: "webhook-secret-visibility",
}), onClick: () => setShowSecret(!showSecret),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { "aria-label": secretVisibilityLabel,
className: "space-y-4", className: "absolute right-3 top-1/2 -translate-y-1/2 h-auto p-0 text-muted-foreground hover:bg-transparent hover:text-foreground",
children: [ children: showSecret ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "h-4 w-4" })
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { }
action: "settings", )
entity: "plugin-connection", ] }),
onClick: handleTest, /* @__PURE__ */ jsxRuntimeExports.jsx(
disabled: testing, Button,
children: {
testing ? action: "copy",
/* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { entity: "webhook-secret",
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), t("calcom.settings.test.testing", "Testing...")], variant: "outline",
}) size: "sm",
: t("calcom.settings.test.button", "Test Connection"), "aria-label": t("calcom.settings.webhook.copySecret", "Copy webhook secret"),
}), onClick: () => copyToClipboard(settings?.webhook_secret_masked || ""),
testResult && children: /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "h-4 w-4" })
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { }
className: "space-y-3", ),
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
testResult.success ? Button,
/* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { {
children: [ action: "rotate",
/* @__PURE__ */ jsxRuntimeExports.jsxs(Alert, { entity: "webhook-secret",
children: [ variant: "outline",
/* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-4 w-4 text-success" }), size: "sm",
/* @__PURE__ */ jsxRuntimeExports.jsxs(AlertDescription, { "aria-label": t("calcom.settings.webhook.rotate", "Rotate webhook secret"),
className: "ml-2", onClick: handleRotate,
children: [ disabled: rotating,
t("calcom.settings.test.successPrefix", "Connection successful! Found"), children: rotating ? /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "h-4 w-4" })
" ", }
testResult.event_types?.length || 0, )
" ", ] }),
t("calcom.settings.test.successSuffix", "event types."), /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t(
], "calcom.settings.webhook.secretHelp",
}), "Cal.com displays the secret only once. Regenerating invalidates the previous value immediately — update it in Cal.com after rotating."
], ) })
}), ] }),
testResult.event_types && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm", children: settings?.last_event_at ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-foreground", children: [
testResult.event_types.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-4 w-4 text-success" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
className: "max-h-96 rounded-lg border", t("calcom.settings.webhook.lastEventPrefix", "Last event"),
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { " ",
className: "w-full text-sm", formatRelativeTime(settings.last_event_at),
children: [ settings.last_event_type && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("thead", { " — ",
className: "bg-muted", /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono text-xs", children: settings.last_event_type })
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { ] })
children: [ ] })
/* @__PURE__ */ jsxRuntimeExports.jsx("th", { ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
className: "text-left px-3 py-2 font-medium", /* @__PURE__ */ jsxRuntimeExports.jsx(AlertCircle, { className: "h-4 w-4" }),
children: t("calcom.settings.test.tableEventType", "Event Type"), /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("calcom.settings.webhook.waitingForEvent", "Status: Waiting for first event") })
}), ] }) })
/* @__PURE__ */ jsxRuntimeExports.jsx("th", { ] })
className: "text-left px-3 py-2 font-medium", ] }),
children: t("calcom.settings.test.tableSlug", "Slug"), /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
}), /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
], /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.settings.docs.title", "Documentation") }),
}), /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.settings.docs.description", "Reference material for working with the Cal.com plugin.") })
}), ] }),
/* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-2 text-sm", children: [
children: testResult.event_types.map((et) => /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "https://cal.com/docs/api-reference/v2", target: "_blank", rel: "noopener noreferrer", className: "text-primary hover:underline block", children: t("calcom.settings.docs.calcomApi", "Cal.com API documentation") }),
/* @__PURE__ */ jsxRuntimeExports.jsxs( /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "/admin/docs/calcom-plugin", target: "_blank", rel: "noopener noreferrer", className: "text-primary hover:underline block", children: t("calcom.settings.docs.pluginGuide", "BlockNinja plugin guide") })
"tr", ] })
{ ] })
className: "border-t", ] });
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2", children: et.title }),
/* @__PURE__ */ jsxRuntimeExports.jsx("td", {
className: "px-3 py-2 font-mono text-xs",
children: et.slug,
}),
],
},
et.id,
),
),
}),
],
}),
}),
],
})
: /* @__PURE__ */ jsxRuntimeExports.jsxs(Alert, {
variant: "destructive",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-4 w-4" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertDescription, {
className: "ml-2",
children: testResult.error || t("calcom.settings.test.failure", "Connection failed"),
}),
],
}),
}),
],
}),
],
}),
settings?.api_key_configured &&
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, {
className: "text-base flex items-center gap-2",
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Send, { className: "h-4 w-4" }), t("calcom.settings.webhook.title", "Webhook")],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, {
children: t("calcom.settings.webhook.description", "Configure this URL in Cal.com → Settings → Developer → Webhooks to receive booking events."),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, {
className: "space-y-4",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "space-y-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "webhookUrl", children: t("calcom.settings.webhook.urlLabel", "Webhook URL") }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex gap-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Input, {
id: "webhookUrl",
readOnly: true,
value: settings?.webhook_url || "",
className: "flex-1 font-mono text-xs",
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, {
"action": "copy",
"entity": "webhook-url",
"variant": "outline",
"size": "sm",
"aria-label": t("calcom.settings.webhook.copyUrl", "Copy webhook URL"),
"onClick": () => copyToClipboard(settings?.webhook_url || ""),
"children": /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "h-4 w-4" }),
}),
],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "space-y-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "webhookSecret", children: t("calcom.settings.webhook.secretLabel", "Webhook Secret") }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex gap-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "relative flex-1",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Input, {
id: "webhookSecret",
type: showSecret ? "text" : "password",
readOnly: true,
value: settings?.webhook_secret_masked || t("calcom.settings.webhook.secretNotConfigured", "Not configured"),
className: "pr-10 font-mono text-xs",
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, {
"type": "button",
"variant": "ghost",
"action": "toggle",
"entity": "webhook-secret-visibility",
"onClick": () => setShowSecret(!showSecret),
"aria-label": secretVisibilityLabel,
"className": "absolute right-3 top-1/2 -translate-y-1/2 h-auto p-0 text-muted-foreground hover:bg-transparent hover:text-foreground",
"children":
showSecret ?
/* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "h-4 w-4" })
: /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "h-4 w-4" }),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, {
"action": "copy",
"entity": "webhook-secret",
"variant": "outline",
"size": "sm",
"aria-label": t("calcom.settings.webhook.copySecret", "Copy webhook secret"),
"onClick": () => copyToClipboard(settings?.webhook_secret_masked || ""),
"children": /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "h-4 w-4" }),
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, {
"action": "rotate",
"entity": "webhook-secret",
"variant": "outline",
"size": "sm",
"aria-label": t("calcom.settings.webhook.rotate", "Rotate webhook secret"),
"onClick": handleRotate,
"disabled": rotating,
"children":
rotating ?
/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-4 w-4 animate-spin" })
: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "h-4 w-4" }),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", {
className: "text-xs text-muted-foreground",
children: t(
"calcom.settings.webhook.secretHelp",
"Cal.com displays the secret only once. Regenerating invalidates the previous value immediately — update it in Cal.com after rotating.",
),
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", {
className: "text-sm",
children:
settings?.last_event_at ?
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex items-center gap-2 text-foreground",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-4 w-4 text-success" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", {
children: [
t("calcom.settings.webhook.lastEventPrefix", "Last event"),
" ",
formatRelativeTime(settings.last_event_at),
settings.last_event_type &&
/* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
children: [
" — ",
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono text-xs", children: settings.last_event_type }),
],
}),
],
}),
],
})
: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex items-center gap-2 text-muted-foreground",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertCircle, { className: "h-4 w-4" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("calcom.settings.webhook.waitingForEvent", "Status: Waiting for first event") }),
],
}),
}),
],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.settings.docs.title", "Documentation") }),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.settings.docs.description", "Reference material for working with the Cal.com plugin.") }),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, {
className: "space-y-2 text-sm",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("a", {
href: "https://cal.com/docs/api-reference/v2",
target: "_blank",
rel: "noopener noreferrer",
className: "text-primary hover:underline block",
children: t("calcom.settings.docs.calcomApi", "Cal.com API documentation"),
}),
/* @__PURE__ */ jsxRuntimeExports.jsx("a", {
href: "/admin/docs/calcom-plugin",
target: "_blank",
rel: "noopener noreferrer",
className: "text-primary hover:underline block",
children: t("calcom.settings.docs.pluginGuide", "BlockNinja plugin guide"),
}),
],
}),
],
}),
],
});
} }
export { CalcomSettings as default }; export { CalcomSettings as default };

View File

@ -27,366 +27,399 @@ const xRange = `^${gtlt}\\s*${xRangePlain}$`;
const comparator = `^${gtlt}\\s*(${fullPlain})$|^$`; const comparator = `^${gtlt}\\s*(${fullPlain})$|^$`;
const gte0 = "^\\s*>=\\s*0.0.0\\s*$"; const gte0 = "^\\s*>=\\s*0.0.0\\s*$";
function parseRegex(source) { function parseRegex(source) {
return new RegExp(source); return new RegExp(source);
} }
function isXVersion(version) { function isXVersion(version) {
return !version || version.toLowerCase() === "x" || version === "*"; return !version || version.toLowerCase() === "x" || version === "*";
} }
function pipe(...fns) { function pipe(...fns) {
return (x) => { return (x) => {
return fns.reduce((v, f) => f(v), x); return fns.reduce((v, f) => f(v), x);
}; };
} }
function extractComparator(comparatorString) { function extractComparator(comparatorString) {
return comparatorString.match(parseRegex(comparator)); return comparatorString.match(parseRegex(comparator));
} }
function combineVersion(major, minor, patch, preRelease2) { function combineVersion(major, minor, patch, preRelease2) {
const mainVersion2 = `${major}.${minor}.${patch}`; const mainVersion2 = `${major}.${minor}.${patch}`;
if (preRelease2) { if (preRelease2) {
return `${mainVersion2}-${preRelease2}`; return `${mainVersion2}-${preRelease2}`;
} }
return mainVersion2; return mainVersion2;
} }
function parseHyphen(range) { function parseHyphen(range) {
return range.replace(parseRegex(hyphenRange), (_range, from, fromMajor, fromMinor, fromPatch, _fromPreRelease, _fromBuild, to, toMajor, toMinor, toPatch, toPreRelease) => { return range.replace(
if (isXVersion(fromMajor)) { parseRegex(hyphenRange),
from = ""; (_range, from, fromMajor, fromMinor, fromPatch, _fromPreRelease, _fromBuild, to, toMajor, toMinor, toPatch, toPreRelease) => {
} else if (isXVersion(fromMinor)) { if (isXVersion(fromMajor)) {
from = `>=${fromMajor}.0.0`; from = "";
} else if (isXVersion(fromPatch)) { } else if (isXVersion(fromMinor)) {
from = `>=${fromMajor}.${fromMinor}.0`; from = `>=${fromMajor}.0.0`;
} else { } else if (isXVersion(fromPatch)) {
from = `>=${from}`; from = `>=${fromMajor}.${fromMinor}.0`;
} } else {
if (isXVersion(toMajor)) { from = `>=${from}`;
to = ""; }
} else if (isXVersion(toMinor)) { if (isXVersion(toMajor)) {
to = `<${+toMajor + 1}.0.0-0`; to = "";
} else if (isXVersion(toPatch)) { } else if (isXVersion(toMinor)) {
to = `<${toMajor}.${+toMinor + 1}.0-0`; to = `<${+toMajor + 1}.0.0-0`;
} else if (toPreRelease) { } else if (isXVersion(toPatch)) {
to = `<=${toMajor}.${toMinor}.${toPatch}-${toPreRelease}`; to = `<${toMajor}.${+toMinor + 1}.0-0`;
} else { } else if (toPreRelease) {
to = `<=${to}`; to = `<=${toMajor}.${toMinor}.${toPatch}-${toPreRelease}`;
} } else {
return `${from} ${to}`.trim(); to = `<=${to}`;
}); }
return `${from} ${to}`.trim();
}
);
} }
function parseComparatorTrim(range) { function parseComparatorTrim(range) {
return range.replace(parseRegex(comparatorTrim), "$1$2$3"); return range.replace(parseRegex(comparatorTrim), "$1$2$3");
} }
function parseTildeTrim(range) { function parseTildeTrim(range) {
return range.replace(parseRegex(tildeTrim), "$1~"); return range.replace(parseRegex(tildeTrim), "$1~");
} }
function parseCaretTrim(range) { function parseCaretTrim(range) {
return range.replace(parseRegex(caretTrim), "$1^"); return range.replace(parseRegex(caretTrim), "$1^");
} }
function parseCarets(range) { function parseCarets(range) {
return range return range.trim().split(/\s+/).map((rangeVersion) => {
.trim() return rangeVersion.replace(
.split(/\s+/) parseRegex(caret),
.map((rangeVersion) => { (_, major, minor, patch, preRelease2) => {
return rangeVersion.replace(parseRegex(caret), (_, major, minor, patch, preRelease2) => { if (isXVersion(major)) {
if (isXVersion(major)) { return "";
return ""; } else if (isXVersion(minor)) {
} else if (isXVersion(minor)) { return `>=${major}.0.0 <${+major + 1}.0.0-0`;
return `>=${major}.0.0 <${+major + 1}.0.0-0`; } else if (isXVersion(patch)) {
} else if (isXVersion(patch)) { if (major === "0") {
if (major === "0") { return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0`;
return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0`; } else {
} else { return `>=${major}.${minor}.0 <${+major + 1}.0.0-0`;
return `>=${major}.${minor}.0 <${+major + 1}.0.0-0`; }
} } else if (preRelease2) {
} else if (preRelease2) { if (major === "0") {
if (major === "0") { if (minor === "0") {
if (minor === "0") { return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${minor}.${+patch + 1}-0`;
return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${minor}.${+patch + 1}-0`; } else {
} else { return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${+minor + 1}.0-0`;
return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${+minor + 1}.0-0`; }
} } else {
} else { return `>=${major}.${minor}.${patch}-${preRelease2} <${+major + 1}.0.0-0`;
return `>=${major}.${minor}.${patch}-${preRelease2} <${+major + 1}.0.0-0`; }
} } else {
} else { if (major === "0") {
if (major === "0") { if (minor === "0") {
if (minor === "0") { return `>=${major}.${minor}.${patch} <${major}.${minor}.${+patch + 1}-0`;
return `>=${major}.${minor}.${patch} <${major}.${minor}.${+patch + 1}-0`; } else {
} else { return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0`;
return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0`; }
} }
} return `>=${major}.${minor}.${patch} <${+major + 1}.0.0-0`;
return `>=${major}.${minor}.${patch} <${+major + 1}.0.0-0`; }
} }
}); );
}) }).join(" ");
.join(" ");
} }
function parseTildes(range) { function parseTildes(range) {
return range return range.trim().split(/\s+/).map((rangeVersion) => {
.trim() return rangeVersion.replace(
.split(/\s+/) parseRegex(tilde),
.map((rangeVersion) => { (_, major, minor, patch, preRelease2) => {
return rangeVersion.replace(parseRegex(tilde), (_, major, minor, patch, preRelease2) => { if (isXVersion(major)) {
if (isXVersion(major)) { return "";
return ""; } else if (isXVersion(minor)) {
} else if (isXVersion(minor)) { return `>=${major}.0.0 <${+major + 1}.0.0-0`;
return `>=${major}.0.0 <${+major + 1}.0.0-0`; } else if (isXVersion(patch)) {
} else if (isXVersion(patch)) { return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0`;
return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0`; } else if (preRelease2) {
} else if (preRelease2) { return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${+minor + 1}.0-0`;
return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${+minor + 1}.0-0`; }
} return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0`;
return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0`; }
}); );
}) }).join(" ");
.join(" ");
} }
function parseXRanges(range) { function parseXRanges(range) {
return range return range.split(/\s+/).map((rangeVersion) => {
.split(/\s+/) return rangeVersion.trim().replace(
.map((rangeVersion) => { parseRegex(xRange),
return rangeVersion.trim().replace(parseRegex(xRange), (ret, gtlt2, major, minor, patch, preRelease2) => { (ret, gtlt2, major, minor, patch, preRelease2) => {
const isXMajor = isXVersion(major); const isXMajor = isXVersion(major);
const isXMinor = isXMajor || isXVersion(minor); const isXMinor = isXMajor || isXVersion(minor);
const isXPatch = isXMinor || isXVersion(patch); const isXPatch = isXMinor || isXVersion(patch);
if (gtlt2 === "=" && isXPatch) { if (gtlt2 === "=" && isXPatch) {
gtlt2 = ""; gtlt2 = "";
} }
preRelease2 = ""; preRelease2 = "";
if (isXMajor) { if (isXMajor) {
if (gtlt2 === ">" || gtlt2 === "<") { if (gtlt2 === ">" || gtlt2 === "<") {
return "<0.0.0-0"; return "<0.0.0-0";
} else { } else {
return "*"; return "*";
} }
} else if (gtlt2 && isXPatch) { } else if (gtlt2 && isXPatch) {
if (isXMinor) { if (isXMinor) {
minor = 0; minor = 0;
} }
patch = 0; patch = 0;
if (gtlt2 === ">") { if (gtlt2 === ">") {
gtlt2 = ">="; gtlt2 = ">=";
if (isXMinor) { if (isXMinor) {
major = +major + 1; major = +major + 1;
minor = 0; minor = 0;
patch = 0; patch = 0;
} else { } else {
minor = +minor + 1; minor = +minor + 1;
patch = 0; patch = 0;
} }
} else if (gtlt2 === "<=") { } else if (gtlt2 === "<=") {
gtlt2 = "<"; gtlt2 = "<";
if (isXMinor) { if (isXMinor) {
major = +major + 1; major = +major + 1;
} else { } else {
minor = +minor + 1; minor = +minor + 1;
} }
} }
if (gtlt2 === "<") { if (gtlt2 === "<") {
preRelease2 = "-0"; preRelease2 = "-0";
} }
return `${gtlt2 + major}.${minor}.${patch}${preRelease2}`; return `${gtlt2 + major}.${minor}.${patch}${preRelease2}`;
} else if (isXMinor) { } else if (isXMinor) {
return `>=${major}.0.0${preRelease2} <${+major + 1}.0.0-0`; return `>=${major}.0.0${preRelease2} <${+major + 1}.0.0-0`;
} else if (isXPatch) { } else if (isXPatch) {
return `>=${major}.${minor}.0${preRelease2} <${major}.${+minor + 1}.0-0`; return `>=${major}.${minor}.0${preRelease2} <${major}.${+minor + 1}.0-0`;
} }
return ret; return ret;
}); }
}) );
.join(" "); }).join(" ");
} }
function parseStar(range) { function parseStar(range) {
return range.trim().replace(parseRegex(star), ""); return range.trim().replace(parseRegex(star), "");
} }
function parseGTE0(comparatorString) { function parseGTE0(comparatorString) {
return comparatorString.trim().replace(parseRegex(gte0), ""); return comparatorString.trim().replace(parseRegex(gte0), "");
} }
function compareAtom(rangeAtom, versionAtom) { function compareAtom(rangeAtom, versionAtom) {
rangeAtom = +rangeAtom || rangeAtom; rangeAtom = +rangeAtom || rangeAtom;
versionAtom = +versionAtom || versionAtom; versionAtom = +versionAtom || versionAtom;
if (rangeAtom > versionAtom) { if (rangeAtom > versionAtom) {
return 1; return 1;
} }
if (rangeAtom === versionAtom) { if (rangeAtom === versionAtom) {
return 0; return 0;
} }
return -1; return -1;
} }
function comparePreRelease(rangeAtom, versionAtom) { function comparePreRelease(rangeAtom, versionAtom) {
const { preRelease: rangePreRelease } = rangeAtom; const { preRelease: rangePreRelease } = rangeAtom;
const { preRelease: versionPreRelease } = versionAtom; const { preRelease: versionPreRelease } = versionAtom;
if (rangePreRelease === void 0 && !!versionPreRelease) { if (rangePreRelease === void 0 && !!versionPreRelease) {
return 1; return 1;
} }
if (!!rangePreRelease && versionPreRelease === void 0) { if (!!rangePreRelease && versionPreRelease === void 0) {
return -1; return -1;
} }
if (rangePreRelease === void 0 && versionPreRelease === void 0) { if (rangePreRelease === void 0 && versionPreRelease === void 0) {
return 0; return 0;
} }
for (let i = 0, n = rangePreRelease.length; i <= n; i++) { for (let i = 0, n = rangePreRelease.length; i <= n; i++) {
const rangeElement = rangePreRelease[i]; const rangeElement = rangePreRelease[i];
const versionElement = versionPreRelease[i]; const versionElement = versionPreRelease[i];
if (rangeElement === versionElement) { if (rangeElement === versionElement) {
continue; continue;
} }
if (rangeElement === void 0 && versionElement === void 0) { if (rangeElement === void 0 && versionElement === void 0) {
return 0; return 0;
} }
if (!rangeElement) { if (!rangeElement) {
return 1; return 1;
} }
if (!versionElement) { if (!versionElement) {
return -1; return -1;
} }
return compareAtom(rangeElement, versionElement); return compareAtom(rangeElement, versionElement);
} }
return 0; return 0;
} }
function compareVersion(rangeAtom, versionAtom) { function compareVersion(rangeAtom, versionAtom) {
return ( return compareAtom(rangeAtom.major, versionAtom.major) || compareAtom(rangeAtom.minor, versionAtom.minor) || compareAtom(rangeAtom.patch, versionAtom.patch) || comparePreRelease(rangeAtom, versionAtom);
compareAtom(rangeAtom.major, versionAtom.major) ||
compareAtom(rangeAtom.minor, versionAtom.minor) ||
compareAtom(rangeAtom.patch, versionAtom.patch) ||
comparePreRelease(rangeAtom, versionAtom)
);
} }
function eq(rangeAtom, versionAtom) { function eq(rangeAtom, versionAtom) {
return rangeAtom.version === versionAtom.version; return rangeAtom.version === versionAtom.version;
} }
function compare(rangeAtom, versionAtom) { function compare(rangeAtom, versionAtom) {
switch (rangeAtom.operator) { switch (rangeAtom.operator) {
case "": case "":
case "=": case "=":
return eq(rangeAtom, versionAtom); return eq(rangeAtom, versionAtom);
case ">": case ">":
return compareVersion(rangeAtom, versionAtom) < 0; return compareVersion(rangeAtom, versionAtom) < 0;
case ">=": case ">=":
return eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) < 0; return eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) < 0;
case "<": case "<":
return compareVersion(rangeAtom, versionAtom) > 0; return compareVersion(rangeAtom, versionAtom) > 0;
case "<=": case "<=":
return eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) > 0; return eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) > 0;
case void 0: { case void 0: {
return true; return true;
} }
default: default:
return false; return false;
} }
} }
function parseComparatorString(range) { function parseComparatorString(range) {
return pipe(parseCarets, parseTildes, parseXRanges, parseStar)(range); return pipe(
parseCarets,
parseTildes,
parseXRanges,
parseStar
)(range);
} }
function parseRange(range) { function parseRange(range) {
return pipe(parseHyphen, parseComparatorTrim, parseTildeTrim, parseCaretTrim)(range.trim()).split(/\s+/).join(" "); return pipe(
parseHyphen,
parseComparatorTrim,
parseTildeTrim,
parseCaretTrim
)(range.trim()).split(/\s+/).join(" ");
} }
function satisfy(version, range) { function satisfy(version, range) {
if (!version) { if (!version) {
return false; return false;
} }
const parsedRange = parseRange(range); const parsedRange = parseRange(range);
const parsedComparator = parsedRange const parsedComparator = parsedRange.split(" ").map((rangeVersion) => parseComparatorString(rangeVersion)).join(" ");
.split(" ") const comparators = parsedComparator.split(/\s+/).map((comparator2) => parseGTE0(comparator2));
.map((rangeVersion) => parseComparatorString(rangeVersion)) const extractedVersion = extractComparator(version);
.join(" "); if (!extractedVersion) {
const comparators = parsedComparator.split(/\s+/).map((comparator2) => parseGTE0(comparator2)); return false;
const extractedVersion = extractComparator(version); }
if (!extractedVersion) { const [
return false; ,
} versionOperator,
const [, versionOperator, , versionMajor, versionMinor, versionPatch, versionPreRelease] = extractedVersion; ,
const versionAtom = { versionMajor,
version: combineVersion(versionMajor, versionMinor, versionPatch, versionPreRelease), versionMinor,
major: versionMajor, versionPatch,
minor: versionMinor, versionPreRelease
patch: versionPatch, ] = extractedVersion;
preRelease: versionPreRelease == null ? void 0 : versionPreRelease.split("."), const versionAtom = {
}; version: combineVersion(
for (const comparator2 of comparators) { versionMajor,
const extractedComparator = extractComparator(comparator2); versionMinor,
if (!extractedComparator) { versionPatch,
return false; versionPreRelease
} ),
const [, rangeOperator, , rangeMajor, rangeMinor, rangePatch, rangePreRelease] = extractedComparator; major: versionMajor,
const rangeAtom = { minor: versionMinor,
operator: rangeOperator, patch: versionPatch,
version: combineVersion(rangeMajor, rangeMinor, rangePatch, rangePreRelease), preRelease: versionPreRelease == null ? void 0 : versionPreRelease.split(".")
major: rangeMajor, };
minor: rangeMinor, for (const comparator2 of comparators) {
patch: rangePatch, const extractedComparator = extractComparator(comparator2);
preRelease: rangePreRelease == null ? void 0 : rangePreRelease.split("."), if (!extractedComparator) {
}; return false;
if (!compare(rangeAtom, versionAtom)) { }
return false; const [
} ,
} rangeOperator,
return true; ,
rangeMajor,
rangeMinor,
rangePatch,
rangePreRelease
] = extractedComparator;
const rangeAtom = {
operator: rangeOperator,
version: combineVersion(
rangeMajor,
rangeMinor,
rangePatch,
rangePreRelease
),
major: rangeMajor,
minor: rangeMinor,
patch: rangePatch,
preRelease: rangePreRelease == null ? void 0 : rangePreRelease.split(".")
};
if (!compare(rangeAtom, versionAtom)) {
return false;
}
}
return true;
} }
const currentImports = {}; const currentImports = {};
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const moduleMap = { const moduleMap = {'react':{get:()=>()=>__federation_import(new URL('__federation_shared_react-DoKb58Ht.js', import.meta.url).href),import:true},'react-dom':{get:()=>()=>__federation_import(new URL('__federation_shared_react-dom-DU2-P0kt.js', import.meta.url).href),import:true},'@block-ninja/ui':{get:()=>()=>__federation_import(new URL('__federation_shared_@block-ninja/ui-C3CAr7wz.js', import.meta.url).href),import:true}};
"react": { get: () => () => __federation_import(new URL("__federation_shared_react-DoKb58Ht.js", import.meta.url).href), import: true },
"react-dom": { get: () => () => __federation_import(new URL("__federation_shared_react-dom-DU2-P0kt.js", import.meta.url).href), import: true },
"@block-ninja/ui": { get: () => () => __federation_import(new URL("__federation_shared_@block-ninja/ui-C3CAr7wz.js", import.meta.url).href), import: true },
};
const moduleCache = Object.create(null); const moduleCache = Object.create(null);
async function importShared(name, shareScope = "default") { async function importShared(name, shareScope = 'default') {
return moduleCache[name] ? new Promise((r) => r(moduleCache[name])) : (await getSharedFromRuntime(name, shareScope)) || getSharedFromLocal(name); return moduleCache[name]
? new Promise((r) => r(moduleCache[name]))
: (await getSharedFromRuntime(name, shareScope)) || getSharedFromLocal(name)
} }
// eslint-disable-next-line // eslint-disable-next-line
async function __federation_import(name) { async function __federation_import(name) {
currentImports[name] ??= import(name); currentImports[name] ??= import(name);
return currentImports[name]; return currentImports[name]
} }
async function getSharedFromRuntime(name, shareScope) { async function getSharedFromRuntime(name, shareScope) {
let module = null; let module = null;
if (globalThis?.__federation_shared__?.[shareScope]?.[name]) { if (globalThis?.__federation_shared__?.[shareScope]?.[name]) {
const versionObj = globalThis.__federation_shared__[shareScope][name]; const versionObj = globalThis.__federation_shared__[shareScope][name];
const requiredVersion = moduleMap[name]?.requiredVersion; const requiredVersion = moduleMap[name]?.requiredVersion;
const hasRequiredVersion = !!requiredVersion; const hasRequiredVersion = !!requiredVersion;
if (hasRequiredVersion) { if (hasRequiredVersion) {
const versionKey = Object.keys(versionObj).find((version) => satisfy(version, requiredVersion)); const versionKey = Object.keys(versionObj).find((version) =>
if (versionKey) { satisfy(version, requiredVersion)
const versionValue = versionObj[versionKey]; );
module = await (await versionValue.get())(); if (versionKey) {
} else { const versionValue = versionObj[versionKey];
console.log(`provider support ${name}(${versionKey}) is not satisfied requiredVersion(\${moduleMap[name].requiredVersion})`); module = await (await versionValue.get())();
} } else {
} else { console.log(
const versionKey = Object.keys(versionObj)[0]; `provider support ${name}(${versionKey}) is not satisfied requiredVersion(\${moduleMap[name].requiredVersion})`
const versionValue = versionObj[versionKey]; );
module = await (await versionValue.get())(); }
} } else {
} const versionKey = Object.keys(versionObj)[0];
if (module) { const versionValue = versionObj[versionKey];
return flattenModule(module, name); module = await (await versionValue.get())();
} }
}
if (module) {
return flattenModule(module, name)
}
} }
async function getSharedFromLocal(name) { async function getSharedFromLocal(name) {
if (moduleMap[name]?.import) { if (moduleMap[name]?.import) {
let module = await (await moduleMap[name].get())(); let module = await (await moduleMap[name].get())();
return flattenModule(module, name); return flattenModule(module, name)
} else { } else {
console.error(`consumer config import=false,so cant use callback shared module`); console.error(
} `consumer config import=false,so cant use callback shared module`
);
}
} }
function flattenModule(module, name) { function flattenModule(module, name) {
// use a shared module which export default a function will getting error 'TypeError: xxx is not a function' // use a shared module which export default a function will getting error 'TypeError: xxx is not a function'
if (typeof module.default === "function") { if (typeof module.default === 'function') {
Object.keys(module).forEach((key) => { Object.keys(module).forEach((key) => {
if (key !== "default") { if (key !== 'default') {
module.default[key] = module[key]; module.default[key] = module[key];
} }
}); });
moduleCache[name] = module.default; moduleCache[name] = module.default;
return module.default; return module.default
} }
if (module.default) module = Object.assign({}, module.default, module); if (module.default) module = Object.assign({}, module.default, module);
moduleCache[name] = module; moduleCache[name] = module;
return module; return module
} }
export { importShared, getSharedFromLocal as importSharedLocal, getSharedFromRuntime as importSharedRuntime }; export { importShared, getSharedFromLocal as importSharedLocal, getSharedFromRuntime as importSharedRuntime };

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
import { g as getDefaultExportFromCjs } from "./_commonjsHelpers-B85MJLTf.js"; import { g as getDefaultExportFromCjs } from './_commonjsHelpers-B85MJLTf.js';
import { r as requireReact } from "./index-DQGM2Mpm.js"; import { r as requireReact } from './index-DQGM2Mpm.js';
var reactExports = requireReact(); var reactExports = requireReact();
const index = /*@__PURE__*/ getDefaultExportFromCjs(reactExports); const index = /*@__PURE__*/getDefaultExportFromCjs(reactExports);
export { index as default }; export { index as default };

View File

@ -1,7 +1,7 @@
import { g as getDefaultExportFromCjs } from "./_commonjsHelpers-B85MJLTf.js"; import { g as getDefaultExportFromCjs } from './_commonjsHelpers-B85MJLTf.js';
import { r as requireReactDom } from "./index-eoEhLOdg.js"; import { r as requireReactDom } from './index-eoEhLOdg.js';
var reactDomExports = requireReactDom(); var reactDomExports = requireReactDom();
const index = /*@__PURE__*/ getDefaultExportFromCjs(reactDomExports); const index = /*@__PURE__*/getDefaultExportFromCjs(reactDomExports);
export { index as default }; export { index as default };

View File

@ -1,5 +1,5 @@
function getDefaultExportFromCjs(x) { function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x; return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
} }
export { getDefaultExportFromCjs as g }; export { getDefaultExportFromCjs as g };

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { importShared } from "./__federation_fn_import-hlt2XzeI.js"; import { importShared } from './__federation_fn_import-hlt2XzeI.js';
import { X as requireShim } from "./index-Bs--Ol2m.js"; import { X as requireShim } from './index-Bs--Ol2m.js';
/** /**
* @license lucide-react v0.468.0 - ISC * @license lucide-react v0.468.0 - ISC
@ -9,13 +9,9 @@ import { X as requireShim } from "./index-Bs--Ol2m.js";
*/ */
const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
const mergeClasses = (...classes) => const mergeClasses = (...classes) => classes.filter((className, index, array) => {
classes return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
.filter((className, index, array) => { }).join(" ").trim();
return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
})
.join(" ")
.trim();
/** /**
* @license lucide-react v0.468.0 - ISC * @license lucide-react v0.468.0 - ISC
@ -25,15 +21,15 @@ const mergeClasses = (...classes) =>
*/ */
var defaultAttributes = { var defaultAttributes = {
xmlns: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/svg",
width: 24, width: 24,
height: 24, height: 24,
viewBox: "0 0 24 24", viewBox: "0 0 24 24",
fill: "none", fill: "none",
stroke: "currentColor", stroke: "currentColor",
strokeWidth: 2, strokeWidth: 2,
strokeLinecap: "round", strokeLinecap: "round",
strokeLinejoin: "round", strokeLinejoin: "round"
}; };
/** /**
@ -43,24 +39,38 @@ var defaultAttributes = {
* See the LICENSE file in the root directory of this source tree. * See the LICENSE file in the root directory of this source tree.
*/ */
const { forwardRef: forwardRef$1, createElement: createElement$1 } = await importShared("react"); const {forwardRef: forwardRef$1,createElement: createElement$1} = await importShared('react');
const Icon = forwardRef$1(({ color = "currentColor", size = 24, strokeWidth = 2, absoluteStrokeWidth, className = "", children, iconNode, ...rest }, ref) => { const Icon = forwardRef$1(
return createElement$1( ({
"svg", color = "currentColor",
{ size = 24,
ref, strokeWidth = 2,
...defaultAttributes, absoluteStrokeWidth,
width: size, className = "",
height: size, children,
stroke: color, iconNode,
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth, ...rest
className: mergeClasses("lucide", className), }, ref) => {
...rest, return createElement$1(
}, "svg",
[...iconNode.map(([tag, attrs]) => createElement$1(tag, attrs)), ...(Array.isArray(children) ? children : [children])], {
); ref,
}); ...defaultAttributes,
width: size,
height: size,
stroke: color,
strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
className: mergeClasses("lucide", className),
...rest
},
[
...iconNode.map(([tag, attrs]) => createElement$1(tag, attrs)),
...Array.isArray(children) ? children : [children]
]
);
}
);
/** /**
* @license lucide-react v0.468.0 - ISC * @license lucide-react v0.468.0 - ISC
@ -69,19 +79,19 @@ const Icon = forwardRef$1(({ color = "currentColor", size = 24, strokeWidth = 2,
* See the LICENSE file in the root directory of this source tree. * See the LICENSE file in the root directory of this source tree.
*/ */
const { forwardRef, createElement } = await importShared("react"); const {forwardRef,createElement} = await importShared('react');
const createLucideIcon = (iconName, iconNode) => { const createLucideIcon = (iconName, iconNode) => {
const Component = forwardRef(({ className, ...props }, ref) => const Component = forwardRef(
createElement(Icon, { ({ className, ...props }, ref) => createElement(Icon, {
ref, ref,
iconNode, iconNode,
className: mergeClasses(`lucide-${toKebabCase(iconName)}`, className), className: mergeClasses(`lucide-${toKebabCase(iconName)}`, className),
...props, ...props
}), })
); );
Component.displayName = `${iconName}`; Component.displayName = `${iconName}`;
return Component; return Component;
}; };
/** /**
@ -91,13 +101,14 @@ const createLucideIcon = (iconName, iconNode) => {
* See the LICENSE file in the root directory of this source tree. * See the LICENSE file in the root directory of this source tree.
*/ */
const List = createLucideIcon("List", [ const List = createLucideIcon("List", [
["path", { d: "M3 12h.01", key: "nlz23k" }], ["path", { d: "M3 12h.01", key: "nlz23k" }],
["path", { d: "M3 18h.01", key: "1tta3j" }], ["path", { d: "M3 18h.01", key: "1tta3j" }],
["path", { d: "M3 6h.01", key: "1rqtza" }], ["path", { d: "M3 6h.01", key: "1rqtza" }],
["path", { d: "M8 12h13", key: "1za7za" }], ["path", { d: "M8 12h13", key: "1za7za" }],
["path", { d: "M8 18h13", key: "1lx6n3" }], ["path", { d: "M8 18h13", key: "1lx6n3" }],
["path", { d: "M8 6h13", key: "ik3vkj" }], ["path", { d: "M8 6h13", key: "ik3vkj" }]
]); ]);
var shimExports = requireShim(); var shimExports = requireShim();

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
var react = { exports: {} }; var react = {exports: {}};
var react_production_min = {}; var react_production_min = {};
@ -14,332 +14,36 @@ var react_production_min = {};
var hasRequiredReact_production_min; var hasRequiredReact_production_min;
function requireReact_production_min() { function requireReact_production_min () {
if (hasRequiredReact_production_min) return react_production_min; if (hasRequiredReact_production_min) return react_production_min;
hasRequiredReact_production_min = 1; hasRequiredReact_production_min = 1;
var l = Symbol.for("react.element"), var l=Symbol.for("react.element"),n=Symbol.for("react.portal"),p=Symbol.for("react.fragment"),q=Symbol.for("react.strict_mode"),r=Symbol.for("react.profiler"),t=Symbol.for("react.provider"),u=Symbol.for("react.context"),v=Symbol.for("react.forward_ref"),w=Symbol.for("react.suspense"),x=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),z=Symbol.iterator;function A(a){if(null===a||"object"!==typeof a)return null;a=z&&a[z]||a["@@iterator"];return "function"===typeof a?a:null}
n = Symbol.for("react.portal"), var B={isMounted:function(){return false},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},C=Object.assign,D={};function E(a,b,e){this.props=a;this.context=b;this.refs=D;this.updater=e||B;}E.prototype.isReactComponent={};
p = Symbol.for("react.fragment"), E.prototype.setState=function(a,b){if("object"!==typeof a&&"function"!==typeof a&&null!=a)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,a,b,"setState");};E.prototype.forceUpdate=function(a){this.updater.enqueueForceUpdate(this,a,"forceUpdate");};function F(){}F.prototype=E.prototype;function G(a,b,e){this.props=a;this.context=b;this.refs=D;this.updater=e||B;}var H=G.prototype=new F;
q = Symbol.for("react.strict_mode"), H.constructor=G;C(H,E.prototype);H.isPureReactComponent=true;var I=Array.isArray,J=Object.prototype.hasOwnProperty,K={current:null},L={key:true,ref:true,__self:true,__source:true};
r = Symbol.for("react.profiler"), function M(a,b,e){var d,c={},k=null,h=null;if(null!=b)for(d in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)J.call(b,d)&&!L.hasOwnProperty(d)&&(c[d]=b[d]);var g=arguments.length-2;if(1===g)c.children=e;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];c.children=f;}if(a&&a.defaultProps)for(d in g=a.defaultProps,g) void 0===c[d]&&(c[d]=g[d]);return {$$typeof:l,type:a,key:k,ref:h,props:c,_owner:K.current}}
t = Symbol.for("react.provider"), function N(a,b){return {$$typeof:l,type:a.type,key:b,ref:a.ref,props:a.props,_owner:a._owner}}function O(a){return "object"===typeof a&&null!==a&&a.$$typeof===l}function escape(a){var b={"=":"=0",":":"=2"};return "$"+a.replace(/[=:]/g,function(a){return b[a]})}var P=/\/+/g;function Q(a,b){return "object"===typeof a&&null!==a&&null!=a.key?escape(""+a.key):b.toString(36)}
u = Symbol.for("react.context"), function R(a,b,e,d,c){var k=typeof a;if("undefined"===k||"boolean"===k)a=null;var h=false;if(null===a)h=true;else switch(k){case "string":case "number":h=true;break;case "object":switch(a.$$typeof){case l:case n:h=true;}}if(h)return h=a,c=c(h),a=""===d?"."+Q(h,0):d,I(c)?(e="",null!=a&&(e=a.replace(P,"$&/")+"/"),R(c,b,e,"",function(a){return a})):null!=c&&(O(c)&&(c=N(c,e+(!c.key||h&&h.key===c.key?"":(""+c.key).replace(P,"$&/")+"/")+a)),b.push(c)),1;h=0;d=""===d?".":d+":";if(I(a))for(var g=0;g<a.length;g++){k=
v = Symbol.for("react.forward_ref"), a[g];var f=d+Q(k,g);h+=R(k,b,e,f,c);}else if(f=A(a),"function"===typeof f)for(a=f.call(a),g=0;!(k=a.next()).done;)k=k.value,f=d+Q(k,g++),h+=R(k,b,e,f,c);else if("object"===k)throw b=String(a),Error("Objects are not valid as a React child (found: "+("[object Object]"===b?"object with keys {"+Object.keys(a).join(", ")+"}":b)+"). If you meant to render a collection of children, use an array instead.");return h}
w = Symbol.for("react.suspense"), function S(a,b,e){if(null==a)return a;var d=[],c=0;R(a,d,"","",function(a){return b.call(e,a,c++)});return d}function T(a){if(-1===a._status){var b=a._result;b=b();b.then(function(b){if(0===a._status||-1===a._status)a._status=1,a._result=b;},function(b){if(0===a._status||-1===a._status)a._status=2,a._result=b;});-1===a._status&&(a._status=0,a._result=b);}if(1===a._status)return a._result.default;throw a._result;}
x = Symbol.for("react.memo"), var U={current:null},V={transition:null},W={ReactCurrentDispatcher:U,ReactCurrentBatchConfig:V,ReactCurrentOwner:K};function X(){throw Error("act(...) is not supported in production builds of React.");}
y = Symbol.for("react.lazy"), react_production_min.Children={map:S,forEach:function(a,b,e){S(a,function(){b.apply(this,arguments);},e);},count:function(a){var b=0;S(a,function(){b++;});return b},toArray:function(a){return S(a,function(a){return a})||[]},only:function(a){if(!O(a))throw Error("React.Children.only expected to receive a single React element child.");return a}};react_production_min.Component=E;react_production_min.Fragment=p;react_production_min.Profiler=r;react_production_min.PureComponent=G;react_production_min.StrictMode=q;react_production_min.Suspense=w;
z = Symbol.iterator; react_production_min.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=W;react_production_min.act=X;
function A(a) { react_production_min.cloneElement=function(a,b,e){if(null===a||void 0===a)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+a+".");var d=C({},a.props),c=a.key,k=a.ref,h=a._owner;if(null!=b){ void 0!==b.ref&&(k=b.ref,h=K.current);void 0!==b.key&&(c=""+b.key);if(a.type&&a.type.defaultProps)var g=a.type.defaultProps;for(f in b)J.call(b,f)&&!L.hasOwnProperty(f)&&(d[f]=void 0===b[f]&&void 0!==g?g[f]:b[f]);}var f=arguments.length-2;if(1===f)d.children=e;else if(1<f){g=Array(f);
if (null === a || "object" !== typeof a) return null; for(var m=0;m<f;m++)g[m]=arguments[m+2];d.children=g;}return {$$typeof:l,type:a.type,key:c,ref:k,props:d,_owner:h}};react_production_min.createContext=function(a){a={$$typeof:u,_currentValue:a,_currentValue2:a,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null};a.Provider={$$typeof:t,_context:a};return a.Consumer=a};react_production_min.createElement=M;react_production_min.createFactory=function(a){var b=M.bind(null,a);b.type=a;return b};react_production_min.createRef=function(){return {current:null}};
a = (z && a[z]) || a["@@iterator"]; react_production_min.forwardRef=function(a){return {$$typeof:v,render:a}};react_production_min.isValidElement=O;react_production_min.lazy=function(a){return {$$typeof:y,_payload:{_status:-1,_result:a},_init:T}};react_production_min.memo=function(a,b){return {$$typeof:x,type:a,compare:void 0===b?null:b}};react_production_min.startTransition=function(a){var b=V.transition;V.transition={};try{a();}finally{V.transition=b;}};react_production_min.unstable_act=X;react_production_min.useCallback=function(a,b){return U.current.useCallback(a,b)};react_production_min.useContext=function(a){return U.current.useContext(a)};
return "function" === typeof a ? a : null; react_production_min.useDebugValue=function(){};react_production_min.useDeferredValue=function(a){return U.current.useDeferredValue(a)};react_production_min.useEffect=function(a,b){return U.current.useEffect(a,b)};react_production_min.useId=function(){return U.current.useId()};react_production_min.useImperativeHandle=function(a,b,e){return U.current.useImperativeHandle(a,b,e)};react_production_min.useInsertionEffect=function(a,b){return U.current.useInsertionEffect(a,b)};react_production_min.useLayoutEffect=function(a,b){return U.current.useLayoutEffect(a,b)};
} react_production_min.useMemo=function(a,b){return U.current.useMemo(a,b)};react_production_min.useReducer=function(a,b,e){return U.current.useReducer(a,b,e)};react_production_min.useRef=function(a){return U.current.useRef(a)};react_production_min.useState=function(a){return U.current.useState(a)};react_production_min.useSyncExternalStore=function(a,b,e){return U.current.useSyncExternalStore(a,b,e)};react_production_min.useTransition=function(){return U.current.useTransition()};react_production_min.version="18.3.1";
var B = {
isMounted: function () {
return false;
},
enqueueForceUpdate: function () {},
enqueueReplaceState: function () {},
enqueueSetState: function () {},
},
C = Object.assign,
D = {};
function E(a, b, e) {
this.props = a;
this.context = b;
this.refs = D;
this.updater = e || B;
}
E.prototype.isReactComponent = {};
E.prototype.setState = function (a, b) {
if ("object" !== typeof a && "function" !== typeof a && null != a)
throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");
this.updater.enqueueSetState(this, a, b, "setState");
};
E.prototype.forceUpdate = function (a) {
this.updater.enqueueForceUpdate(this, a, "forceUpdate");
};
function F() {}
F.prototype = E.prototype;
function G(a, b, e) {
this.props = a;
this.context = b;
this.refs = D;
this.updater = e || B;
}
var H = (G.prototype = new F());
H.constructor = G;
C(H, E.prototype);
H.isPureReactComponent = true;
var I = Array.isArray,
J = Object.prototype.hasOwnProperty,
K = { current: null },
L = { key: true, ref: true, __self: true, __source: true };
function M(a, b, e) {
var d,
c = {},
k = null,
h = null;
if (null != b) for (d in (void 0 !== b.ref && (h = b.ref), void 0 !== b.key && (k = "" + b.key), b)) J.call(b, d) && !L.hasOwnProperty(d) && (c[d] = b[d]);
var g = arguments.length - 2;
if (1 === g) c.children = e;
else if (1 < g) {
for (var f = Array(g), m = 0; m < g; m++) f[m] = arguments[m + 2];
c.children = f;
}
if (a && a.defaultProps) for (d in ((g = a.defaultProps), g)) void 0 === c[d] && (c[d] = g[d]);
return { $$typeof: l, type: a, key: k, ref: h, props: c, _owner: K.current };
}
function N(a, b) {
return { $$typeof: l, type: a.type, key: b, ref: a.ref, props: a.props, _owner: a._owner };
}
function O(a) {
return "object" === typeof a && null !== a && a.$$typeof === l;
}
function escape(a) {
var b = { "=": "=0", ":": "=2" };
return (
"$" +
a.replace(/[=:]/g, function (a) {
return b[a];
})
);
}
var P = /\/+/g;
function Q(a, b) {
return "object" === typeof a && null !== a && null != a.key ? escape("" + a.key) : b.toString(36);
}
function R(a, b, e, d, c) {
var k = typeof a;
if ("undefined" === k || "boolean" === k) a = null;
var h = false;
if (null === a) h = true;
else
switch (k) {
case "string":
case "number":
h = true;
break;
case "object":
switch (a.$$typeof) {
case l:
case n:
h = true;
}
}
if (h)
return (
(h = a),
(c = c(h)),
(a = "" === d ? "." + Q(h, 0) : d),
I(c) ?
((e = ""),
null != a && (e = a.replace(P, "$&/") + "/"),
R(c, b, e, "", function (a) {
return a;
}))
: null != c && (O(c) && (c = N(c, e + (!c.key || (h && h.key === c.key) ? "" : ("" + c.key).replace(P, "$&/") + "/") + a)), b.push(c)),
1
);
h = 0;
d = "" === d ? "." : d + ":";
if (I(a))
for (var g = 0; g < a.length; g++) {
k = a[g];
var f = d + Q(k, g);
h += R(k, b, e, f, c);
}
else if (((f = A(a)), "function" === typeof f)) for (a = f.call(a), g = 0; !(k = a.next()).done; ) ((k = k.value), (f = d + Q(k, g++)), (h += R(k, b, e, f, c)));
else if ("object" === k)
throw (
(b = String(a)),
Error(
"Objects are not valid as a React child (found: " +
("[object Object]" === b ? "object with keys {" + Object.keys(a).join(", ") + "}" : b) +
"). If you meant to render a collection of children, use an array instead.",
)
);
return h;
}
function S(a, b, e) {
if (null == a) return a;
var d = [],
c = 0;
R(a, d, "", "", function (a) {
return b.call(e, a, c++);
});
return d;
}
function T(a) {
if (-1 === a._status) {
var b = a._result;
b = b();
b.then(
function (b) {
if (0 === a._status || -1 === a._status) ((a._status = 1), (a._result = b));
},
function (b) {
if (0 === a._status || -1 === a._status) ((a._status = 2), (a._result = b));
},
);
-1 === a._status && ((a._status = 0), (a._result = b));
}
if (1 === a._status) return a._result.default;
throw a._result;
}
var U = { current: null },
V = { transition: null },
W = { ReactCurrentDispatcher: U, ReactCurrentBatchConfig: V, ReactCurrentOwner: K };
function X() {
throw Error("act(...) is not supported in production builds of React.");
}
react_production_min.Children = {
map: S,
forEach: function (a, b, e) {
S(
a,
function () {
b.apply(this, arguments);
},
e,
);
},
count: function (a) {
var b = 0;
S(a, function () {
b++;
});
return b;
},
toArray: function (a) {
return (
S(a, function (a) {
return a;
}) || []
);
},
only: function (a) {
if (!O(a)) throw Error("React.Children.only expected to receive a single React element child.");
return a;
},
};
react_production_min.Component = E;
react_production_min.Fragment = p;
react_production_min.Profiler = r;
react_production_min.PureComponent = G;
react_production_min.StrictMode = q;
react_production_min.Suspense = w;
react_production_min.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = W;
react_production_min.act = X;
react_production_min.cloneElement = function (a, b, e) {
if (null === a || void 0 === a) throw Error("React.cloneElement(...): The argument must be a React element, but you passed " + a + ".");
var d = C({}, a.props),
c = a.key,
k = a.ref,
h = a._owner;
if (null != b) {
void 0 !== b.ref && ((k = b.ref), (h = K.current));
void 0 !== b.key && (c = "" + b.key);
if (a.type && a.type.defaultProps) var g = a.type.defaultProps;
for (f in b) J.call(b, f) && !L.hasOwnProperty(f) && (d[f] = void 0 === b[f] && void 0 !== g ? g[f] : b[f]);
}
var f = arguments.length - 2;
if (1 === f) d.children = e;
else if (1 < f) {
g = Array(f);
for (var m = 0; m < f; m++) g[m] = arguments[m + 2];
d.children = g;
}
return { $$typeof: l, type: a.type, key: c, ref: k, props: d, _owner: h };
};
react_production_min.createContext = function (a) {
a = { $$typeof: u, _currentValue: a, _currentValue2: a, _threadCount: 0, Provider: null, Consumer: null, _defaultValue: null, _globalName: null };
a.Provider = { $$typeof: t, _context: a };
return (a.Consumer = a);
};
react_production_min.createElement = M;
react_production_min.createFactory = function (a) {
var b = M.bind(null, a);
b.type = a;
return b;
};
react_production_min.createRef = function () {
return { current: null };
};
react_production_min.forwardRef = function (a) {
return { $$typeof: v, render: a };
};
react_production_min.isValidElement = O;
react_production_min.lazy = function (a) {
return { $$typeof: y, _payload: { _status: -1, _result: a }, _init: T };
};
react_production_min.memo = function (a, b) {
return { $$typeof: x, type: a, compare: void 0 === b ? null : b };
};
react_production_min.startTransition = function (a) {
var b = V.transition;
V.transition = {};
try {
a();
} finally {
V.transition = b;
}
};
react_production_min.unstable_act = X;
react_production_min.useCallback = function (a, b) {
return U.current.useCallback(a, b);
};
react_production_min.useContext = function (a) {
return U.current.useContext(a);
};
react_production_min.useDebugValue = function () {};
react_production_min.useDeferredValue = function (a) {
return U.current.useDeferredValue(a);
};
react_production_min.useEffect = function (a, b) {
return U.current.useEffect(a, b);
};
react_production_min.useId = function () {
return U.current.useId();
};
react_production_min.useImperativeHandle = function (a, b, e) {
return U.current.useImperativeHandle(a, b, e);
};
react_production_min.useInsertionEffect = function (a, b) {
return U.current.useInsertionEffect(a, b);
};
react_production_min.useLayoutEffect = function (a, b) {
return U.current.useLayoutEffect(a, b);
};
react_production_min.useMemo = function (a, b) {
return U.current.useMemo(a, b);
};
react_production_min.useReducer = function (a, b, e) {
return U.current.useReducer(a, b, e);
};
react_production_min.useRef = function (a) {
return U.current.useRef(a);
};
react_production_min.useState = function (a) {
return U.current.useState(a);
};
react_production_min.useSyncExternalStore = function (a, b, e) {
return U.current.useSyncExternalStore(a, b, e);
};
react_production_min.useTransition = function () {
return U.current.useTransition();
};
react_production_min.version = "18.3.1";
return react_production_min; return react_production_min;
} }
var hasRequiredReact; var hasRequiredReact;
function requireReact() { function requireReact () {
if (hasRequiredReact) return react.exports; if (hasRequiredReact) return react.exports;
hasRequiredReact = 1; hasRequiredReact = 1;
{ {
react.exports = requireReact_production_min(); react.exports = requireReact_production_min();
} }
return react.exports; return react.exports;
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
import { r as requireReact } from "./index-DQGM2Mpm.js"; import { r as requireReact } from './index-DQGM2Mpm.js';
var jsxRuntime = { exports: {} }; var jsxRuntime = {exports: {}};
var reactJsxRuntime_production_min = {}; var reactJsxRuntime_production_min = {};
@ -16,40 +16,21 @@ var reactJsxRuntime_production_min = {};
var hasRequiredReactJsxRuntime_production_min; var hasRequiredReactJsxRuntime_production_min;
function requireReactJsxRuntime_production_min() { function requireReactJsxRuntime_production_min () {
if (hasRequiredReactJsxRuntime_production_min) return reactJsxRuntime_production_min; if (hasRequiredReactJsxRuntime_production_min) return reactJsxRuntime_production_min;
hasRequiredReactJsxRuntime_production_min = 1; hasRequiredReactJsxRuntime_production_min = 1;
var f = requireReact(), var f=requireReact(),k=Symbol.for("react.element"),l=Symbol.for("react.fragment"),m=Object.prototype.hasOwnProperty,n=f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,p={key:true,ref:true,__self:true,__source:true};
k = Symbol.for("react.element"), function q(c,a,g){var b,d={},e=null,h=null;void 0!==g&&(e=""+g);void 0!==a.key&&(e=""+a.key);void 0!==a.ref&&(h=a.ref);for(b in a)m.call(a,b)&&!p.hasOwnProperty(b)&&(d[b]=a[b]);if(c&&c.defaultProps)for(b in a=c.defaultProps,a) void 0===d[b]&&(d[b]=a[b]);return {$$typeof:k,type:c,key:e,ref:h,props:d,_owner:n.current}}reactJsxRuntime_production_min.Fragment=l;reactJsxRuntime_production_min.jsx=q;reactJsxRuntime_production_min.jsxs=q;
l = Symbol.for("react.fragment"),
m = Object.prototype.hasOwnProperty,
n = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,
p = { key: true, ref: true, __self: true, __source: true };
function q(c, a, g) {
var b,
d = {},
e = null,
h = null;
void 0 !== g && (e = "" + g);
void 0 !== a.key && (e = "" + a.key);
void 0 !== a.ref && (h = a.ref);
for (b in a) m.call(a, b) && !p.hasOwnProperty(b) && (d[b] = a[b]);
if (c && c.defaultProps) for (b in ((a = c.defaultProps), a)) void 0 === d[b] && (d[b] = a[b]);
return { $$typeof: k, type: c, key: e, ref: h, props: d, _owner: n.current };
}
reactJsxRuntime_production_min.Fragment = l;
reactJsxRuntime_production_min.jsx = q;
reactJsxRuntime_production_min.jsxs = q;
return reactJsxRuntime_production_min; return reactJsxRuntime_production_min;
} }
var hasRequiredJsxRuntime; var hasRequiredJsxRuntime;
function requireJsxRuntime() { function requireJsxRuntime () {
if (hasRequiredJsxRuntime) return jsxRuntime.exports; if (hasRequiredJsxRuntime) return jsxRuntime.exports;
hasRequiredJsxRuntime = 1; hasRequiredJsxRuntime = 1;
{ {
jsxRuntime.exports = requireReactJsxRuntime_production_min(); jsxRuntime.exports = requireReactJsxRuntime_production_min();
} }
return jsxRuntime.exports; return jsxRuntime.exports;
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,88 +1,84 @@
const currentImports = {}; const currentImports = {};
const exportSet = new Set(["Module", "__esModule", "default", "_export_sfc"]); const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']);
let moduleMap = { let moduleMap = {
"./editor": () => { "./editor":()=>{
dynamicLoadingCss(["style-Bs2zy9jQ.css"], false, "./editor"); dynamicLoadingCss(["style-Bs2zy9jQ.css"], false, './editor');
return __federation_import("./__federation_expose_Editor-BDCHVRx7.js").then((module) => (Object.keys(module).every((item) => exportSet.has(item)) ? () => module.default : () => module)); return __federation_import('./__federation_expose_Editor-DbTTOPh9.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
}, "./settings":()=>{
"./settings": () => { dynamicLoadingCss(["style-Bs2zy9jQ.css"], false, './settings');
dynamicLoadingCss(["style-Bs2zy9jQ.css"], false, "./settings"); return __federation_import('./__federation_expose_Settings-C7jFM1o4.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},};
return __federation_import("./__federation_expose_Settings-C7jFM1o4.js").then((module) => (Object.keys(module).every((item) => exportSet.has(item)) ? () => module.default : () => module)); const seen = {};
}, const dynamicLoadingCss = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => {
}; const metaUrl = import.meta.url;
const seen = {}; if (typeof metaUrl === 'undefined') {
const dynamicLoadingCss = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => { console.warn('The remote style takes effect only when the build.target option in the vite.config.ts file is higher than that of "es2020".');
const metaUrl = import.meta.url; return;
if (typeof metaUrl === "undefined") { }
console.warn('The remote style takes effect only when the build.target option in the vite.config.ts file is higher than that of "es2020".');
return;
}
const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf("remoteEntry.js")); const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('remoteEntry.js'));
const base = "/"; const base = '/';
("assets"); 'assets';
cssFilePaths.forEach((cssPath) => { cssFilePaths.forEach(cssPath => {
let href = ""; let href = '';
const baseUrl = base || curUrl; const baseUrl = base || curUrl;
if (baseUrl) { if (baseUrl) {
const trimmer = { const trimmer = {
trailing: (path) => (path.endsWith("/") ? path.slice(0, -1) : path), trailing: (path) => (path.endsWith('/') ? path.slice(0, -1) : path),
leading: (path) => (path.startsWith("/") ? path.slice(1) : path), leading: (path) => (path.startsWith('/') ? path.slice(1) : path)
}; };
const isAbsoluteUrl = (url) => url.startsWith("http") || url.startsWith("//"); const isAbsoluteUrl = (url) => url.startsWith('http') || url.startsWith('//');
const cleanBaseUrl = trimmer.trailing(baseUrl); const cleanBaseUrl = trimmer.trailing(baseUrl);
const cleanCssPath = trimmer.leading(cssPath); const cleanCssPath = trimmer.leading(cssPath);
const cleanCurUrl = trimmer.trailing(curUrl); const cleanCurUrl = trimmer.trailing(curUrl);
if (isAbsoluteUrl(baseUrl)) { if (isAbsoluteUrl(baseUrl)) {
href = [cleanBaseUrl, cleanCssPath].filter(Boolean).join("/"); href = [cleanBaseUrl, cleanCssPath].filter(Boolean).join('/');
} else { } else {
if (cleanCurUrl.includes(cleanBaseUrl)) { if (cleanCurUrl.includes(cleanBaseUrl)) {
href = [cleanCurUrl, cleanCssPath].filter(Boolean).join("/"); href = [cleanCurUrl, cleanCssPath].filter(Boolean).join('/');
} else { } else {
href = [cleanCurUrl + cleanBaseUrl, cleanCssPath].filter(Boolean).join("/"); href = [cleanCurUrl + cleanBaseUrl, cleanCssPath].filter(Boolean).join('/');
} }
} }
} else { } else {
href = cssPath; href = cssPath;
} }
if (dontAppendStylesToHead) { if (dontAppendStylesToHead) {
const key = "css__calcomblock__" + exposeItemName; const key = 'css__calcomblock__' + exposeItemName;
window[key] = window[key] || []; window[key] = window[key] || [];
window[key].push(href); window[key].push(href);
return; return;
} }
if (href in seen) return; if (href in seen) return;
seen[href] = true; seen[href] = true;
const element = document.createElement("link"); const element = document.createElement('link');
element.rel = "stylesheet"; element.rel = 'stylesheet';
element.href = href; element.href = href;
document.head.appendChild(element); document.head.appendChild(element);
}); });
}; };
async function __federation_import(name) { async function __federation_import(name) {
currentImports[name] ??= import(name); currentImports[name] ??= import(name);
return currentImports[name]; return currentImports[name]
} } const get =(module) => {
const get = (module) => { if(!moduleMap[module]) throw new Error('Can not find remote module ' + module)
if (!moduleMap[module]) throw new Error("Can not find remote module " + module); return moduleMap[module]();
return moduleMap[module](); };
}; const init =(shareScope) => {
const init = (shareScope) => { globalThis.__federation_shared__= globalThis.__federation_shared__|| {};
globalThis.__federation_shared__ = globalThis.__federation_shared__ || {}; Object.entries(shareScope).forEach(([key, value]) => {
Object.entries(shareScope).forEach(([key, value]) => { for (const [versionKey, versionValue] of Object.entries(value)) {
for (const [versionKey, versionValue] of Object.entries(value)) { const scope = versionValue.scope || 'default';
const scope = versionValue.scope || "default"; globalThis.__federation_shared__[scope] = globalThis.__federation_shared__[scope] || {};
globalThis.__federation_shared__[scope] = globalThis.__federation_shared__[scope] || {}; const shared= globalThis.__federation_shared__[scope];
const shared = globalThis.__federation_shared__[scope]; (shared[key] = shared[key]||{})[versionKey] = versionValue;
(shared[key] = shared[key] || {})[versionKey] = versionValue; }
} });
}); };
};
export { dynamicLoadingCss, get, init }; export { dynamicLoadingCss, get, init };

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
import { r as requireReact } from "./index-DQGM2Mpm.js"; import { r as requireReact } from './index-DQGM2Mpm.js';
import { X as requireShim } from "./index-Bs--Ol2m.js"; import { X as requireShim } from './index-Bs--Ol2m.js';
var withSelector = { exports: {} }; var withSelector = {exports: {}};
var withSelector_production = {}; var withSelector_production = {};
@ -17,84 +17,93 @@ var withSelector_production = {};
var hasRequiredWithSelector_production; var hasRequiredWithSelector_production;
function requireWithSelector_production() { function requireWithSelector_production () {
if (hasRequiredWithSelector_production) return withSelector_production; if (hasRequiredWithSelector_production) return withSelector_production;
hasRequiredWithSelector_production = 1; hasRequiredWithSelector_production = 1;
var React = requireReact(), var React = requireReact(),
shim = requireShim(); shim = requireShim();
function is(x, y) { function is(x, y) {
return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y); return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);
} }
var objectIs = "function" === typeof Object.is ? Object.is : is, var objectIs = "function" === typeof Object.is ? Object.is : is,
useSyncExternalStore = shim.useSyncExternalStore, useSyncExternalStore = shim.useSyncExternalStore,
useRef = React.useRef, useRef = React.useRef,
useEffect = React.useEffect, useEffect = React.useEffect,
useMemo = React.useMemo, useMemo = React.useMemo,
useDebugValue = React.useDebugValue; useDebugValue = React.useDebugValue;
withSelector_production.useSyncExternalStoreWithSelector = function (subscribe, getSnapshot, getServerSnapshot, selector, isEqual) { withSelector_production.useSyncExternalStoreWithSelector = function (
var instRef = useRef(null); subscribe,
if (null === instRef.current) { getSnapshot,
var inst = { hasValue: false, value: null }; getServerSnapshot,
instRef.current = inst; selector,
} else inst = instRef.current; isEqual
instRef = useMemo( ) {
function () { var instRef = useRef(null);
function memoizedSelector(nextSnapshot) { if (null === instRef.current) {
if (!hasMemo) { var inst = { hasValue: false, value: null };
hasMemo = true; instRef.current = inst;
memoizedSnapshot = nextSnapshot; } else inst = instRef.current;
nextSnapshot = selector(nextSnapshot); instRef = useMemo(
if (void 0 !== isEqual && inst.hasValue) { function () {
var currentSelection = inst.value; function memoizedSelector(nextSnapshot) {
if (isEqual(currentSelection, nextSnapshot)) return (memoizedSelection = currentSelection); if (!hasMemo) {
} hasMemo = true;
return (memoizedSelection = nextSnapshot); memoizedSnapshot = nextSnapshot;
} nextSnapshot = selector(nextSnapshot);
currentSelection = memoizedSelection; if (void 0 !== isEqual && inst.hasValue) {
if (objectIs(memoizedSnapshot, nextSnapshot)) return currentSelection; var currentSelection = inst.value;
var nextSelection = selector(nextSnapshot); if (isEqual(currentSelection, nextSnapshot))
if (void 0 !== isEqual && isEqual(currentSelection, nextSelection)) return ((memoizedSnapshot = nextSnapshot), currentSelection); return (memoizedSelection = currentSelection);
memoizedSnapshot = nextSnapshot; }
return (memoizedSelection = nextSelection); return (memoizedSelection = nextSnapshot);
} }
var hasMemo = false, currentSelection = memoizedSelection;
memoizedSnapshot, if (objectIs(memoizedSnapshot, nextSnapshot)) return currentSelection;
memoizedSelection, var nextSelection = selector(nextSnapshot);
maybeGetServerSnapshot = void 0 === getServerSnapshot ? null : getServerSnapshot; if (void 0 !== isEqual && isEqual(currentSelection, nextSelection))
return [ return (memoizedSnapshot = nextSnapshot), currentSelection;
function () { memoizedSnapshot = nextSnapshot;
return memoizedSelector(getSnapshot()); return (memoizedSelection = nextSelection);
}, }
null === maybeGetServerSnapshot ? void 0 : ( var hasMemo = false,
function () { memoizedSnapshot,
return memoizedSelector(maybeGetServerSnapshot()); memoizedSelection,
} maybeGetServerSnapshot =
), void 0 === getServerSnapshot ? null : getServerSnapshot;
]; return [
}, function () {
[getSnapshot, getServerSnapshot, selector, isEqual], return memoizedSelector(getSnapshot());
); },
var value = useSyncExternalStore(subscribe, instRef[0], instRef[1]); null === maybeGetServerSnapshot
useEffect( ? void 0
function () { : function () {
inst.hasValue = true; return memoizedSelector(maybeGetServerSnapshot());
inst.value = value; }
}, ];
[value], },
); [getSnapshot, getServerSnapshot, selector, isEqual]
useDebugValue(value); );
return value; var value = useSyncExternalStore(subscribe, instRef[0], instRef[1]);
useEffect(
function () {
inst.hasValue = true;
inst.value = value;
},
[value]
);
useDebugValue(value);
return value;
}; };
return withSelector_production; return withSelector_production;
} }
var hasRequiredWithSelector; var hasRequiredWithSelector;
function requireWithSelector() { function requireWithSelector () {
if (hasRequiredWithSelector) return withSelector.exports; if (hasRequiredWithSelector) return withSelector.exports;
hasRequiredWithSelector = 1; hasRequiredWithSelector = 1;
{ {
withSelector.exports = requireWithSelector_production(); withSelector.exports = requireWithSelector_production();
} }
return withSelector.exports; return withSelector.exports;
} }

2
web/dist/index.html vendored
View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<title>CalcomBlock Plugin</title> <title>CalcomBlock Plugin</title>
<link rel="stylesheet" crossorigin href="/assets/style-Bs2zy9jQ.css" /> <link rel="stylesheet" crossorigin href="/assets/style-Bs2zy9jQ.css">
</head> </head>
<body> <body>
<!-- This is a placeholder for Module Federation remote entry --> <!-- This is a placeholder for Module Federation remote entry -->

View File

@ -318,6 +318,20 @@ function CalcomBlockEditor({ content, onChange }: BlockEditorProps) {
aria-label={t("calcom.editor.showTimezone.label", "Show Timezone Selector")} aria-label={t("calcom.editor.showTimezone.label", "Show Timezone Selector")}
/> />
</div> </div>
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label>{t("calcom.editor.captchaEnabled.label", "Enable captcha")}</Label>
<p className="text-xs text-muted-foreground">
{t("calcom.editor.captchaEnabled.help", "Require visitors to solve a privacy-friendly proof-of-work captcha before booking (spam protection).")}
</p>
</div>
<Switch
checked={getBool("captchaEnabled", false)}
onCheckedChange={(v) => handleFieldChange("captchaEnabled", v)}
aria-label={t("calcom.editor.captchaEnabled.label", "Enable captcha")}
/>
</div>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -18,5 +18,10 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite": "^6.4.1" "vite": "^6.4.1"
},
"pnpm": {
"onlyBuiltDependencies": [
"esbuild"
]
} }
} }