@scalemule/nextjs 0.0.41 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1560,6 +1560,52 @@ var AuthService = class extends ServiceModule {
1560
1560
  async me(options) {
1561
1561
  return this._get("/me", options);
1562
1562
  }
1563
+ // --------------------------------------------------------------------------
1564
+ // User directory (customer-scoped)
1565
+ // --------------------------------------------------------------------------
1566
+ //
1567
+ // Search / fetch users within the caller's application. These endpoints are
1568
+ // scoped by the gateway-injected x-app-id header, so when invoked with a
1569
+ // customer API key + user session they will only return users belonging to
1570
+ // the caller's application. They replace the prior pattern of customer apps
1571
+ // reaching for platform admin credentials to hit admin-only user routes.
1572
+ //
1573
+ // DO NOT call these with platform admin credentials from customer-facing
1574
+ // applications. Use the standard customer auth path (API key + user session)
1575
+ // and let the gateway inject x-app-id on your behalf.
1576
+ /**
1577
+ * Search users within the caller's application.
1578
+ *
1579
+ * Results are automatically scoped to the caller's application via the
1580
+ * x-app-id header injected by the gateway. Server-side page size is fixed
1581
+ * at 50 (the `per_page` query param is not honored upstream).
1582
+ *
1583
+ * @example
1584
+ * const res = await sm.auth.searchUsers({ search: 'alice' });
1585
+ * res.data?.users.forEach(u => console.log(u.email));
1586
+ */
1587
+ async searchUsers(params, options) {
1588
+ const query = {};
1589
+ if (params?.search !== void 0) query.search = params.search;
1590
+ if (params?.status !== void 0) query.status = params.status;
1591
+ if (params?.email_verified !== void 0) {
1592
+ query.email_verified = params.email_verified ? "true" : "false";
1593
+ }
1594
+ if (params?.phone_verified !== void 0) {
1595
+ query.phone_verified = params.phone_verified ? "true" : "false";
1596
+ }
1597
+ if (params?.page !== void 0) query.page = params.page;
1598
+ return this._get(this.withQuery("/users", query), options);
1599
+ }
1600
+ /**
1601
+ * Fetch a single user by ID within the caller's application.
1602
+ *
1603
+ * Returns 404 if the user is not in the caller's application — cross-tenant
1604
+ * reads are blocked at the gateway via the x-app-id header scope.
1605
+ */
1606
+ async getUser(userId, options) {
1607
+ return this._get(`/users/${encodeURIComponent(userId)}`, options);
1608
+ }
1563
1609
  /** Refresh the session. Alias: refreshToken() */
1564
1610
  async refreshSession(data, options) {
1565
1611
  return this.post("/refresh", data ?? {}, options);
@@ -2745,7 +2791,10 @@ var StorageService = class extends ServiceModule {
2745
2791
  const stallTimeout = DEFAULT_STALL_TIMEOUT_MS;
2746
2792
  const controller = new AbortController();
2747
2793
  let parentSignalCleanup;
2748
- const combinedSignal = signal ? AbortSignal.any?.([signal, controller.signal]) ?? (() => {
2794
+ const combinedSignal = signal ? AbortSignal.any?.([
2795
+ signal,
2796
+ controller.signal
2797
+ ]) ?? (() => {
2749
2798
  const onAbort = () => controller.abort();
2750
2799
  signal.addEventListener("abort", onAbort, { once: true });
2751
2800
  parentSignalCleanup = () => signal.removeEventListener("abort", onAbort);
@@ -2926,7 +2975,10 @@ var StorageService = class extends ServiceModule {
2926
2975
  }
2927
2976
  const controller = new AbortController();
2928
2977
  let partSignalCleanup;
2929
- const combinedSignal = signal ? AbortSignal.any?.([signal, controller.signal]) ?? (() => {
2978
+ const combinedSignal = signal ? AbortSignal.any?.([
2979
+ signal,
2980
+ controller.signal
2981
+ ]) ?? (() => {
2930
2982
  const onAbort = () => controller.abort();
2931
2983
  signal.addEventListener("abort", onAbort, { once: true });
2932
2984
  partSignalCleanup = () => signal.removeEventListener("abort", onAbort);
@@ -3090,6 +3142,7 @@ var RealtimeService = class extends ServiceModule {
3090
3142
  super(...arguments);
3091
3143
  this.basePath = "/v1/realtime";
3092
3144
  this.ws = null;
3145
+ this.usedTicketAuth = false;
3093
3146
  this.subscriptions = /* @__PURE__ */ new Map();
3094
3147
  this.presenceCallbacks = /* @__PURE__ */ new Map();
3095
3148
  this.statusCallbacks = /* @__PURE__ */ new Set();
@@ -3218,10 +3271,31 @@ var RealtimeService = class extends ServiceModule {
3218
3271
  // --------------------------------------------------------------------------
3219
3272
  connect() {
3220
3273
  if (this._status === "connecting" || this._status === "connected") return;
3221
- const baseUrl = this.client.getBaseUrl();
3222
- const wsUrl = baseUrl.replace(/^http/, "ws") + "/v1/realtime/ws";
3223
3274
  this.setStatus(this.reconnectAttempt > 0 ? "reconnecting" : "connecting");
3275
+ this.fetchTicketAndConnect();
3276
+ }
3277
+ async fetchTicketAndConnect() {
3278
+ const baseUrl = this.client.getBaseUrl();
3224
3279
  try {
3280
+ const headers = { "Content-Type": "application/json" };
3281
+ const apiKey = this.client.getApiKey();
3282
+ if (apiKey) headers["x-api-key"] = apiKey;
3283
+ const token = this.client.getSessionToken();
3284
+ if (token) headers["Authorization"] = `Bearer ${token}`;
3285
+ const ticketRes = await fetch(`${baseUrl}/v1/realtime/ws/ticket`, {
3286
+ method: "POST",
3287
+ headers
3288
+ });
3289
+ let wsUrl;
3290
+ if (ticketRes.ok) {
3291
+ const ticketData = await ticketRes.json();
3292
+ const ticket = ticketData.ticket;
3293
+ wsUrl = baseUrl.replace(/^http/, "ws") + `/v1/realtime/ws?ticket=${encodeURIComponent(ticket)}`;
3294
+ this.usedTicketAuth = true;
3295
+ } else {
3296
+ wsUrl = baseUrl.replace(/^http/, "ws") + "/v1/realtime/ws";
3297
+ this.usedTicketAuth = false;
3298
+ }
3225
3299
  this.ws = new WebSocket(wsUrl);
3226
3300
  } catch {
3227
3301
  this.scheduleReconnect();
@@ -3229,8 +3303,21 @@ var RealtimeService = class extends ServiceModule {
3229
3303
  }
3230
3304
  this.ws.onopen = () => {
3231
3305
  this.reconnectAttempt = 0;
3232
- this.authenticate();
3306
+ if (!this.usedTicketAuth) {
3307
+ this.authenticate();
3308
+ }
3233
3309
  this.startHeartbeat();
3310
+ if (this.usedTicketAuth) {
3311
+ setTimeout(() => {
3312
+ if (!this.authenticated) {
3313
+ this.authenticated = true;
3314
+ this.setStatus("connected");
3315
+ for (const channel of this.subscriptions.keys()) {
3316
+ this.sendWs({ type: "subscribe", channel });
3317
+ }
3318
+ }
3319
+ }, 2e3);
3320
+ }
3234
3321
  };
3235
3322
  this.ws.onmessage = (event) => {
3236
3323
  try {
@@ -3682,6 +3769,9 @@ var ChatService = class extends ServiceModule {
3682
3769
  async sendMessage(conversationId, data, options) {
3683
3770
  return this.post(`/conversations/${conversationId}/messages`, data, options);
3684
3771
  }
3772
+ async getThreadReplies(messageId, params, requestOptions) {
3773
+ return this._get(this.withQuery(`/messages/${messageId}/replies`, params), requestOptions);
3774
+ }
3685
3775
  async getMessages(conversationId, options, requestOptions) {
3686
3776
  return this._get(
3687
3777
  this.withQuery(`/conversations/${conversationId}/messages`, options),
@@ -3717,6 +3807,66 @@ var ChatService = class extends ServiceModule {
3717
3807
  return this.createConversation(data);
3718
3808
  }
3719
3809
  };
3810
+ var ConferenceService = class extends ServiceModule {
3811
+ constructor() {
3812
+ super(...arguments);
3813
+ this.basePath = "/v1/conference";
3814
+ }
3815
+ // --------------------------------------------------------------------------
3816
+ // Call Lifecycle
3817
+ // --------------------------------------------------------------------------
3818
+ async createCall(data, options) {
3819
+ return this.post("/calls", data, options);
3820
+ }
3821
+ async getCall(callId, options) {
3822
+ return this._get(`/calls/${callId}`, options);
3823
+ }
3824
+ async listCalls(params, options) {
3825
+ return this._get(this.withQuery("/calls", params), options);
3826
+ }
3827
+ async endCall(callId, options) {
3828
+ return this.post(`/calls/${callId}/end`, void 0, options);
3829
+ }
3830
+ // --------------------------------------------------------------------------
3831
+ // Participants
3832
+ // --------------------------------------------------------------------------
3833
+ async joinCall(callId, options) {
3834
+ return this.post(`/calls/${callId}/join`, void 0, options);
3835
+ }
3836
+ async leaveCall(callId, options) {
3837
+ return this.post(`/calls/${callId}/leave`, void 0, options);
3838
+ }
3839
+ async listParticipants(callId, options) {
3840
+ return this._get(`/calls/${callId}/participants`, options);
3841
+ }
3842
+ // --------------------------------------------------------------------------
3843
+ // Recording
3844
+ // --------------------------------------------------------------------------
3845
+ async startRecording(callId, options) {
3846
+ return this.post(`/calls/${callId}/recording/start`, void 0, options);
3847
+ }
3848
+ async stopRecording(callId, options) {
3849
+ return this.post(`/calls/${callId}/recording/stop`, void 0, options);
3850
+ }
3851
+ async consentToRecording(callId, options) {
3852
+ return this.post(`/calls/${callId}/recording/consent`, void 0, options);
3853
+ }
3854
+ // --------------------------------------------------------------------------
3855
+ // Settings
3856
+ // --------------------------------------------------------------------------
3857
+ async getSettings(options) {
3858
+ return this._get("/settings", options);
3859
+ }
3860
+ async updateSettings(data, options) {
3861
+ return this.put("/settings", data, options);
3862
+ }
3863
+ // --------------------------------------------------------------------------
3864
+ // Stats
3865
+ // --------------------------------------------------------------------------
3866
+ async submitStats(callId, stats, options) {
3867
+ return this.post(`/calls/${callId}/stats`, stats, options);
3868
+ }
3869
+ };
3720
3870
  var SocialService = class extends ServiceModule {
3721
3871
  constructor() {
3722
3872
  super(...arguments);
@@ -3838,7 +3988,14 @@ var ReferralsService = class extends ServiceModule {
3838
3988
  var BillingService = class extends ServiceModule {
3839
3989
  constructor() {
3840
3990
  super(...arguments);
3841
- this.basePath = "/v1/billing";
3991
+ this.basePath = "/v1/money/billing";
3992
+ }
3993
+ retiredSurface(route) {
3994
+ return Promise.reject(
3995
+ new Error(
3996
+ `${route} was retired after the money-services cutover. Use the dedicated money services instead of BillingService for this operation.`
3997
+ )
3998
+ );
3842
3999
  }
3843
4000
  // --------------------------------------------------------------------------
3844
4001
  // Customers
@@ -3853,28 +4010,28 @@ var BillingService = class extends ServiceModule {
3853
4010
  // Subscriptions
3854
4011
  // --------------------------------------------------------------------------
3855
4012
  async subscribe(data, options) {
3856
- return this.post("/subscriptions", data, options);
4013
+ return this.retiredSurface("/v1/money/billing/subscriptions");
3857
4014
  }
3858
4015
  async listSubscriptions(params, options) {
3859
- return this._list("/subscriptions", params, options);
4016
+ return this.retiredSurface("/v1/money/billing/subscriptions");
3860
4017
  }
3861
4018
  async cancelSubscription(id, options) {
3862
- return this.post(`/subscriptions/${id}/cancel`, void 0, options);
4019
+ return this.retiredSurface(`/v1/money/billing/subscriptions/${id}/cancel`);
3863
4020
  }
3864
4021
  async resumeSubscription(id, options) {
3865
- return this.post(`/subscriptions/${id}/resume`, void 0, options);
4022
+ return this.retiredSurface(`/v1/money/billing/subscriptions/${id}/resume`);
3866
4023
  }
3867
4024
  async upgradeSubscription(id, data, options) {
3868
- return this.patch(`/subscriptions/${id}/upgrade`, data, options);
4025
+ return this.retiredSurface(`/v1/money/billing/subscriptions/${id}/upgrade`);
3869
4026
  }
3870
4027
  // --------------------------------------------------------------------------
3871
4028
  // Usage
3872
4029
  // --------------------------------------------------------------------------
3873
4030
  async reportUsage(data, options) {
3874
- return this.post("/usage", data, options);
4031
+ return this.retiredSurface("/v1/money/billing/usage");
3875
4032
  }
3876
4033
  async getUsageSummary(options) {
3877
- return this._get("/usage/summary", options);
4034
+ return this.retiredSurface("/v1/money/billing/usage/summary");
3878
4035
  }
3879
4036
  // --------------------------------------------------------------------------
3880
4037
  // Invoices
@@ -3907,7 +4064,7 @@ var BillingService = class extends ServiceModule {
3907
4064
  return this.post(`/connected-accounts/${id}/onboarding-link`, data, options);
3908
4065
  }
3909
4066
  async getAccountBalance(id, options) {
3910
- return this._get(`/connected-accounts/${id}/balance`, options);
4067
+ return this.retiredSurface(`/v1/money/billing/connected-accounts/${id}/balance`);
3911
4068
  }
3912
4069
  async createAccountSession(id, options) {
3913
4070
  return this.post(`/connected-accounts/${id}/account-session`, void 0, options);
@@ -3922,25 +4079,25 @@ var BillingService = class extends ServiceModule {
3922
4079
  // Payments (Marketplace)
3923
4080
  // --------------------------------------------------------------------------
3924
4081
  async createPayment(data, options) {
3925
- return this.post("/payments", data, options);
4082
+ return this.retiredSurface("/v1/money/billing/payments");
3926
4083
  }
3927
4084
  async getPayment(id, options) {
3928
- return this._get(`/payments/${id}`, options);
4085
+ return this.retiredSurface(`/v1/money/billing/payments/${id}`);
3929
4086
  }
3930
4087
  async listPayments(params, options) {
3931
- return this._list("/payments", params, options);
4088
+ return this.retiredSurface("/v1/money/billing/payments");
3932
4089
  }
3933
4090
  // --------------------------------------------------------------------------
3934
4091
  // Refunds
3935
4092
  // --------------------------------------------------------------------------
3936
4093
  async refundPayment(id, data, options) {
3937
- return this.post(`/payments/${id}/refund`, data, options);
4094
+ return this.retiredSurface(`/v1/money/billing/payments/${id}/refund`);
3938
4095
  }
3939
4096
  // --------------------------------------------------------------------------
3940
4097
  // Payouts
3941
4098
  // --------------------------------------------------------------------------
3942
4099
  async getPayoutHistory(accountId, params, options) {
3943
- return this._list(`/connected-accounts/${accountId}/payouts`, params, options);
4100
+ return this.retiredSurface(`/v1/money/billing/connected-accounts/${accountId}/payouts`);
3944
4101
  }
3945
4102
  async getPayoutSchedule(accountId, options) {
3946
4103
  return this._get(`/connected-accounts/${accountId}/payout-schedule`, options);
@@ -3952,13 +4109,10 @@ var BillingService = class extends ServiceModule {
3952
4109
  // Ledger
3953
4110
  // --------------------------------------------------------------------------
3954
4111
  async getTransactions(params, options) {
3955
- return this._list("/transactions", params, options);
4112
+ return this.retiredSurface("/v1/money/billing/transactions");
3956
4113
  }
3957
4114
  async getTransactionSummary(params, options) {
3958
- return this._get(
3959
- this.withQuery("/transactions/summary", params),
3960
- options
3961
- );
4115
+ return this.retiredSurface("/v1/money/billing/transactions/summary");
3962
4116
  }
3963
4117
  // --------------------------------------------------------------------------
3964
4118
  // Setup Sessions
@@ -3970,35 +4124,35 @@ var BillingService = class extends ServiceModule {
3970
4124
  // Connected Account Operations: Products, Prices, Subscriptions, Transfers
3971
4125
  // --------------------------------------------------------------------------
3972
4126
  async createProduct(data, options) {
3973
- return this.post("/products", data, options);
4127
+ return this.retiredSurface("/v1/money/billing/products");
3974
4128
  }
3975
4129
  async createPrice(data, options) {
3976
- return this.post("/prices", data, options);
4130
+ return this.retiredSurface("/v1/money/billing/prices");
3977
4131
  }
3978
4132
  async deactivatePrice(id, options) {
3979
- return this.post(`/prices/${id}/deactivate`, void 0, options);
4133
+ return this.retiredSurface(`/v1/money/billing/prices/${id}/deactivate`);
3980
4134
  }
3981
4135
  async createConnectedSubscription(data, options) {
3982
- return this.post("/connected-subscriptions", data, options);
4136
+ return this.retiredSurface("/v1/money/billing/connected-subscriptions");
3983
4137
  }
3984
4138
  async cancelConnectedSubscription(id, data, options) {
3985
- return this.post(`/connected-subscriptions/${id}/cancel`, data, options);
4139
+ return this.retiredSurface(
4140
+ `/v1/money/billing/connected-subscriptions/${id}/cancel`
4141
+ );
3986
4142
  }
3987
4143
  async listConnectedSubscriptions(params, options) {
3988
- return this._list(
3989
- "/connected-subscriptions",
3990
- params,
3991
- options
4144
+ return this.retiredSurface(
4145
+ "/v1/money/billing/connected-subscriptions"
3992
4146
  );
3993
4147
  }
3994
4148
  async createConnectedSetupIntent(data, options) {
3995
- return this.post("/connected-setup-intents", data, options);
4149
+ return this.retiredSurface("/v1/money/billing/connected-setup-intents");
3996
4150
  }
3997
4151
  async createTransfer(data, options) {
3998
- return this.post("/transfers", data, options);
4152
+ return this.retiredSurface("/v1/money/billing/transfers");
3999
4153
  }
4000
4154
  async syncPaymentStatus(id, options) {
4001
- return this.post(`/payments/${id}/sync`, void 0, options);
4155
+ return this.retiredSurface(`/v1/money/billing/payments/${id}/sync`);
4002
4156
  }
4003
4157
  // --------------------------------------------------------------------------
4004
4158
  // Legacy methods (backward compat)
@@ -5011,7 +5165,7 @@ var SearchService = class extends ServiceModule {
5011
5165
  return this.post("/documents", data);
5012
5166
  }
5013
5167
  };
5014
- var PHOTO_BREAKPOINTS = [150, 320, 640, 1080];
5168
+ var PHOTO_BREAKPOINTS = [36, 150, 320, 640, 1080];
5015
5169
  var PhotoService = class extends ServiceModule {
5016
5170
  constructor() {
5017
5171
  super(...arguments);
@@ -5070,6 +5224,9 @@ var PhotoService = class extends ServiceModule {
5070
5224
  *
5071
5225
  * // Profile avatar at 48px -> snaps to 150px
5072
5226
  * const url = sm.photo.getOptimalUrl(photoId, 48)
5227
+ *
5228
+ * // Tiny avatar at 36px -> exact cache hit on 36px micro-thumbnail
5229
+ * const url = sm.photo.getOptimalUrl(photoId, 36)
5073
5230
  * ```
5074
5231
  */
5075
5232
  getOptimalUrl(photoId, displayWidth, options) {
@@ -5080,6 +5237,13 @@ var PhotoService = class extends ServiceModule {
5080
5237
  const size = PHOTO_BREAKPOINTS.find((bp) => bp >= physicalWidth) ?? PHOTO_BREAKPOINTS[PHOTO_BREAKPOINTS.length - 1];
5081
5238
  return this.getTransformUrl(photoId, { width: size, height: size, fit: "cover" });
5082
5239
  }
5240
+ /**
5241
+ * Get the 36px avatar micro-thumbnail URL for a photo.
5242
+ * Hits the pre-generated 36x36 bicubic-resized cached variant.
5243
+ */
5244
+ getAvatarThumbnailUrl(photoId) {
5245
+ return this.getOptimalUrl(photoId, 36);
5246
+ }
5083
5247
  /**
5084
5248
  * Generate an HTML srcset string for responsive square photo display.
5085
5249
  *
@@ -5951,6 +6115,7 @@ var ScaleMule = class {
5951
6115
  this.video = new VideoService(this._client);
5952
6116
  this.data = new DataService(this._client);
5953
6117
  this.chat = new ChatService(this._client);
6118
+ this.conference = new ConferenceService(this._client);
5954
6119
  this.social = new SocialService(this._client);
5955
6120
  this.referrals = new ReferralsService(this._client);
5956
6121
  this.billing = new BillingService(this._client);
@@ -9437,6 +9602,341 @@ function useShare(options) {
9437
9602
  }, [shareUrl]);
9438
9603
  return { shareUrl, referralCode, copyLink, copied, loading };
9439
9604
  }
9605
+ function toApiError2(error) {
9606
+ if (error instanceof ScaleMuleApiError) {
9607
+ return {
9608
+ code: error.code,
9609
+ message: error.message,
9610
+ field: error.field
9611
+ };
9612
+ }
9613
+ return {
9614
+ code: "UNKNOWN",
9615
+ message: error instanceof Error ? error.message : "Feedback request failed"
9616
+ };
9617
+ }
9618
+ function useFeedback(options = {}) {
9619
+ const { client } = useScaleMule();
9620
+ const { status, type, enabled = true } = options;
9621
+ const [items, setItems] = react.useState([]);
9622
+ const [loading, setLoading] = react.useState(enabled);
9623
+ const [error, setError] = react.useState(null);
9624
+ const refresh = react.useCallback(async () => {
9625
+ if (!enabled) {
9626
+ setLoading(false);
9627
+ return;
9628
+ }
9629
+ setLoading(true);
9630
+ try {
9631
+ const params = new URLSearchParams();
9632
+ if (status) params.append("status", status);
9633
+ if (type) params.append("type", type);
9634
+ const qs = params.toString();
9635
+ const path = `/v1/feedback/items${qs ? "?" + qs : ""}`;
9636
+ const result = await client.get(path);
9637
+ setItems(Array.isArray(result) ? result : []);
9638
+ setError(null);
9639
+ } catch (err) {
9640
+ setError(toApiError2(err));
9641
+ } finally {
9642
+ setLoading(false);
9643
+ }
9644
+ }, [client, enabled, status, type]);
9645
+ react.useEffect(() => {
9646
+ refresh();
9647
+ }, [refresh]);
9648
+ const submit = react.useCallback(
9649
+ async (input) => {
9650
+ try {
9651
+ const created = await client.post("/v1/feedback/submit", input);
9652
+ setItems((prev) => [created, ...prev]);
9653
+ setError(null);
9654
+ return created;
9655
+ } catch (err) {
9656
+ const apiErr = toApiError2(err);
9657
+ setError(apiErr);
9658
+ throw err;
9659
+ }
9660
+ },
9661
+ [client]
9662
+ );
9663
+ return { items, loading, error, submit, refresh };
9664
+ }
9665
+ var TYPE_LABELS = {
9666
+ bug_report: "Bug",
9667
+ feature_request: "Feature request",
9668
+ improvement: "Improvement",
9669
+ other: "Other"
9670
+ };
9671
+ var POSITION_STYLES = {
9672
+ "bottom-right": { bottom: 24, right: 24 },
9673
+ "bottom-left": { bottom: 24, left: 24 },
9674
+ "top-right": { top: 24, right: 24 },
9675
+ "top-left": { top: 24, left: 24 }
9676
+ };
9677
+ var STYLES = {
9678
+ trigger: {
9679
+ position: "fixed",
9680
+ zIndex: 999998,
9681
+ padding: "10px 16px",
9682
+ fontSize: 14,
9683
+ fontWeight: 600,
9684
+ border: "none",
9685
+ borderRadius: 999,
9686
+ cursor: "pointer",
9687
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
9688
+ background: "#1f2937",
9689
+ color: "#fff"
9690
+ },
9691
+ overlay: {
9692
+ position: "fixed",
9693
+ inset: 0,
9694
+ background: "rgba(15, 23, 42, 0.5)",
9695
+ zIndex: 999999,
9696
+ display: "flex",
9697
+ alignItems: "center",
9698
+ justifyContent: "center",
9699
+ padding: 16
9700
+ },
9701
+ modal: {
9702
+ width: "min(440px, 100%)",
9703
+ background: "#fff",
9704
+ color: "#0f172a",
9705
+ borderRadius: 12,
9706
+ boxShadow: "0 20px 60px rgba(0,0,0,0.2)",
9707
+ padding: 20,
9708
+ display: "flex",
9709
+ flexDirection: "column",
9710
+ gap: 12
9711
+ },
9712
+ modalDark: {
9713
+ background: "#0f172a",
9714
+ color: "#f8fafc"
9715
+ },
9716
+ label: {
9717
+ fontSize: 12,
9718
+ fontWeight: 600,
9719
+ textTransform: "uppercase",
9720
+ letterSpacing: 0.5,
9721
+ opacity: 0.7
9722
+ },
9723
+ field: {
9724
+ width: "100%",
9725
+ padding: "8px 10px",
9726
+ fontSize: 14,
9727
+ border: "1px solid #e2e8f0",
9728
+ borderRadius: 6,
9729
+ boxSizing: "border-box",
9730
+ background: "transparent",
9731
+ color: "inherit"
9732
+ },
9733
+ textarea: {
9734
+ minHeight: 96,
9735
+ resize: "vertical"
9736
+ },
9737
+ rowEnd: {
9738
+ display: "flex",
9739
+ justifyContent: "flex-end",
9740
+ gap: 8
9741
+ },
9742
+ primaryBtn: {
9743
+ padding: "8px 14px",
9744
+ fontSize: 14,
9745
+ fontWeight: 600,
9746
+ border: "none",
9747
+ borderRadius: 6,
9748
+ background: "#2563eb",
9749
+ color: "#fff",
9750
+ cursor: "pointer"
9751
+ },
9752
+ secondaryBtn: {
9753
+ padding: "8px 14px",
9754
+ fontSize: 14,
9755
+ border: "1px solid #cbd5e1",
9756
+ borderRadius: 6,
9757
+ background: "transparent",
9758
+ color: "inherit",
9759
+ cursor: "pointer"
9760
+ },
9761
+ error: {
9762
+ fontSize: 13,
9763
+ color: "#dc2626"
9764
+ },
9765
+ success: {
9766
+ fontSize: 14,
9767
+ fontWeight: 500,
9768
+ textAlign: "center",
9769
+ padding: "24px 8px"
9770
+ }
9771
+ };
9772
+ var ALL_TYPES = ["bug_report", "feature_request", "improvement", "other"];
9773
+ function FeedbackWidget(props) {
9774
+ const {
9775
+ position = "bottom-right",
9776
+ defaultType = "feature_request",
9777
+ allowedTypes = ALL_TYPES,
9778
+ triggerLabel = "Feedback",
9779
+ theme = "auto",
9780
+ className,
9781
+ onSubmitted
9782
+ } = props;
9783
+ const sm = useScaleMule();
9784
+ const { submit } = useFeedback({ enabled: false });
9785
+ const [open, setOpen] = react.useState(false);
9786
+ const [type, setType] = react.useState(defaultType);
9787
+ const [title, setTitle] = react.useState("");
9788
+ const [description, setDescription] = react.useState("");
9789
+ const [email, setEmail] = react.useState("");
9790
+ const [submitting, setSubmitting] = react.useState(false);
9791
+ const [errMsg, setErrMsg] = react.useState(null);
9792
+ const [done, setDone] = react.useState(false);
9793
+ Boolean(sm.user || sm.user === void 0 ? false : false);
9794
+ const signedIn = Boolean(sm.user);
9795
+ function reset() {
9796
+ setType(defaultType);
9797
+ setTitle("");
9798
+ setDescription("");
9799
+ setEmail("");
9800
+ setErrMsg(null);
9801
+ setDone(false);
9802
+ }
9803
+ async function handleSubmit(e) {
9804
+ e.preventDefault();
9805
+ if (!title.trim() || !description.trim()) return;
9806
+ if (!signedIn && !email.trim()) {
9807
+ setErrMsg("email is required when not signed in");
9808
+ return;
9809
+ }
9810
+ setSubmitting(true);
9811
+ setErrMsg(null);
9812
+ try {
9813
+ const item = await submit({
9814
+ type,
9815
+ title: title.trim(),
9816
+ description: description.trim(),
9817
+ email: signedIn ? void 0 : email.trim() || void 0
9818
+ });
9819
+ setDone(true);
9820
+ onSubmitted?.(item);
9821
+ } catch (err) {
9822
+ const message = err && typeof err === "object" && "message" in err ? String(err.message) : "Submit failed";
9823
+ setErrMsg(message);
9824
+ } finally {
9825
+ setSubmitting(false);
9826
+ }
9827
+ }
9828
+ function handleClose() {
9829
+ setOpen(false);
9830
+ setTimeout(reset, 200);
9831
+ }
9832
+ const dark = theme === "dark" || theme === "auto" && typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
9833
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9834
+ /* @__PURE__ */ jsxRuntime.jsx(
9835
+ "button",
9836
+ {
9837
+ type: "button",
9838
+ onClick: () => setOpen(true),
9839
+ className,
9840
+ style: { ...STYLES.trigger, ...POSITION_STYLES[position] },
9841
+ children: triggerLabel
9842
+ }
9843
+ ),
9844
+ open && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "dialog", "aria-modal": "true", style: STYLES.overlay, onClick: handleClose, children: /* @__PURE__ */ jsxRuntime.jsx(
9845
+ "form",
9846
+ {
9847
+ onSubmit: handleSubmit,
9848
+ onClick: (e) => e.stopPropagation(),
9849
+ style: { ...STYLES.modal, ...dark ? STYLES.modalDark : null },
9850
+ children: done ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9851
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: STYLES.success, children: [
9852
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 32, marginBottom: 8 }, children: "\u{1F389}" }),
9853
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: "Thanks \u2014 we got it." })
9854
+ ] }),
9855
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.rowEnd, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: STYLES.primaryBtn, onClick: handleClose, children: "Close" }) })
9856
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9857
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
9858
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 16 }, children: "Send feedback" }),
9859
+ /* @__PURE__ */ jsxRuntime.jsx(
9860
+ "button",
9861
+ {
9862
+ type: "button",
9863
+ onClick: handleClose,
9864
+ "aria-label": "Close",
9865
+ style: {
9866
+ background: "transparent",
9867
+ border: "none",
9868
+ fontSize: 18,
9869
+ cursor: "pointer",
9870
+ color: "inherit"
9871
+ },
9872
+ children: "\xD7"
9873
+ }
9874
+ )
9875
+ ] }),
9876
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
9877
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.label, children: "Type" }),
9878
+ /* @__PURE__ */ jsxRuntime.jsx(
9879
+ "select",
9880
+ {
9881
+ style: STYLES.field,
9882
+ value: type,
9883
+ onChange: (e) => setType(e.target.value),
9884
+ children: allowedTypes.map((t) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: t, children: TYPE_LABELS[t] }, t))
9885
+ }
9886
+ )
9887
+ ] }),
9888
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
9889
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.label, children: "Title" }),
9890
+ /* @__PURE__ */ jsxRuntime.jsx(
9891
+ "input",
9892
+ {
9893
+ style: STYLES.field,
9894
+ type: "text",
9895
+ value: title,
9896
+ onChange: (e) => setTitle(e.target.value),
9897
+ placeholder: "One-line summary",
9898
+ maxLength: 255,
9899
+ required: true
9900
+ }
9901
+ )
9902
+ ] }),
9903
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
9904
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.label, children: "Details" }),
9905
+ /* @__PURE__ */ jsxRuntime.jsx(
9906
+ "textarea",
9907
+ {
9908
+ style: { ...STYLES.field, ...STYLES.textarea },
9909
+ value: description,
9910
+ onChange: (e) => setDescription(e.target.value),
9911
+ placeholder: "What happened, what you expected, anything else helpful\u2026",
9912
+ required: true
9913
+ }
9914
+ )
9915
+ ] }),
9916
+ !signedIn && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
9917
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.label, children: "Email" }),
9918
+ /* @__PURE__ */ jsxRuntime.jsx(
9919
+ "input",
9920
+ {
9921
+ style: STYLES.field,
9922
+ type: "email",
9923
+ value: email,
9924
+ onChange: (e) => setEmail(e.target.value),
9925
+ placeholder: "you@example.com",
9926
+ required: true
9927
+ }
9928
+ )
9929
+ ] }),
9930
+ errMsg && /* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.error, children: errMsg }),
9931
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: STYLES.rowEnd, children: [
9932
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: STYLES.secondaryBtn, onClick: handleClose, children: "Cancel" }),
9933
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", style: STYLES.primaryBtn, disabled: submitting, children: submitting ? "Sending\u2026" : "Send" })
9934
+ ] })
9935
+ ] })
9936
+ }
9937
+ ) })
9938
+ ] });
9939
+ }
9440
9940
 
9441
9941
  // src/validation.ts
9442
9942
  var phoneCountries = [
@@ -9737,6 +10237,7 @@ Object.defineProperty(exports, "createMoneyClient", {
9737
10237
  enumerable: true,
9738
10238
  get: function () { return money.createMoneyClient; }
9739
10239
  });
10240
+ exports.FeedbackWidget = FeedbackWidget;
9740
10241
  exports.ScaleMuleApiError = ScaleMuleApiError;
9741
10242
  exports.ScaleMuleClient = ScaleMuleClient2;
9742
10243
  exports.ScaleMuleProvider = ScaleMuleProvider;
@@ -9751,6 +10252,7 @@ exports.useAuth = useAuth;
9751
10252
  exports.useBilling = useBilling;
9752
10253
  exports.useContent = useContent;
9753
10254
  exports.useFeatureFlags = useFeatureFlags;
10255
+ exports.useFeedback = useFeedback;
9754
10256
  exports.useMoney = useMoney;
9755
10257
  exports.useMoneyClient = useMoneyClient;
9756
10258
  exports.usePushNotifications = usePushNotifications;