@appgram/react 0.1.0 → 0.1.3
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/{useVote-CLhkwtLT.d.mts → StatusBoard-DhKRu-An.d.mts} +125 -1
- package/dist/{useVote-CLhkwtLT.d.ts → StatusBoard-DhKRu-An.d.ts} +125 -1
- package/dist/{chunk-N6PJDQCU.mjs → chunk-NABMGLTY.mjs} +210 -6
- package/dist/chunk-NABMGLTY.mjs.map +1 -0
- package/dist/chunk-NSV6Q6QQ.js +202 -0
- package/dist/chunk-NSV6Q6QQ.js.map +1 -0
- package/dist/chunk-ONZ7RQBM.mjs +199 -0
- package/dist/chunk-ONZ7RQBM.mjs.map +1 -0
- package/dist/{chunk-75P634IK.js → chunk-UPTP7QX5.js} +213 -5
- package/dist/chunk-UPTP7QX5.js.map +1 -0
- package/dist/{chunk-AIDLOCVJ.mjs → chunk-UWIJR4ZY.mjs} +760 -15
- package/dist/chunk-UWIJR4ZY.mjs.map +1 -0
- package/dist/{chunk-3UBJGXCO.js → chunk-WZIN7KEM.js} +820 -74
- package/dist/chunk-WZIN7KEM.js.map +1 -0
- package/dist/components/index.d.mts +68 -106
- package/dist/components/index.d.ts +68 -106
- package/dist/components/index.js +29 -21
- package/dist/components/index.mjs +2 -2
- package/dist/hooks/index.d.mts +3 -572
- package/dist/hooks/index.d.ts +3 -572
- package/dist/hooks/index.js +33 -13
- package/dist/hooks/index.mjs +2 -2
- package/dist/index-DpZz_TZE.d.ts +917 -0
- package/dist/index-X95JANOa.d.mts +917 -0
- package/dist/index.d.mts +32 -5
- package/dist/index.d.ts +32 -5
- package/dist/index.js +123 -76
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +60 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-3UBJGXCO.js.map +0 -1
- package/dist/chunk-75P634IK.js.map +0 -1
- package/dist/chunk-AIDLOCVJ.mjs.map +0 -1
- package/dist/chunk-KPIKYXAN.mjs +0 -47
- package/dist/chunk-KPIKYXAN.mjs.map +0 -1
- package/dist/chunk-N6PJDQCU.mjs.map +0 -1
- package/dist/chunk-ZJZ3A2S3.js +0 -49
- package/dist/chunk-ZJZ3A2S3.js.map +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Wish (Feature Request) Types
|
|
3
5
|
*/
|
|
@@ -417,4 +419,126 @@ interface UseVoteResult {
|
|
|
417
419
|
}
|
|
418
420
|
declare function useVote(options: UseVoteOptions): UseVoteResult;
|
|
419
421
|
|
|
420
|
-
|
|
422
|
+
/**
|
|
423
|
+
* StatusBoard Component
|
|
424
|
+
*
|
|
425
|
+
* Modern system status page with service health indicators and incident tracking.
|
|
426
|
+
* Adapted from StatusModern variant.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```tsx
|
|
430
|
+
* import { StatusBoard } from '@appgram/react'
|
|
431
|
+
*
|
|
432
|
+
* <StatusBoard
|
|
433
|
+
* heading="System Status"
|
|
434
|
+
* description="Current operational status of all services"
|
|
435
|
+
* status={statusData}
|
|
436
|
+
* onIncidentClick={(incident) => openIncidentDetail(incident.id)}
|
|
437
|
+
* />
|
|
438
|
+
* ```
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* ```tsx
|
|
442
|
+
* // With custom component rendering
|
|
443
|
+
* <StatusBoard
|
|
444
|
+
* status={statusData}
|
|
445
|
+
* renderComponent={(component) => (
|
|
446
|
+
* <div className="custom-card">
|
|
447
|
+
* <Icon name={component.status} />
|
|
448
|
+
* <span>{component.name}</span>
|
|
449
|
+
* </div>
|
|
450
|
+
* )}
|
|
451
|
+
* />
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
|
|
455
|
+
type OverallStatus = 'operational' | 'degraded' | 'partial_outage' | 'major_outage';
|
|
456
|
+
type ComponentStatus = 'operational' | 'degraded' | 'partial_outage' | 'major_outage';
|
|
457
|
+
type IncidentStatus = 'investigating' | 'identified' | 'monitoring' | 'resolved';
|
|
458
|
+
type IncidentImpact = 'minor' | 'major' | 'critical';
|
|
459
|
+
interface StatusComponent {
|
|
460
|
+
id: string;
|
|
461
|
+
name: string;
|
|
462
|
+
description?: string;
|
|
463
|
+
status: ComponentStatus;
|
|
464
|
+
group?: string;
|
|
465
|
+
}
|
|
466
|
+
interface IncidentUpdate {
|
|
467
|
+
id: string;
|
|
468
|
+
message: string;
|
|
469
|
+
status: IncidentStatus;
|
|
470
|
+
created_at: string;
|
|
471
|
+
}
|
|
472
|
+
interface StatusIncident {
|
|
473
|
+
id: string;
|
|
474
|
+
title: string;
|
|
475
|
+
status: IncidentStatus;
|
|
476
|
+
impact: IncidentImpact;
|
|
477
|
+
created_at: string;
|
|
478
|
+
resolved_at?: string | null;
|
|
479
|
+
updates: IncidentUpdate[];
|
|
480
|
+
affected_components?: string[];
|
|
481
|
+
}
|
|
482
|
+
interface StatusData {
|
|
483
|
+
overall_status: OverallStatus;
|
|
484
|
+
components: StatusComponent[];
|
|
485
|
+
incidents: StatusIncident[];
|
|
486
|
+
last_updated?: string;
|
|
487
|
+
}
|
|
488
|
+
interface StatusBoardProps {
|
|
489
|
+
/**
|
|
490
|
+
* Status data to display
|
|
491
|
+
*/
|
|
492
|
+
status: StatusData;
|
|
493
|
+
/**
|
|
494
|
+
* Page heading
|
|
495
|
+
*/
|
|
496
|
+
heading?: string;
|
|
497
|
+
/**
|
|
498
|
+
* Page description
|
|
499
|
+
*/
|
|
500
|
+
description?: string;
|
|
501
|
+
/**
|
|
502
|
+
* Heading alignment
|
|
503
|
+
* @default 'left'
|
|
504
|
+
*/
|
|
505
|
+
headingAlignment?: 'left' | 'center' | 'right';
|
|
506
|
+
/**
|
|
507
|
+
* Show component descriptions
|
|
508
|
+
* @default true
|
|
509
|
+
*/
|
|
510
|
+
showComponentDescriptions?: boolean;
|
|
511
|
+
/**
|
|
512
|
+
* Show incident history
|
|
513
|
+
* @default true
|
|
514
|
+
*/
|
|
515
|
+
showIncidentHistory?: boolean;
|
|
516
|
+
/**
|
|
517
|
+
* Max number of past incidents to show
|
|
518
|
+
* @default 5
|
|
519
|
+
*/
|
|
520
|
+
maxPastIncidents?: number;
|
|
521
|
+
/**
|
|
522
|
+
* Click handler for incidents
|
|
523
|
+
*/
|
|
524
|
+
onIncidentClick?: (incident: StatusIncident) => void;
|
|
525
|
+
/**
|
|
526
|
+
* Custom render for overall status
|
|
527
|
+
*/
|
|
528
|
+
renderOverallStatus?: (status: OverallStatus) => React.ReactNode;
|
|
529
|
+
/**
|
|
530
|
+
* Custom render for component
|
|
531
|
+
*/
|
|
532
|
+
renderComponent?: (component: StatusComponent) => React.ReactNode;
|
|
533
|
+
/**
|
|
534
|
+
* Custom render for incident
|
|
535
|
+
*/
|
|
536
|
+
renderIncident?: (incident: StatusIncident) => React.ReactNode;
|
|
537
|
+
/**
|
|
538
|
+
* Custom class name
|
|
539
|
+
*/
|
|
540
|
+
className?: string;
|
|
541
|
+
}
|
|
542
|
+
declare function StatusBoard({ status, heading, description, headingAlignment, showComponentDescriptions, showIncidentHistory, maxPastIncidents, onIncidentClick, renderOverallStatus, renderComponent, renderIncident, className, }: StatusBoardProps): React.ReactElement;
|
|
543
|
+
|
|
544
|
+
export { type ArticleType as A, type SupportAttachment as B, type CommentsResponse as C, type SupportMessage as D, type SupportRequestCategory as E, type FlowDisplayType as F, type SupportRequestPriority as G, type HelpCollection as H, type IncidentImpact as I, type SupportRequestStatus as J, type SupportRequestsResponse as K, type UseVoteResult as L, type WishAuthor as M, type WishPriority as N, type OverallStatus as O, type WishStatus as P, useVote as Q, type RoadmapData as R, type SupportRequestInput as S, type UseVoteOptions as U, type WishFilters as W, type WishesResponse as a, type Wish as b, type Comment as c, type Release as d, type ReleaseFeature as e, type HelpFlow as f, type HelpArticle as g, type SupportRequest as h, type Category as i, type CommentAuthor as j, type CommentCreateInput as k, type ComponentStatus as l, type HelpArticlesResponse as m, type HelpCenterData as n, type IncidentStatus as o, type IncidentUpdate as p, type ReleasesResponse as q, type Roadmap as r, type RoadmapColumn as s, type RoadmapItem as t, type RoadmapVisibility as u, StatusBoard as v, type StatusBoardProps as w, type StatusComponent as x, type StatusData as y, type StatusIncident as z };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Wish (Feature Request) Types
|
|
3
5
|
*/
|
|
@@ -417,4 +419,126 @@ interface UseVoteResult {
|
|
|
417
419
|
}
|
|
418
420
|
declare function useVote(options: UseVoteOptions): UseVoteResult;
|
|
419
421
|
|
|
420
|
-
|
|
422
|
+
/**
|
|
423
|
+
* StatusBoard Component
|
|
424
|
+
*
|
|
425
|
+
* Modern system status page with service health indicators and incident tracking.
|
|
426
|
+
* Adapted from StatusModern variant.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```tsx
|
|
430
|
+
* import { StatusBoard } from '@appgram/react'
|
|
431
|
+
*
|
|
432
|
+
* <StatusBoard
|
|
433
|
+
* heading="System Status"
|
|
434
|
+
* description="Current operational status of all services"
|
|
435
|
+
* status={statusData}
|
|
436
|
+
* onIncidentClick={(incident) => openIncidentDetail(incident.id)}
|
|
437
|
+
* />
|
|
438
|
+
* ```
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* ```tsx
|
|
442
|
+
* // With custom component rendering
|
|
443
|
+
* <StatusBoard
|
|
444
|
+
* status={statusData}
|
|
445
|
+
* renderComponent={(component) => (
|
|
446
|
+
* <div className="custom-card">
|
|
447
|
+
* <Icon name={component.status} />
|
|
448
|
+
* <span>{component.name}</span>
|
|
449
|
+
* </div>
|
|
450
|
+
* )}
|
|
451
|
+
* />
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
|
|
455
|
+
type OverallStatus = 'operational' | 'degraded' | 'partial_outage' | 'major_outage';
|
|
456
|
+
type ComponentStatus = 'operational' | 'degraded' | 'partial_outage' | 'major_outage';
|
|
457
|
+
type IncidentStatus = 'investigating' | 'identified' | 'monitoring' | 'resolved';
|
|
458
|
+
type IncidentImpact = 'minor' | 'major' | 'critical';
|
|
459
|
+
interface StatusComponent {
|
|
460
|
+
id: string;
|
|
461
|
+
name: string;
|
|
462
|
+
description?: string;
|
|
463
|
+
status: ComponentStatus;
|
|
464
|
+
group?: string;
|
|
465
|
+
}
|
|
466
|
+
interface IncidentUpdate {
|
|
467
|
+
id: string;
|
|
468
|
+
message: string;
|
|
469
|
+
status: IncidentStatus;
|
|
470
|
+
created_at: string;
|
|
471
|
+
}
|
|
472
|
+
interface StatusIncident {
|
|
473
|
+
id: string;
|
|
474
|
+
title: string;
|
|
475
|
+
status: IncidentStatus;
|
|
476
|
+
impact: IncidentImpact;
|
|
477
|
+
created_at: string;
|
|
478
|
+
resolved_at?: string | null;
|
|
479
|
+
updates: IncidentUpdate[];
|
|
480
|
+
affected_components?: string[];
|
|
481
|
+
}
|
|
482
|
+
interface StatusData {
|
|
483
|
+
overall_status: OverallStatus;
|
|
484
|
+
components: StatusComponent[];
|
|
485
|
+
incidents: StatusIncident[];
|
|
486
|
+
last_updated?: string;
|
|
487
|
+
}
|
|
488
|
+
interface StatusBoardProps {
|
|
489
|
+
/**
|
|
490
|
+
* Status data to display
|
|
491
|
+
*/
|
|
492
|
+
status: StatusData;
|
|
493
|
+
/**
|
|
494
|
+
* Page heading
|
|
495
|
+
*/
|
|
496
|
+
heading?: string;
|
|
497
|
+
/**
|
|
498
|
+
* Page description
|
|
499
|
+
*/
|
|
500
|
+
description?: string;
|
|
501
|
+
/**
|
|
502
|
+
* Heading alignment
|
|
503
|
+
* @default 'left'
|
|
504
|
+
*/
|
|
505
|
+
headingAlignment?: 'left' | 'center' | 'right';
|
|
506
|
+
/**
|
|
507
|
+
* Show component descriptions
|
|
508
|
+
* @default true
|
|
509
|
+
*/
|
|
510
|
+
showComponentDescriptions?: boolean;
|
|
511
|
+
/**
|
|
512
|
+
* Show incident history
|
|
513
|
+
* @default true
|
|
514
|
+
*/
|
|
515
|
+
showIncidentHistory?: boolean;
|
|
516
|
+
/**
|
|
517
|
+
* Max number of past incidents to show
|
|
518
|
+
* @default 5
|
|
519
|
+
*/
|
|
520
|
+
maxPastIncidents?: number;
|
|
521
|
+
/**
|
|
522
|
+
* Click handler for incidents
|
|
523
|
+
*/
|
|
524
|
+
onIncidentClick?: (incident: StatusIncident) => void;
|
|
525
|
+
/**
|
|
526
|
+
* Custom render for overall status
|
|
527
|
+
*/
|
|
528
|
+
renderOverallStatus?: (status: OverallStatus) => React.ReactNode;
|
|
529
|
+
/**
|
|
530
|
+
* Custom render for component
|
|
531
|
+
*/
|
|
532
|
+
renderComponent?: (component: StatusComponent) => React.ReactNode;
|
|
533
|
+
/**
|
|
534
|
+
* Custom render for incident
|
|
535
|
+
*/
|
|
536
|
+
renderIncident?: (incident: StatusIncident) => React.ReactNode;
|
|
537
|
+
/**
|
|
538
|
+
* Custom class name
|
|
539
|
+
*/
|
|
540
|
+
className?: string;
|
|
541
|
+
}
|
|
542
|
+
declare function StatusBoard({ status, heading, description, headingAlignment, showComponentDescriptions, showIncidentHistory, maxPastIncidents, onIncidentClick, renderOverallStatus, renderComponent, renderIncident, className, }: StatusBoardProps): React.ReactElement;
|
|
543
|
+
|
|
544
|
+
export { type ArticleType as A, type SupportAttachment as B, type CommentsResponse as C, type SupportMessage as D, type SupportRequestCategory as E, type FlowDisplayType as F, type SupportRequestPriority as G, type HelpCollection as H, type IncidentImpact as I, type SupportRequestStatus as J, type SupportRequestsResponse as K, type UseVoteResult as L, type WishAuthor as M, type WishPriority as N, type OverallStatus as O, type WishStatus as P, useVote as Q, type RoadmapData as R, type SupportRequestInput as S, type UseVoteOptions as U, type WishFilters as W, type WishesResponse as a, type Wish as b, type Comment as c, type Release as d, type ReleaseFeature as e, type HelpFlow as f, type HelpArticle as g, type SupportRequest as h, type Category as i, type CommentAuthor as j, type CommentCreateInput as k, type ComponentStatus as l, type HelpArticlesResponse as m, type HelpCenterData as n, type IncidentStatus as o, type IncidentUpdate as p, type ReleasesResponse as q, type Roadmap as r, type RoadmapColumn as s, type RoadmapItem as t, type RoadmapVisibility as u, StatusBoard as v, type StatusBoardProps as w, type StatusComponent as x, type StatusData as y, type StatusIncident as z };
|
|
@@ -44,9 +44,7 @@ function getBrowserCharacteristics() {
|
|
|
44
44
|
function generateFingerprint() {
|
|
45
45
|
const characteristics = getBrowserCharacteristics();
|
|
46
46
|
const timestamp = Date.now().toString(36);
|
|
47
|
-
const
|
|
48
|
-
crypto.getRandomValues(randomBytes);
|
|
49
|
-
const random = Array.from(randomBytes, (b) => b.toString(36)).join("").substring(0, 10);
|
|
47
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
50
48
|
return `${simpleHash(characteristics)}-${timestamp}-${random}`;
|
|
51
49
|
}
|
|
52
50
|
function getFingerprint() {
|
|
@@ -735,7 +733,213 @@ function useSupport(options = {}) {
|
|
|
735
733
|
clearMessages
|
|
736
734
|
};
|
|
737
735
|
}
|
|
736
|
+
function useSurvey(slug, options = {}) {
|
|
737
|
+
const { enabled = true } = options;
|
|
738
|
+
const { client } = useAppgramContext();
|
|
739
|
+
const [survey, setSurvey] = useState(null);
|
|
740
|
+
const [nodes, setNodes] = useState([]);
|
|
741
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
742
|
+
const [error, setError] = useState(null);
|
|
743
|
+
const fetchSurvey = useCallback(async () => {
|
|
744
|
+
if (!slug) return;
|
|
745
|
+
setIsLoading(true);
|
|
746
|
+
setError(null);
|
|
747
|
+
try {
|
|
748
|
+
const response = await client.getPublicSurvey(slug);
|
|
749
|
+
if (response.success && response.data) {
|
|
750
|
+
const { nodes: surveyNodes, ...surveyData } = response.data;
|
|
751
|
+
setSurvey(surveyData);
|
|
752
|
+
setNodes(surveyNodes || []);
|
|
753
|
+
} else {
|
|
754
|
+
setError(getErrorMessage(response.error, "Failed to fetch survey"));
|
|
755
|
+
}
|
|
756
|
+
} catch (err) {
|
|
757
|
+
setError(getErrorMessage(err, "An error occurred"));
|
|
758
|
+
} finally {
|
|
759
|
+
setIsLoading(false);
|
|
760
|
+
}
|
|
761
|
+
}, [client, slug]);
|
|
762
|
+
useEffect(() => {
|
|
763
|
+
if (enabled) {
|
|
764
|
+
fetchSurvey();
|
|
765
|
+
}
|
|
766
|
+
}, [enabled, fetchSurvey]);
|
|
767
|
+
return {
|
|
768
|
+
survey,
|
|
769
|
+
nodes,
|
|
770
|
+
isLoading,
|
|
771
|
+
error,
|
|
772
|
+
refetch: fetchSurvey
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function useSurveySubmit(options = {}) {
|
|
776
|
+
const { client } = useAppgramContext();
|
|
777
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
778
|
+
const [error, setError] = useState(null);
|
|
779
|
+
const [successMessage, setSuccessMessage] = useState(null);
|
|
780
|
+
const clearMessages = useCallback(() => {
|
|
781
|
+
setError(null);
|
|
782
|
+
setSuccessMessage(null);
|
|
783
|
+
}, []);
|
|
784
|
+
const submitResponse = useCallback(
|
|
785
|
+
async (surveyId, data) => {
|
|
786
|
+
setIsSubmitting(true);
|
|
787
|
+
setError(null);
|
|
788
|
+
setSuccessMessage(null);
|
|
789
|
+
try {
|
|
790
|
+
const response = await client.submitSurveyResponse(surveyId, data);
|
|
791
|
+
if (response.success && response.data) {
|
|
792
|
+
setSuccessMessage("Survey response submitted successfully.");
|
|
793
|
+
options.onSuccess?.(response.data);
|
|
794
|
+
return response.data;
|
|
795
|
+
} else {
|
|
796
|
+
const errorMsg = getErrorMessage(response.error, "Failed to submit survey response");
|
|
797
|
+
setError(errorMsg);
|
|
798
|
+
options.onError?.(errorMsg);
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
} catch (err) {
|
|
802
|
+
const errorMsg = getErrorMessage(err, "An error occurred");
|
|
803
|
+
setError(errorMsg);
|
|
804
|
+
options.onError?.(errorMsg);
|
|
805
|
+
return null;
|
|
806
|
+
} finally {
|
|
807
|
+
setIsSubmitting(false);
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
[client, options]
|
|
811
|
+
);
|
|
812
|
+
return {
|
|
813
|
+
isSubmitting,
|
|
814
|
+
error,
|
|
815
|
+
successMessage,
|
|
816
|
+
submitResponse,
|
|
817
|
+
clearMessages
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function useContactForm(formId, options = {}) {
|
|
821
|
+
const { enabled = true } = options;
|
|
822
|
+
const { client } = useAppgramContext();
|
|
823
|
+
const [form, setForm] = useState(null);
|
|
824
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
825
|
+
const [error, setError] = useState(null);
|
|
826
|
+
const fetchForm = useCallback(async () => {
|
|
827
|
+
if (!formId) return;
|
|
828
|
+
setIsLoading(true);
|
|
829
|
+
setError(null);
|
|
830
|
+
try {
|
|
831
|
+
const response = await client.getPublicForm(formId);
|
|
832
|
+
if (response.success && response.data) {
|
|
833
|
+
setForm(response.data);
|
|
834
|
+
} else {
|
|
835
|
+
setError(getErrorMessage(response.error, "Failed to fetch form"));
|
|
836
|
+
}
|
|
837
|
+
} catch (err) {
|
|
838
|
+
setError(getErrorMessage(err, "An error occurred"));
|
|
839
|
+
} finally {
|
|
840
|
+
setIsLoading(false);
|
|
841
|
+
}
|
|
842
|
+
}, [client, formId]);
|
|
843
|
+
useEffect(() => {
|
|
844
|
+
if (enabled) {
|
|
845
|
+
fetchForm();
|
|
846
|
+
}
|
|
847
|
+
}, [enabled, fetchForm]);
|
|
848
|
+
return {
|
|
849
|
+
form,
|
|
850
|
+
isLoading,
|
|
851
|
+
error,
|
|
852
|
+
refetch: fetchForm
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function useContactFormSubmit(options = {}) {
|
|
856
|
+
const { rateLimitMs = 5e3 } = options;
|
|
857
|
+
const { client } = useAppgramContext();
|
|
858
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
859
|
+
const [error, setError] = useState(null);
|
|
860
|
+
const [successMessage, setSuccessMessage] = useState(null);
|
|
861
|
+
const [isRateLimited, setIsRateLimited] = useState(false);
|
|
862
|
+
const lastSubmitRef = useRef(0);
|
|
863
|
+
const clearMessages = useCallback(() => {
|
|
864
|
+
setError(null);
|
|
865
|
+
setSuccessMessage(null);
|
|
866
|
+
}, []);
|
|
867
|
+
const validateField = useCallback(
|
|
868
|
+
(value, field) => {
|
|
869
|
+
if (field.required && !value.trim()) {
|
|
870
|
+
return "This field is required";
|
|
871
|
+
}
|
|
872
|
+
if (field.type === "email" && value) {
|
|
873
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
874
|
+
if (!emailRegex.test(value)) {
|
|
875
|
+
return "Please enter a valid email address";
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (field.validation) {
|
|
879
|
+
if (field.validation.minLength && value.length < field.validation.minLength) {
|
|
880
|
+
return `Must be at least ${field.validation.minLength} characters`;
|
|
881
|
+
}
|
|
882
|
+
if (field.validation.maxLength && value.length > field.validation.maxLength) {
|
|
883
|
+
return `Must be no more than ${field.validation.maxLength} characters`;
|
|
884
|
+
}
|
|
885
|
+
if (field.validation.pattern) {
|
|
886
|
+
const regex = new RegExp(field.validation.pattern);
|
|
887
|
+
if (!regex.test(value)) {
|
|
888
|
+
return "Invalid format";
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return null;
|
|
893
|
+
},
|
|
894
|
+
[]
|
|
895
|
+
);
|
|
896
|
+
const submitForm = useCallback(
|
|
897
|
+
async (projectId, formId, data) => {
|
|
898
|
+
const now = Date.now();
|
|
899
|
+
if (now - lastSubmitRef.current < rateLimitMs) {
|
|
900
|
+
setIsRateLimited(true);
|
|
901
|
+
setError("Please wait before submitting again.");
|
|
902
|
+
setTimeout(() => setIsRateLimited(false), rateLimitMs - (now - lastSubmitRef.current));
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
setIsSubmitting(true);
|
|
906
|
+
setError(null);
|
|
907
|
+
setSuccessMessage(null);
|
|
908
|
+
try {
|
|
909
|
+
const response = await client.submitContactForm(projectId, formId, data);
|
|
910
|
+
if (response.success && response.data) {
|
|
911
|
+
lastSubmitRef.current = Date.now();
|
|
912
|
+
setSuccessMessage("Form submitted successfully.");
|
|
913
|
+
options.onSuccess?.(response.data);
|
|
914
|
+
return response.data;
|
|
915
|
+
} else {
|
|
916
|
+
const errorMsg = getErrorMessage(response.error, "Failed to submit form");
|
|
917
|
+
setError(errorMsg);
|
|
918
|
+
options.onError?.(errorMsg);
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
921
|
+
} catch (err) {
|
|
922
|
+
const errorMsg = getErrorMessage(err, "An error occurred");
|
|
923
|
+
setError(errorMsg);
|
|
924
|
+
options.onError?.(errorMsg);
|
|
925
|
+
return null;
|
|
926
|
+
} finally {
|
|
927
|
+
setIsSubmitting(false);
|
|
928
|
+
}
|
|
929
|
+
},
|
|
930
|
+
[client, options, rateLimitMs]
|
|
931
|
+
);
|
|
932
|
+
return {
|
|
933
|
+
isSubmitting,
|
|
934
|
+
error,
|
|
935
|
+
successMessage,
|
|
936
|
+
isRateLimited,
|
|
937
|
+
submitForm,
|
|
938
|
+
validateField,
|
|
939
|
+
clearMessages
|
|
940
|
+
};
|
|
941
|
+
}
|
|
738
942
|
|
|
739
|
-
export { AppgramContext, cn, getErrorMessage, getFingerprint, resetFingerprint, useAppgramContext, useComments, useHelpArticle, useHelpCenter, useHelpFlow, useRelease, useReleases, useRoadmap, useSupport, useVote, useWishes };
|
|
740
|
-
//# sourceMappingURL=chunk-
|
|
741
|
-
//# sourceMappingURL=chunk-
|
|
943
|
+
export { AppgramContext, cn, getErrorMessage, getFingerprint, resetFingerprint, useAppgramContext, useComments, useContactForm, useContactFormSubmit, useHelpArticle, useHelpCenter, useHelpFlow, useRelease, useReleases, useRoadmap, useSupport, useSurvey, useSurveySubmit, useVote, useWishes };
|
|
944
|
+
//# sourceMappingURL=chunk-NABMGLTY.mjs.map
|
|
945
|
+
//# sourceMappingURL=chunk-NABMGLTY.mjs.map
|