@scalemule/nextjs 0.0.42 → 0.1.1
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.d.mts +110 -2
- package/dist/index.d.ts +110 -2
- package/dist/index.js +392 -0
- package/dist/index.mjs +392 -2
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ReactNode, ReactElement } from 'react';
|
|
3
3
|
import { MoneyClient } from '@scalemule/money';
|
|
4
4
|
export { MoneyClient, MoneyClientConfig, createMoneyClient } from '@scalemule/money';
|
|
5
5
|
import { RealtimeService } from '@scalemule/sdk';
|
|
@@ -346,6 +346,114 @@ interface UseShareReturn {
|
|
|
346
346
|
}
|
|
347
347
|
declare function useShare(options?: UseShareOptions): UseShareReturn;
|
|
348
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Public types for the feedback hook + widget.
|
|
351
|
+
*
|
|
352
|
+
* Mirrors the JSON shape returned by `scalemule-feedback`'s public endpoints
|
|
353
|
+
* (`/v1/feedback/submit`, `/v1/feedback/items`). Dashboard/admin shapes live
|
|
354
|
+
* server-side and are not exported here — customer apps interact with the
|
|
355
|
+
* SDK only through the end-user surface.
|
|
356
|
+
*/
|
|
357
|
+
type FeedbackType = 'bug_report' | 'feature_request' | 'improvement' | 'other';
|
|
358
|
+
type FeedbackStatus = 'new' | 'reviewed' | 'planned' | 'in_progress' | 'completed' | 'declined';
|
|
359
|
+
type FeedbackPriority = 'low' | 'medium' | 'high' | 'urgent';
|
|
360
|
+
/**
|
|
361
|
+
* The end-user-visible shape of a feedback item. Excludes staff-only fields
|
|
362
|
+
* (assigned_to, internal_notes, email of OTHER users, etc.) that the service
|
|
363
|
+
* never returns through `/items`.
|
|
364
|
+
*/
|
|
365
|
+
interface FeedbackItem {
|
|
366
|
+
id: string;
|
|
367
|
+
type: FeedbackType;
|
|
368
|
+
title: string;
|
|
369
|
+
description: string;
|
|
370
|
+
status: FeedbackStatus;
|
|
371
|
+
priority: FeedbackPriority;
|
|
372
|
+
tags: string[] | null;
|
|
373
|
+
created_at: string;
|
|
374
|
+
updated_at: string;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Body for `POST /v1/feedback/submit`.
|
|
378
|
+
*
|
|
379
|
+
* `email` is required when no end-user session is present — the gateway omits
|
|
380
|
+
* `x-user-id` in that case and the service rejects with 400 unless email is
|
|
381
|
+
* given. When a session exists, email is optional.
|
|
382
|
+
*/
|
|
383
|
+
interface FeedbackItemInput {
|
|
384
|
+
type: FeedbackType;
|
|
385
|
+
title: string;
|
|
386
|
+
description: string;
|
|
387
|
+
email?: string;
|
|
388
|
+
tags?: string[];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
interface UseFeedbackOptions {
|
|
392
|
+
/** Optional status filter applied to the list call. */
|
|
393
|
+
status?: FeedbackStatus;
|
|
394
|
+
/** Optional type filter. */
|
|
395
|
+
type?: FeedbackType;
|
|
396
|
+
/** When false, suppress the initial list fetch (the widget submits without listing). */
|
|
397
|
+
enabled?: boolean;
|
|
398
|
+
}
|
|
399
|
+
interface UseFeedbackResult {
|
|
400
|
+
/** End-user's own feedback items for the current tenant. Empty when not signed in. */
|
|
401
|
+
items: FeedbackItem[];
|
|
402
|
+
loading: boolean;
|
|
403
|
+
error: ApiError | null;
|
|
404
|
+
/** Submit a new feedback item. Returns the persisted item on success. */
|
|
405
|
+
submit: (input: FeedbackItemInput) => Promise<FeedbackItem>;
|
|
406
|
+
/** Re-fetch the list. */
|
|
407
|
+
refresh: () => Promise<void>;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Hook for end-user feedback submission and read-own.
|
|
411
|
+
*
|
|
412
|
+
* Calls go through `client` from `ScaleMuleProvider`, which attaches the
|
|
413
|
+
* configured API key and (when present) the user session token. Tenancy
|
|
414
|
+
* (`x-app-id`) is derived by the gateway from the API key — never set
|
|
415
|
+
* client-side.
|
|
416
|
+
*/
|
|
417
|
+
declare function useFeedback(options?: UseFeedbackOptions): UseFeedbackResult;
|
|
418
|
+
|
|
419
|
+
interface FeedbackWidgetProps {
|
|
420
|
+
/** Floating-button corner. Default `bottom-right`. */
|
|
421
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
422
|
+
/** Default selected type when the modal opens. Default `feature_request`. */
|
|
423
|
+
defaultType?: FeedbackType;
|
|
424
|
+
/** Restrict the type select to this subset. Default: all four types. */
|
|
425
|
+
allowedTypes?: FeedbackType[];
|
|
426
|
+
/** Trigger button label. Default `Feedback`. */
|
|
427
|
+
triggerLabel?: string;
|
|
428
|
+
/** `light`/`dark`/`auto` (matches OS). Default `auto`. */
|
|
429
|
+
theme?: 'light' | 'dark' | 'auto';
|
|
430
|
+
/** Optional class for the trigger button (use to override styling). */
|
|
431
|
+
className?: string;
|
|
432
|
+
/** Show the 1–5 star rating row. Default `true`. Rating is stored as a
|
|
433
|
+
* `rating:N` tag on the feedback item — staff can filter on it from the
|
|
434
|
+
* dashboard inbox. Optional per submission; users who skip it just submit
|
|
435
|
+
* without a rating. */
|
|
436
|
+
enableRating?: boolean;
|
|
437
|
+
/** Label for the rating row when enabled. Default `How would you rate
|
|
438
|
+
* your experience?`. */
|
|
439
|
+
ratingLabel?: string;
|
|
440
|
+
/** Called after a successful submit. */
|
|
441
|
+
onSubmitted?: (item: FeedbackItem) => void;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Floating feedback widget. Renders nothing until the trigger is clicked,
|
|
445
|
+
* then opens a small modal with type / title / description fields. Submits
|
|
446
|
+
* via `useFeedback().submit()` — inherits identity from `ScaleMuleProvider`.
|
|
447
|
+
*
|
|
448
|
+
* If the end-user is not signed in, the widget shows an additional `email`
|
|
449
|
+
* field (required by the service for anonymous submissions).
|
|
450
|
+
*
|
|
451
|
+
* Tenant must have `feedback_app_config.enabled = TRUE` for `/submit` to
|
|
452
|
+
* accept submissions; otherwise the service responds with 404
|
|
453
|
+
* `FEEDBACK_DISABLED` and the widget surfaces the error message.
|
|
454
|
+
*/
|
|
455
|
+
declare function FeedbackWidget(props: FeedbackWidgetProps): ReactElement | null;
|
|
456
|
+
|
|
349
457
|
/**
|
|
350
458
|
* Client-side validation helpers
|
|
351
459
|
*
|
|
@@ -504,4 +612,4 @@ declare function createSafeLogger(prefix: string): {
|
|
|
504
612
|
error: (message: string, data?: unknown) => void;
|
|
505
613
|
};
|
|
506
614
|
|
|
507
|
-
export { ApiError, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, ListFilesParams, LoginResponse, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, type UseShareOptions, type UseShareReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
|
|
615
|
+
export { ApiError, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, type FeedbackItem, type FeedbackItemInput, type FeedbackPriority, type FeedbackStatus, type FeedbackType, FeedbackWidget, type FeedbackWidgetProps, ListFilesParams, LoginResponse, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeedbackOptions, type UseFeedbackResult, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, type UseShareOptions, type UseShareReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ReactNode, ReactElement } from 'react';
|
|
3
3
|
import { MoneyClient } from '@scalemule/money';
|
|
4
4
|
export { MoneyClient, MoneyClientConfig, createMoneyClient } from '@scalemule/money';
|
|
5
5
|
import { RealtimeService } from '@scalemule/sdk';
|
|
@@ -346,6 +346,114 @@ interface UseShareReturn {
|
|
|
346
346
|
}
|
|
347
347
|
declare function useShare(options?: UseShareOptions): UseShareReturn;
|
|
348
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Public types for the feedback hook + widget.
|
|
351
|
+
*
|
|
352
|
+
* Mirrors the JSON shape returned by `scalemule-feedback`'s public endpoints
|
|
353
|
+
* (`/v1/feedback/submit`, `/v1/feedback/items`). Dashboard/admin shapes live
|
|
354
|
+
* server-side and are not exported here — customer apps interact with the
|
|
355
|
+
* SDK only through the end-user surface.
|
|
356
|
+
*/
|
|
357
|
+
type FeedbackType = 'bug_report' | 'feature_request' | 'improvement' | 'other';
|
|
358
|
+
type FeedbackStatus = 'new' | 'reviewed' | 'planned' | 'in_progress' | 'completed' | 'declined';
|
|
359
|
+
type FeedbackPriority = 'low' | 'medium' | 'high' | 'urgent';
|
|
360
|
+
/**
|
|
361
|
+
* The end-user-visible shape of a feedback item. Excludes staff-only fields
|
|
362
|
+
* (assigned_to, internal_notes, email of OTHER users, etc.) that the service
|
|
363
|
+
* never returns through `/items`.
|
|
364
|
+
*/
|
|
365
|
+
interface FeedbackItem {
|
|
366
|
+
id: string;
|
|
367
|
+
type: FeedbackType;
|
|
368
|
+
title: string;
|
|
369
|
+
description: string;
|
|
370
|
+
status: FeedbackStatus;
|
|
371
|
+
priority: FeedbackPriority;
|
|
372
|
+
tags: string[] | null;
|
|
373
|
+
created_at: string;
|
|
374
|
+
updated_at: string;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Body for `POST /v1/feedback/submit`.
|
|
378
|
+
*
|
|
379
|
+
* `email` is required when no end-user session is present — the gateway omits
|
|
380
|
+
* `x-user-id` in that case and the service rejects with 400 unless email is
|
|
381
|
+
* given. When a session exists, email is optional.
|
|
382
|
+
*/
|
|
383
|
+
interface FeedbackItemInput {
|
|
384
|
+
type: FeedbackType;
|
|
385
|
+
title: string;
|
|
386
|
+
description: string;
|
|
387
|
+
email?: string;
|
|
388
|
+
tags?: string[];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
interface UseFeedbackOptions {
|
|
392
|
+
/** Optional status filter applied to the list call. */
|
|
393
|
+
status?: FeedbackStatus;
|
|
394
|
+
/** Optional type filter. */
|
|
395
|
+
type?: FeedbackType;
|
|
396
|
+
/** When false, suppress the initial list fetch (the widget submits without listing). */
|
|
397
|
+
enabled?: boolean;
|
|
398
|
+
}
|
|
399
|
+
interface UseFeedbackResult {
|
|
400
|
+
/** End-user's own feedback items for the current tenant. Empty when not signed in. */
|
|
401
|
+
items: FeedbackItem[];
|
|
402
|
+
loading: boolean;
|
|
403
|
+
error: ApiError | null;
|
|
404
|
+
/** Submit a new feedback item. Returns the persisted item on success. */
|
|
405
|
+
submit: (input: FeedbackItemInput) => Promise<FeedbackItem>;
|
|
406
|
+
/** Re-fetch the list. */
|
|
407
|
+
refresh: () => Promise<void>;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Hook for end-user feedback submission and read-own.
|
|
411
|
+
*
|
|
412
|
+
* Calls go through `client` from `ScaleMuleProvider`, which attaches the
|
|
413
|
+
* configured API key and (when present) the user session token. Tenancy
|
|
414
|
+
* (`x-app-id`) is derived by the gateway from the API key — never set
|
|
415
|
+
* client-side.
|
|
416
|
+
*/
|
|
417
|
+
declare function useFeedback(options?: UseFeedbackOptions): UseFeedbackResult;
|
|
418
|
+
|
|
419
|
+
interface FeedbackWidgetProps {
|
|
420
|
+
/** Floating-button corner. Default `bottom-right`. */
|
|
421
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
422
|
+
/** Default selected type when the modal opens. Default `feature_request`. */
|
|
423
|
+
defaultType?: FeedbackType;
|
|
424
|
+
/** Restrict the type select to this subset. Default: all four types. */
|
|
425
|
+
allowedTypes?: FeedbackType[];
|
|
426
|
+
/** Trigger button label. Default `Feedback`. */
|
|
427
|
+
triggerLabel?: string;
|
|
428
|
+
/** `light`/`dark`/`auto` (matches OS). Default `auto`. */
|
|
429
|
+
theme?: 'light' | 'dark' | 'auto';
|
|
430
|
+
/** Optional class for the trigger button (use to override styling). */
|
|
431
|
+
className?: string;
|
|
432
|
+
/** Show the 1–5 star rating row. Default `true`. Rating is stored as a
|
|
433
|
+
* `rating:N` tag on the feedback item — staff can filter on it from the
|
|
434
|
+
* dashboard inbox. Optional per submission; users who skip it just submit
|
|
435
|
+
* without a rating. */
|
|
436
|
+
enableRating?: boolean;
|
|
437
|
+
/** Label for the rating row when enabled. Default `How would you rate
|
|
438
|
+
* your experience?`. */
|
|
439
|
+
ratingLabel?: string;
|
|
440
|
+
/** Called after a successful submit. */
|
|
441
|
+
onSubmitted?: (item: FeedbackItem) => void;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Floating feedback widget. Renders nothing until the trigger is clicked,
|
|
445
|
+
* then opens a small modal with type / title / description fields. Submits
|
|
446
|
+
* via `useFeedback().submit()` — inherits identity from `ScaleMuleProvider`.
|
|
447
|
+
*
|
|
448
|
+
* If the end-user is not signed in, the widget shows an additional `email`
|
|
449
|
+
* field (required by the service for anonymous submissions).
|
|
450
|
+
*
|
|
451
|
+
* Tenant must have `feedback_app_config.enabled = TRUE` for `/submit` to
|
|
452
|
+
* accept submissions; otherwise the service responds with 404
|
|
453
|
+
* `FEEDBACK_DISABLED` and the widget surfaces the error message.
|
|
454
|
+
*/
|
|
455
|
+
declare function FeedbackWidget(props: FeedbackWidgetProps): ReactElement | null;
|
|
456
|
+
|
|
349
457
|
/**
|
|
350
458
|
* Client-side validation helpers
|
|
351
459
|
*
|
|
@@ -504,4 +612,4 @@ declare function createSafeLogger(prefix: string): {
|
|
|
504
612
|
error: (message: string, data?: unknown) => void;
|
|
505
613
|
};
|
|
506
614
|
|
|
507
|
-
export { ApiError, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, ListFilesParams, LoginResponse, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, type UseShareOptions, type UseShareReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
|
|
615
|
+
export { ApiError, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, type FeedbackItem, type FeedbackItemInput, type FeedbackPriority, type FeedbackStatus, type FeedbackType, FeedbackWidget, type FeedbackWidgetProps, ListFilesParams, LoginResponse, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeedbackOptions, type UseFeedbackResult, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, type UseShareOptions, type UseShareReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
|
package/dist/index.js
CHANGED
|
@@ -9602,6 +9602,396 @@ function useShare(options) {
|
|
|
9602
9602
|
}, [shareUrl]);
|
|
9603
9603
|
return { shareUrl, referralCode, copyLink, copied, loading };
|
|
9604
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
|
+
enableRating = true,
|
|
9782
|
+
ratingLabel = "How would you rate your experience?",
|
|
9783
|
+
onSubmitted
|
|
9784
|
+
} = props;
|
|
9785
|
+
const sm = useScaleMule();
|
|
9786
|
+
const { submit } = useFeedback({ enabled: false });
|
|
9787
|
+
const [open, setOpen] = react.useState(false);
|
|
9788
|
+
const [type, setType] = react.useState(defaultType);
|
|
9789
|
+
const [title, setTitle] = react.useState("");
|
|
9790
|
+
const [description, setDescription] = react.useState("");
|
|
9791
|
+
const [email, setEmail] = react.useState("");
|
|
9792
|
+
const [rating, setRating] = react.useState(null);
|
|
9793
|
+
const [hoverRating, setHoverRating] = react.useState(null);
|
|
9794
|
+
const [submitting, setSubmitting] = react.useState(false);
|
|
9795
|
+
const [errMsg, setErrMsg] = react.useState(null);
|
|
9796
|
+
const [done, setDone] = react.useState(false);
|
|
9797
|
+
Boolean(sm.user || sm.user === void 0 ? false : false);
|
|
9798
|
+
const signedIn = Boolean(sm.user);
|
|
9799
|
+
function reset() {
|
|
9800
|
+
setType(defaultType);
|
|
9801
|
+
setTitle("");
|
|
9802
|
+
setDescription("");
|
|
9803
|
+
setEmail("");
|
|
9804
|
+
setRating(null);
|
|
9805
|
+
setHoverRating(null);
|
|
9806
|
+
setErrMsg(null);
|
|
9807
|
+
setDone(false);
|
|
9808
|
+
}
|
|
9809
|
+
async function handleSubmit(e) {
|
|
9810
|
+
e.preventDefault();
|
|
9811
|
+
if (!title.trim() || !description.trim()) return;
|
|
9812
|
+
if (!signedIn && !email.trim()) {
|
|
9813
|
+
setErrMsg("email is required when not signed in");
|
|
9814
|
+
return;
|
|
9815
|
+
}
|
|
9816
|
+
setSubmitting(true);
|
|
9817
|
+
setErrMsg(null);
|
|
9818
|
+
try {
|
|
9819
|
+
const tags = rating != null ? [`rating:${rating}`] : void 0;
|
|
9820
|
+
const item = await submit({
|
|
9821
|
+
type,
|
|
9822
|
+
title: title.trim(),
|
|
9823
|
+
description: description.trim(),
|
|
9824
|
+
email: signedIn ? void 0 : email.trim() || void 0,
|
|
9825
|
+
tags
|
|
9826
|
+
});
|
|
9827
|
+
setDone(true);
|
|
9828
|
+
onSubmitted?.(item);
|
|
9829
|
+
} catch (err) {
|
|
9830
|
+
const message = err && typeof err === "object" && "message" in err ? String(err.message) : "Submit failed";
|
|
9831
|
+
setErrMsg(message);
|
|
9832
|
+
} finally {
|
|
9833
|
+
setSubmitting(false);
|
|
9834
|
+
}
|
|
9835
|
+
}
|
|
9836
|
+
function handleClose() {
|
|
9837
|
+
setOpen(false);
|
|
9838
|
+
setTimeout(reset, 200);
|
|
9839
|
+
}
|
|
9840
|
+
const dark = theme === "dark" || theme === "auto" && typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
9841
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9842
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9843
|
+
"button",
|
|
9844
|
+
{
|
|
9845
|
+
type: "button",
|
|
9846
|
+
onClick: () => setOpen(true),
|
|
9847
|
+
className,
|
|
9848
|
+
style: { ...STYLES.trigger, ...POSITION_STYLES[position] },
|
|
9849
|
+
children: triggerLabel
|
|
9850
|
+
}
|
|
9851
|
+
),
|
|
9852
|
+
open && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "dialog", "aria-modal": "true", style: STYLES.overlay, onClick: handleClose, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9853
|
+
"form",
|
|
9854
|
+
{
|
|
9855
|
+
onSubmit: handleSubmit,
|
|
9856
|
+
onClick: (e) => e.stopPropagation(),
|
|
9857
|
+
style: { ...STYLES.modal, ...dark ? STYLES.modalDark : null },
|
|
9858
|
+
children: done ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9859
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: STYLES.success, children: [
|
|
9860
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 32, marginBottom: 8 }, children: "\u{1F389}" }),
|
|
9861
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { children: "Thanks \u2014 we got it." })
|
|
9862
|
+
] }),
|
|
9863
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.rowEnd, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: STYLES.primaryBtn, onClick: handleClose, children: "Close" }) })
|
|
9864
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9865
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
9866
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 16 }, children: "Send feedback" }),
|
|
9867
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9868
|
+
"button",
|
|
9869
|
+
{
|
|
9870
|
+
type: "button",
|
|
9871
|
+
onClick: handleClose,
|
|
9872
|
+
"aria-label": "Close",
|
|
9873
|
+
style: {
|
|
9874
|
+
background: "transparent",
|
|
9875
|
+
border: "none",
|
|
9876
|
+
fontSize: 18,
|
|
9877
|
+
cursor: "pointer",
|
|
9878
|
+
color: "inherit"
|
|
9879
|
+
},
|
|
9880
|
+
children: "\xD7"
|
|
9881
|
+
}
|
|
9882
|
+
)
|
|
9883
|
+
] }),
|
|
9884
|
+
enableRating && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
9885
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.label, children: ratingLabel }),
|
|
9886
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 4, marginTop: 4 }, children: [
|
|
9887
|
+
[1, 2, 3, 4, 5].map((n) => {
|
|
9888
|
+
const active = (hoverRating ?? rating ?? 0) >= n;
|
|
9889
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9890
|
+
"button",
|
|
9891
|
+
{
|
|
9892
|
+
type: "button",
|
|
9893
|
+
"aria-label": `${n} star${n === 1 ? "" : "s"}`,
|
|
9894
|
+
onClick: () => setRating(rating === n ? null : n),
|
|
9895
|
+
onMouseEnter: () => setHoverRating(n),
|
|
9896
|
+
onMouseLeave: () => setHoverRating(null),
|
|
9897
|
+
style: {
|
|
9898
|
+
background: "transparent",
|
|
9899
|
+
border: "none",
|
|
9900
|
+
cursor: "pointer",
|
|
9901
|
+
padding: 2,
|
|
9902
|
+
fontSize: 24,
|
|
9903
|
+
lineHeight: 1,
|
|
9904
|
+
color: active ? "#f59e0b" : "#cbd5e1",
|
|
9905
|
+
transition: "color 80ms ease"
|
|
9906
|
+
},
|
|
9907
|
+
children: active ? "\u2605" : "\u2606"
|
|
9908
|
+
},
|
|
9909
|
+
n
|
|
9910
|
+
);
|
|
9911
|
+
}),
|
|
9912
|
+
rating != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
9913
|
+
"button",
|
|
9914
|
+
{
|
|
9915
|
+
type: "button",
|
|
9916
|
+
onClick: () => setRating(null),
|
|
9917
|
+
style: {
|
|
9918
|
+
marginLeft: 8,
|
|
9919
|
+
background: "transparent",
|
|
9920
|
+
border: "none",
|
|
9921
|
+
color: "inherit",
|
|
9922
|
+
opacity: 0.6,
|
|
9923
|
+
fontSize: 12,
|
|
9924
|
+
cursor: "pointer"
|
|
9925
|
+
},
|
|
9926
|
+
children: "clear"
|
|
9927
|
+
}
|
|
9928
|
+
)
|
|
9929
|
+
] })
|
|
9930
|
+
] }),
|
|
9931
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
9932
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.label, children: "Type" }),
|
|
9933
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9934
|
+
"select",
|
|
9935
|
+
{
|
|
9936
|
+
style: STYLES.field,
|
|
9937
|
+
value: type,
|
|
9938
|
+
onChange: (e) => setType(e.target.value),
|
|
9939
|
+
children: allowedTypes.map((t) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: t, children: TYPE_LABELS[t] }, t))
|
|
9940
|
+
}
|
|
9941
|
+
)
|
|
9942
|
+
] }),
|
|
9943
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
9944
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.label, children: "Title" }),
|
|
9945
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9946
|
+
"input",
|
|
9947
|
+
{
|
|
9948
|
+
style: STYLES.field,
|
|
9949
|
+
type: "text",
|
|
9950
|
+
value: title,
|
|
9951
|
+
onChange: (e) => setTitle(e.target.value),
|
|
9952
|
+
placeholder: "One-line summary",
|
|
9953
|
+
maxLength: 255,
|
|
9954
|
+
required: true
|
|
9955
|
+
}
|
|
9956
|
+
)
|
|
9957
|
+
] }),
|
|
9958
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
9959
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.label, children: "Details" }),
|
|
9960
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9961
|
+
"textarea",
|
|
9962
|
+
{
|
|
9963
|
+
style: { ...STYLES.field, ...STYLES.textarea },
|
|
9964
|
+
value: description,
|
|
9965
|
+
onChange: (e) => setDescription(e.target.value),
|
|
9966
|
+
placeholder: "What happened, what you expected, anything else helpful\u2026",
|
|
9967
|
+
required: true
|
|
9968
|
+
}
|
|
9969
|
+
)
|
|
9970
|
+
] }),
|
|
9971
|
+
!signedIn && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
9972
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.label, children: "Email" }),
|
|
9973
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9974
|
+
"input",
|
|
9975
|
+
{
|
|
9976
|
+
style: STYLES.field,
|
|
9977
|
+
type: "email",
|
|
9978
|
+
value: email,
|
|
9979
|
+
onChange: (e) => setEmail(e.target.value),
|
|
9980
|
+
placeholder: "you@example.com",
|
|
9981
|
+
required: true
|
|
9982
|
+
}
|
|
9983
|
+
)
|
|
9984
|
+
] }),
|
|
9985
|
+
errMsg && /* @__PURE__ */ jsxRuntime.jsx("div", { style: STYLES.error, children: errMsg }),
|
|
9986
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: STYLES.rowEnd, children: [
|
|
9987
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: STYLES.secondaryBtn, onClick: handleClose, children: "Cancel" }),
|
|
9988
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", style: STYLES.primaryBtn, disabled: submitting, children: submitting ? "Sending\u2026" : "Send" })
|
|
9989
|
+
] })
|
|
9990
|
+
] })
|
|
9991
|
+
}
|
|
9992
|
+
) })
|
|
9993
|
+
] });
|
|
9994
|
+
}
|
|
9605
9995
|
|
|
9606
9996
|
// src/validation.ts
|
|
9607
9997
|
var phoneCountries = [
|
|
@@ -9902,6 +10292,7 @@ Object.defineProperty(exports, "createMoneyClient", {
|
|
|
9902
10292
|
enumerable: true,
|
|
9903
10293
|
get: function () { return money.createMoneyClient; }
|
|
9904
10294
|
});
|
|
10295
|
+
exports.FeedbackWidget = FeedbackWidget;
|
|
9905
10296
|
exports.ScaleMuleApiError = ScaleMuleApiError;
|
|
9906
10297
|
exports.ScaleMuleClient = ScaleMuleClient2;
|
|
9907
10298
|
exports.ScaleMuleProvider = ScaleMuleProvider;
|
|
@@ -9916,6 +10307,7 @@ exports.useAuth = useAuth;
|
|
|
9916
10307
|
exports.useBilling = useBilling;
|
|
9917
10308
|
exports.useContent = useContent;
|
|
9918
10309
|
exports.useFeatureFlags = useFeatureFlags;
|
|
10310
|
+
exports.useFeedback = useFeedback;
|
|
9919
10311
|
exports.useMoney = useMoney;
|
|
9920
10312
|
exports.useMoneyClient = useMoneyClient;
|
|
9921
10313
|
exports.usePushNotifications = usePushNotifications;
|
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;
|
|
@@ -9601,6 +9601,396 @@ function useShare(options) {
|
|
|
9601
9601
|
}, [shareUrl]);
|
|
9602
9602
|
return { shareUrl, referralCode, copyLink, copied, loading };
|
|
9603
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
|
+
enableRating = true,
|
|
9781
|
+
ratingLabel = "How would you rate your experience?",
|
|
9782
|
+
onSubmitted
|
|
9783
|
+
} = props;
|
|
9784
|
+
const sm = useScaleMule();
|
|
9785
|
+
const { submit } = useFeedback({ enabled: false });
|
|
9786
|
+
const [open, setOpen] = useState(false);
|
|
9787
|
+
const [type, setType] = useState(defaultType);
|
|
9788
|
+
const [title, setTitle] = useState("");
|
|
9789
|
+
const [description, setDescription] = useState("");
|
|
9790
|
+
const [email, setEmail] = useState("");
|
|
9791
|
+
const [rating, setRating] = useState(null);
|
|
9792
|
+
const [hoverRating, setHoverRating] = useState(null);
|
|
9793
|
+
const [submitting, setSubmitting] = useState(false);
|
|
9794
|
+
const [errMsg, setErrMsg] = useState(null);
|
|
9795
|
+
const [done, setDone] = useState(false);
|
|
9796
|
+
Boolean(sm.user || sm.user === void 0 ? false : false);
|
|
9797
|
+
const signedIn = Boolean(sm.user);
|
|
9798
|
+
function reset() {
|
|
9799
|
+
setType(defaultType);
|
|
9800
|
+
setTitle("");
|
|
9801
|
+
setDescription("");
|
|
9802
|
+
setEmail("");
|
|
9803
|
+
setRating(null);
|
|
9804
|
+
setHoverRating(null);
|
|
9805
|
+
setErrMsg(null);
|
|
9806
|
+
setDone(false);
|
|
9807
|
+
}
|
|
9808
|
+
async function handleSubmit(e) {
|
|
9809
|
+
e.preventDefault();
|
|
9810
|
+
if (!title.trim() || !description.trim()) return;
|
|
9811
|
+
if (!signedIn && !email.trim()) {
|
|
9812
|
+
setErrMsg("email is required when not signed in");
|
|
9813
|
+
return;
|
|
9814
|
+
}
|
|
9815
|
+
setSubmitting(true);
|
|
9816
|
+
setErrMsg(null);
|
|
9817
|
+
try {
|
|
9818
|
+
const tags = rating != null ? [`rating:${rating}`] : void 0;
|
|
9819
|
+
const item = await submit({
|
|
9820
|
+
type,
|
|
9821
|
+
title: title.trim(),
|
|
9822
|
+
description: description.trim(),
|
|
9823
|
+
email: signedIn ? void 0 : email.trim() || void 0,
|
|
9824
|
+
tags
|
|
9825
|
+
});
|
|
9826
|
+
setDone(true);
|
|
9827
|
+
onSubmitted?.(item);
|
|
9828
|
+
} catch (err) {
|
|
9829
|
+
const message = err && typeof err === "object" && "message" in err ? String(err.message) : "Submit failed";
|
|
9830
|
+
setErrMsg(message);
|
|
9831
|
+
} finally {
|
|
9832
|
+
setSubmitting(false);
|
|
9833
|
+
}
|
|
9834
|
+
}
|
|
9835
|
+
function handleClose() {
|
|
9836
|
+
setOpen(false);
|
|
9837
|
+
setTimeout(reset, 200);
|
|
9838
|
+
}
|
|
9839
|
+
const dark = theme === "dark" || theme === "auto" && typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
9840
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9841
|
+
/* @__PURE__ */ jsx(
|
|
9842
|
+
"button",
|
|
9843
|
+
{
|
|
9844
|
+
type: "button",
|
|
9845
|
+
onClick: () => setOpen(true),
|
|
9846
|
+
className,
|
|
9847
|
+
style: { ...STYLES.trigger, ...POSITION_STYLES[position] },
|
|
9848
|
+
children: triggerLabel
|
|
9849
|
+
}
|
|
9850
|
+
),
|
|
9851
|
+
open && /* @__PURE__ */ jsx("div", { role: "dialog", "aria-modal": "true", style: STYLES.overlay, onClick: handleClose, children: /* @__PURE__ */ jsx(
|
|
9852
|
+
"form",
|
|
9853
|
+
{
|
|
9854
|
+
onSubmit: handleSubmit,
|
|
9855
|
+
onClick: (e) => e.stopPropagation(),
|
|
9856
|
+
style: { ...STYLES.modal, ...dark ? STYLES.modalDark : null },
|
|
9857
|
+
children: done ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9858
|
+
/* @__PURE__ */ jsxs("div", { style: STYLES.success, children: [
|
|
9859
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 32, marginBottom: 8 }, children: "\u{1F389}" }),
|
|
9860
|
+
/* @__PURE__ */ jsx("div", { children: "Thanks \u2014 we got it." })
|
|
9861
|
+
] }),
|
|
9862
|
+
/* @__PURE__ */ jsx("div", { style: STYLES.rowEnd, children: /* @__PURE__ */ jsx("button", { type: "button", style: STYLES.primaryBtn, onClick: handleClose, children: "Close" }) })
|
|
9863
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9864
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
9865
|
+
/* @__PURE__ */ jsx("strong", { style: { fontSize: 16 }, children: "Send feedback" }),
|
|
9866
|
+
/* @__PURE__ */ jsx(
|
|
9867
|
+
"button",
|
|
9868
|
+
{
|
|
9869
|
+
type: "button",
|
|
9870
|
+
onClick: handleClose,
|
|
9871
|
+
"aria-label": "Close",
|
|
9872
|
+
style: {
|
|
9873
|
+
background: "transparent",
|
|
9874
|
+
border: "none",
|
|
9875
|
+
fontSize: 18,
|
|
9876
|
+
cursor: "pointer",
|
|
9877
|
+
color: "inherit"
|
|
9878
|
+
},
|
|
9879
|
+
children: "\xD7"
|
|
9880
|
+
}
|
|
9881
|
+
)
|
|
9882
|
+
] }),
|
|
9883
|
+
enableRating && /* @__PURE__ */ jsxs("div", { children: [
|
|
9884
|
+
/* @__PURE__ */ jsx("div", { style: STYLES.label, children: ratingLabel }),
|
|
9885
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4, marginTop: 4 }, children: [
|
|
9886
|
+
[1, 2, 3, 4, 5].map((n) => {
|
|
9887
|
+
const active = (hoverRating ?? rating ?? 0) >= n;
|
|
9888
|
+
return /* @__PURE__ */ jsx(
|
|
9889
|
+
"button",
|
|
9890
|
+
{
|
|
9891
|
+
type: "button",
|
|
9892
|
+
"aria-label": `${n} star${n === 1 ? "" : "s"}`,
|
|
9893
|
+
onClick: () => setRating(rating === n ? null : n),
|
|
9894
|
+
onMouseEnter: () => setHoverRating(n),
|
|
9895
|
+
onMouseLeave: () => setHoverRating(null),
|
|
9896
|
+
style: {
|
|
9897
|
+
background: "transparent",
|
|
9898
|
+
border: "none",
|
|
9899
|
+
cursor: "pointer",
|
|
9900
|
+
padding: 2,
|
|
9901
|
+
fontSize: 24,
|
|
9902
|
+
lineHeight: 1,
|
|
9903
|
+
color: active ? "#f59e0b" : "#cbd5e1",
|
|
9904
|
+
transition: "color 80ms ease"
|
|
9905
|
+
},
|
|
9906
|
+
children: active ? "\u2605" : "\u2606"
|
|
9907
|
+
},
|
|
9908
|
+
n
|
|
9909
|
+
);
|
|
9910
|
+
}),
|
|
9911
|
+
rating != null && /* @__PURE__ */ jsx(
|
|
9912
|
+
"button",
|
|
9913
|
+
{
|
|
9914
|
+
type: "button",
|
|
9915
|
+
onClick: () => setRating(null),
|
|
9916
|
+
style: {
|
|
9917
|
+
marginLeft: 8,
|
|
9918
|
+
background: "transparent",
|
|
9919
|
+
border: "none",
|
|
9920
|
+
color: "inherit",
|
|
9921
|
+
opacity: 0.6,
|
|
9922
|
+
fontSize: 12,
|
|
9923
|
+
cursor: "pointer"
|
|
9924
|
+
},
|
|
9925
|
+
children: "clear"
|
|
9926
|
+
}
|
|
9927
|
+
)
|
|
9928
|
+
] })
|
|
9929
|
+
] }),
|
|
9930
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
9931
|
+
/* @__PURE__ */ jsx("div", { style: STYLES.label, children: "Type" }),
|
|
9932
|
+
/* @__PURE__ */ jsx(
|
|
9933
|
+
"select",
|
|
9934
|
+
{
|
|
9935
|
+
style: STYLES.field,
|
|
9936
|
+
value: type,
|
|
9937
|
+
onChange: (e) => setType(e.target.value),
|
|
9938
|
+
children: allowedTypes.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: TYPE_LABELS[t] }, t))
|
|
9939
|
+
}
|
|
9940
|
+
)
|
|
9941
|
+
] }),
|
|
9942
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
9943
|
+
/* @__PURE__ */ jsx("div", { style: STYLES.label, children: "Title" }),
|
|
9944
|
+
/* @__PURE__ */ jsx(
|
|
9945
|
+
"input",
|
|
9946
|
+
{
|
|
9947
|
+
style: STYLES.field,
|
|
9948
|
+
type: "text",
|
|
9949
|
+
value: title,
|
|
9950
|
+
onChange: (e) => setTitle(e.target.value),
|
|
9951
|
+
placeholder: "One-line summary",
|
|
9952
|
+
maxLength: 255,
|
|
9953
|
+
required: true
|
|
9954
|
+
}
|
|
9955
|
+
)
|
|
9956
|
+
] }),
|
|
9957
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
9958
|
+
/* @__PURE__ */ jsx("div", { style: STYLES.label, children: "Details" }),
|
|
9959
|
+
/* @__PURE__ */ jsx(
|
|
9960
|
+
"textarea",
|
|
9961
|
+
{
|
|
9962
|
+
style: { ...STYLES.field, ...STYLES.textarea },
|
|
9963
|
+
value: description,
|
|
9964
|
+
onChange: (e) => setDescription(e.target.value),
|
|
9965
|
+
placeholder: "What happened, what you expected, anything else helpful\u2026",
|
|
9966
|
+
required: true
|
|
9967
|
+
}
|
|
9968
|
+
)
|
|
9969
|
+
] }),
|
|
9970
|
+
!signedIn && /* @__PURE__ */ jsxs("label", { children: [
|
|
9971
|
+
/* @__PURE__ */ jsx("div", { style: STYLES.label, children: "Email" }),
|
|
9972
|
+
/* @__PURE__ */ jsx(
|
|
9973
|
+
"input",
|
|
9974
|
+
{
|
|
9975
|
+
style: STYLES.field,
|
|
9976
|
+
type: "email",
|
|
9977
|
+
value: email,
|
|
9978
|
+
onChange: (e) => setEmail(e.target.value),
|
|
9979
|
+
placeholder: "you@example.com",
|
|
9980
|
+
required: true
|
|
9981
|
+
}
|
|
9982
|
+
)
|
|
9983
|
+
] }),
|
|
9984
|
+
errMsg && /* @__PURE__ */ jsx("div", { style: STYLES.error, children: errMsg }),
|
|
9985
|
+
/* @__PURE__ */ jsxs("div", { style: STYLES.rowEnd, children: [
|
|
9986
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: STYLES.secondaryBtn, onClick: handleClose, children: "Cancel" }),
|
|
9987
|
+
/* @__PURE__ */ jsx("button", { type: "submit", style: STYLES.primaryBtn, disabled: submitting, children: submitting ? "Sending\u2026" : "Send" })
|
|
9988
|
+
] })
|
|
9989
|
+
] })
|
|
9990
|
+
}
|
|
9991
|
+
) })
|
|
9992
|
+
] });
|
|
9993
|
+
}
|
|
9604
9994
|
|
|
9605
9995
|
// src/validation.ts
|
|
9606
9996
|
var phoneCountries = [
|
|
@@ -9893,4 +10283,4 @@ function createSafeLogger(prefix) {
|
|
|
9893
10283
|
};
|
|
9894
10284
|
}
|
|
9895
10285
|
|
|
9896
|
-
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 };
|
|
10286
|
+
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 };
|
package/package.json
CHANGED