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