@mediansh/widget 0.6.0 → 0.8.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.d.ts CHANGED
@@ -79,6 +79,18 @@ type MedianSupportOptions = {
79
79
  * first reply asking. Pass `false` and none of it is read at all.
80
80
  */
81
81
  telemetry?: boolean;
82
+ /**
83
+ * Whether the agent may take a picture of this page when it asks to see
84
+ * it. Drawn from the page itself rather than recorded off the screen, so
85
+ * nobody is asked for permission to share anything, and only when the
86
+ * agent asks: it says it is taking a look, and the picture lands in the
87
+ * thread where the visitor can see what was sent. On by default. Pass
88
+ * `false` on a page showing somebody else's data and the agent is told no
89
+ * picture is coming.
90
+ *
91
+ * Never the panel itself or anything else Median renders.
92
+ */
93
+ screenshot?: boolean;
82
94
  /**
83
95
  * Take questions from signed in people only.
84
96
  *
@@ -134,6 +146,17 @@ type MedianSupportOptions = {
134
146
  * either side saying no is enough to keep the card away.
135
147
  */
136
148
  messagePreview?: boolean;
149
+ /**
150
+ * Put the unread count in the tab's title, as `(1) Your page`, while a
151
+ * reply is waiting. On by default: it is the one part of your page a
152
+ * visitor can see from another tab. Your own title is kept and put back
153
+ * once the reply has been read.
154
+ *
155
+ * <MedianSupport apiKey="median_pk_..." titleCount={false} />
156
+ *
157
+ * Pass `false` and the title is never touched.
158
+ */
159
+ titleCount?: boolean;
137
160
  /**
138
161
  * Watch what goes wrong on the page, so a bug report arrives with the
139
162
  * error already attached instead of after four messages of asking.
@@ -260,19 +283,26 @@ type MedianSupportProps = MedianSupportOptions & ({
260
283
  declare function MedianSupport(props: MedianSupportProps): react.JSX.Element;
261
284
 
262
285
  /**
263
- * What a mounted widget hands back: the two things a host without React has
264
- * no re-render to do for them. Everything else goes through `medianSupport`,
265
- * same as anywhere.
286
+ * What a mounted component hands back: the two things a host without React
287
+ * has no re-render to do for them. Everything else goes through the
288
+ * component's own control, same as anywhere.
266
289
  */
267
- type MedianSupportInstance = {
290
+ type MedianMountInstance<Options> = {
268
291
  /**
269
292
  * Change any option after mount. Only the keys passed change, so
270
293
  * `update({ user })` when somebody signs in leaves the rest alone.
271
294
  */
272
- update: (options: Partial<MedianSupportProps>) => void;
273
- /** Take the widget off the page: panel, launcher, connection, all of it. */
295
+ update: (options: Partial<Options>) => void;
296
+ /** Take it off the page: the component, its container, its connection. */
274
297
  unmount: () => void;
275
298
  };
299
+
300
+ /**
301
+ * What a mounted widget hands back: the two things a host without React has
302
+ * no re-render to do for them. Everything else goes through `medianSupport`,
303
+ * same as anywhere.
304
+ */
305
+ type MedianSupportInstance = MedianMountInstance<MedianSupportProps>;
276
306
  /**
277
307
  * The same widget, for a page that has never heard of React: Vue, Svelte,
278
308
  * Rails, a script on plain HTML. One call puts the launcher in the corner,
@@ -293,6 +323,43 @@ type MedianSupportInstance = {
293
323
  */
294
324
  declare function mountMedianSupport(options: MedianSupportProps): MedianSupportInstance;
295
325
 
326
+ /**
327
+ * Every prop the widget takes, unchanged. The modal is the widget in a
328
+ * different place, not a different widget.
329
+ */
330
+ type MedianSupportModalProps = MedianSupportProps;
331
+ /**
332
+ * The support widget as a modal: the same button in the corner, and a panel
333
+ * that opens in the middle of the screen over a scrim rather than out of
334
+ * the corner.
335
+ *
336
+ * <MedianSupportModal apiKey="median_pk_..." />
337
+ *
338
+ * Everything else is the widget's. Same props, same thread, same inbox on
339
+ * the other side, same `useMedianSupport()` to open it from your own nav,
340
+ * same sheet on a phone. Pick this for a site where a conversation deserves
341
+ * the room, or where a panel floating over the page would be in the way of
342
+ * the page.
343
+ *
344
+ * One or the other. Mounting both shares one open state, and the console
345
+ * says so.
346
+ */
347
+ declare function MedianSupportModal(props: MedianSupportModalProps): react.JSX.Element;
348
+
349
+ type MedianSupportModalInstance = MedianMountInstance<MedianSupportModalProps>;
350
+ /**
351
+ * The modal, for a page that has never heard of React: Vue, Svelte, Rails,
352
+ * a script on plain HTML.
353
+ *
354
+ * const support = mountMedianSupportModal({ apiKey: "median_pk_..." });
355
+ *
356
+ * It takes the component's props as one options object, and the rest of the
357
+ * SDK does not know the difference: `medianSupport.open()` opens this modal
358
+ * exactly as it opens the component. `update` and `unmount` are the same two
359
+ * `mountMedianSupport` hands back.
360
+ */
361
+ declare function mountMedianSupportModal(options: MedianSupportModalProps): MedianSupportModalInstance;
362
+
296
363
  /**
297
364
  * Filing the crash the visitor is looking at.
298
365
  *
@@ -334,6 +401,12 @@ type MedianErrorReportOptions = {
334
401
  attach?: Record<string, unknown>;
335
402
  /** Whether the report carries what the browser knows. On by default. */
336
403
  telemetry?: boolean;
404
+ /**
405
+ * Whether the report carries a picture of the screen it was filed from. On
406
+ * by default: what a crash screen actually looks like is the one thing the
407
+ * stack does not say. Pass `false` and no picture is taken.
408
+ */
409
+ screenshot?: boolean;
337
410
  };
338
411
  /** Where the report went, or why it did not go. */
339
412
  type MedianErrorReportOutcome = {
@@ -358,12 +431,14 @@ declare function useCanReportError(): boolean;
358
431
  /**
359
432
  * The widget from anywhere, React or not: the same shape the hook hands
360
433
  * back, for a plain `onclick` or a shortcut listener that never sees a
361
- * component. `isOpen` reads live but does not re-render anything; the hook
362
- * is for that.
434
+ * component. `isOpen` and `unreadCount` read live but do not re-render
435
+ * anything; the hook is for that.
363
436
  */
364
437
  declare const medianSupport: {
365
438
  /** What the panel is doing right now. */
366
439
  readonly isOpen: boolean;
440
+ /** Replies the visitor has not read. Zero while nothing is mounted. */
441
+ readonly unreadCount: number;
367
442
  open(): void;
368
443
  close(): void;
369
444
  toggle(): void;
@@ -396,6 +471,13 @@ declare const medianSupport: {
396
471
  type MedianSupportControl = {
397
472
  /** Whether the panel is open. */
398
473
  isOpen: boolean;
474
+ /**
475
+ * Replies the visitor has not read: the same count the closed button
476
+ * wears and the tab's title carries. For a badge on a button of your own,
477
+ * on a page where the launcher is hidden. Zero while the panel is open,
478
+ * once it has been looked at, and whenever no panel is mounted.
479
+ */
480
+ unreadCount: number;
399
481
  open: () => void;
400
482
  close: () => void;
401
483
  toggle: () => void;
@@ -411,10 +493,15 @@ type MedianSupportControl = {
411
493
  *
412
494
  * const support = useMedianSupport();
413
495
  *
414
- * <button onClick={support.open}>Contact us</button>
496
+ * <button onClick={support.open}>
497
+ * Contact us
498
+ * {support.unreadCount > 0 && <span>{support.unreadCount}</span>}
499
+ * </button>
415
500
  *
416
501
  * `isOpen` follows the panel however it was opened, so a trigger can render
417
502
  * itself as pressed, or step out of the way while the panel is up.
503
+ * `unreadCount` follows the thread, so the trigger can say a reply is
504
+ * waiting where the corner button would have.
418
505
  */
419
506
  declare function useMedianSupport(): MedianSupportControl;
420
507
 
@@ -459,6 +546,17 @@ type MedianFeedbackOptions = {
459
546
  * read at all.
460
547
  */
461
548
  telemetry?: boolean;
549
+ /**
550
+ * Whether a note carries a picture of the page it was written on, drawn
551
+ * from the page itself rather than recorded off the screen, so nobody is
552
+ * asked for permission to share anything. On by default: a bug report with
553
+ * the screen on it is one nobody has to describe. Pass `false` and no
554
+ * picture is taken.
555
+ *
556
+ * The panel itself is never in the picture, and neither is anything else
557
+ * Median renders.
558
+ */
559
+ screenshot?: boolean;
462
560
  /**
463
561
  * Watch what goes wrong on the page, so a bug filed from a note arrives
464
562
  * with the error already attached. The same switch `<MedianSupport>`
@@ -522,8 +620,8 @@ type MedianFeedbackMountOptions = Omit<MedianFeedbackOptions, "children"> & ({
522
620
  * <MenuItem>Share feedback</MenuItem>
523
621
  * </MedianFeedback>
524
622
  *
525
- * The page they were on rides along on its own, which the line under the
526
- * title says plainly.
623
+ * The page they were on rides along on its own, and so does a picture of it.
624
+ * `screenshot={false}` leaves the picture behind.
527
625
  *
528
626
  * It brings its own connection, so it works on pages that have never heard
529
627
  * of Convex, and it is independent of `<MedianSupport>`: mount either, both,
@@ -537,15 +635,7 @@ declare function MedianFeedback(props: MedianFeedbackProps): react.JSX.Element;
537
635
  * no re-render to do for them. Opening and closing go through
538
636
  * `medianFeedback`, same as anywhere.
539
637
  */
540
- type MedianFeedbackInstance = {
541
- /**
542
- * Change any option after mount. Only the keys passed change, so
543
- * `update({ user })` when somebody signs in leaves the rest alone.
544
- */
545
- update: (options: Partial<MedianFeedbackProps>) => void;
546
- /** Take the panel off the page. */
547
- unmount: () => void;
548
- };
638
+ type MedianFeedbackInstance = MedianMountInstance<MedianFeedbackProps>;
549
639
  /**
550
640
  * The same panel, for a page that has never heard of React: Vue, Svelte,
551
641
  * Rails, a script on plain HTML.
@@ -587,4 +677,300 @@ type MedianFeedbackControl = {
587
677
  */
588
678
  declare function useMedianFeedback(): MedianFeedbackControl;
589
679
 
590
- export { type MedianDiagnostics, type MedianErrorReportOptions, type MedianErrorReportOutcome, MedianFeedback, type MedianFeedbackControl, type MedianFeedbackInstance, type MedianFeedbackProps, MedianSupport, type MedianSupportControl, type MedianSupportInstance, type MedianSupportProps, type MedianUser, type MedianDiagnostics as PortalDiagnostics, type MedianErrorReportOptions as PortalErrorReportOptions, type MedianErrorReportOutcome as PortalErrorReportOutcome, MedianFeedback as PortalFeedback, type MedianFeedbackControl as PortalFeedbackControl, type MedianFeedbackInstance as PortalFeedbackInstance, type MedianFeedbackProps as PortalFeedbackProps, MedianSupport as PortalSupport, type MedianSupportControl as PortalSupportControl, type MedianSupportInstance as PortalSupportInstance, type MedianSupportProps as PortalSupportProps, type MedianUser as PortalUser, type SupportLauncherKind, medianFeedback, medianSupport, mountMedianFeedback, mountMedianSupport, mountMedianFeedback as mountPortalFeedback, mountMedianSupport as mountPortalSupport, medianFeedback as portalFeedback, medianSupport as portalSupport, reportError, useCanReportError, useMedianFeedback, useMedianSupport, useMedianFeedback as usePortalFeedback, useMedianSupport as usePortalSupport };
680
+ /**
681
+ * What a host can put on the contact form, and what the form always has.
682
+ *
683
+ * Two fields are on every form whatever the host says: an email, because a
684
+ * message nobody can reply to is a message nobody sent, and the message
685
+ * itself. Everything else is the host's to add: a name, a topic, an order
686
+ * number, whatever their team wants to know before they read.
687
+ */
688
+ /** A line, a paragraph, or one of a list. */
689
+ type MedianContactFieldKind = "text" | "textarea" | "select";
690
+ /**
691
+ * One field a host adds to the form.
692
+ *
693
+ * { name: "topic", label: "Topic", type: "select", options: ["Billing", "A bug"] }
694
+ *
695
+ * Three names are reserved. `email` and `message` are the two the form always
696
+ * has, and naming either here only changes its label and placeholder. `name`
697
+ * lands on the customer record beside the email rather than in the message,
698
+ * so the team sees who wrote rather than a line saying so.
699
+ */
700
+ type MedianContactField = {
701
+ /** Its key. Rides the message as the label on its line. */
702
+ name: string;
703
+ /** Shown above the box. Made from `name` when left out. */
704
+ label?: string;
705
+ /** `text` by default. Ignored on `email` and `message`, which know their shape. */
706
+ type?: MedianContactFieldKind;
707
+ /** What a `select` offers. A select with none falls back to a text box. */
708
+ options?: string[];
709
+ placeholder?: string;
710
+ /** Off by default. `email` and `message` are required whatever this says. */
711
+ required?: boolean;
712
+ };
713
+
714
+ type MedianContactFormOptions = {
715
+ /**
716
+ * What the form asks, beyond an email and a message, which it always asks:
717
+ *
718
+ * <MedianContactForm
719
+ * apiKey="median_pk_..."
720
+ * fields={[
721
+ * { name: "name", label: "Name", required: true },
722
+ * { name: "topic", type: "select", options: ["Billing", "A bug", "Something else"] },
723
+ * ]}
724
+ * />
725
+ *
726
+ * Shown in this order. Email goes first, or right after a `name` field,
727
+ * and the message goes last, unless either is placed here by name. A
728
+ * `name` field lands on the customer record; every other answer goes
729
+ * above the message as a line of its own.
730
+ */
731
+ fields?: MedianContactField[];
732
+ /**
733
+ * Who is signed in on your site, if anyone. Their email and name fill the
734
+ * boxes in, and the rest is shown to your team.
735
+ */
736
+ user?: MedianUser;
737
+ /**
738
+ * Whether the message carries what the browser already knows: time zone,
739
+ * language, browser, OS, device, screen, and the page it was sent from.
740
+ * On by default. Pass `false` and none of it is read at all.
741
+ */
742
+ telemetry?: boolean;
743
+ /**
744
+ * Watch what goes wrong on the page, so a bug report filed from the
745
+ * message arrives with the error already attached. The same switch
746
+ * `<MedianSupport>` takes, and the same buffer behind it.
747
+ */
748
+ diagnostics?: MedianDiagnostics;
749
+ /**
750
+ * Anything about your own app worth having next to a bug report: a build,
751
+ * a route, a flag. Read when the form is sent.
752
+ */
753
+ appState?: Record<string, string | number | boolean>;
754
+ /** The heading, for a page that calls this something else. */
755
+ title?: string;
756
+ /** The line under it. */
757
+ description?: string;
758
+ /**
759
+ * The message landed. For whatever your page does next: a thank-you of its
760
+ * own, a redirect, an event. The form has already said thank you itself.
761
+ */
762
+ onSent?: () => void;
763
+ };
764
+ /**
765
+ * Two ways in, the same two `<MedianSupport>` takes.
766
+ *
767
+ * `apiKey` is the publishable id on its own. `identity` is the path to a
768
+ * route built with `medianIdentity`, which answers with the key and with
769
+ * whoever is signed in, already signed. Pass both and the form is up on the
770
+ * key from the prop while the route is still answering.
771
+ */
772
+ type MedianContactFormProps = MedianContactFormOptions & ({
773
+ apiKey: string;
774
+ identity?: string;
775
+ } | {
776
+ apiKey?: string;
777
+ identity: string;
778
+ });
779
+ /**
780
+ * A contact form, for the page where a chat panel is the wrong shape.
781
+ *
782
+ * <MedianContactForm apiKey="median_pk_..." />
783
+ *
784
+ * An email and a message, on a card the shape of the support panel, with
785
+ * whatever else your team wants asked first. What is sent starts a
786
+ * conversation in your inbox exactly as the widget would: the agent answers
787
+ * first, your team picks it up, and the reply goes to the email given.
788
+ *
789
+ * Built on `useMedianContact`, which is also what a form of your own uses.
790
+ * It needs no connection of its own, and it is independent of
791
+ * `<MedianSupport>`: mount either, both, or neither. Mounted together they
792
+ * share a visitor, so somebody who writes here and later opens the widget
793
+ * finds the same thread.
794
+ */
795
+ declare function MedianContactForm(props: MedianContactFormProps): react.JSX.Element;
796
+
797
+ /**
798
+ * The component's props, plus where on the page the form goes.
799
+ *
800
+ * Spelled out rather than `Omit`ped off the props, because omitting a key
801
+ * from a union of two shapes flattens it into one and loses the "either,
802
+ * but not neither" the union is there to say.
803
+ */
804
+ type MedianContactFormMountOptions = MedianContactFormProps & {
805
+ /**
806
+ * The element the form is rendered into, or a selector for it:
807
+ *
808
+ * mountMedianContactForm({ target: "#contact", apiKey: "median_pk_..." });
809
+ *
810
+ * Unlike the widget, a form has no corner to go to, so this is required.
811
+ */
812
+ target: string | Element;
813
+ };
814
+ type MedianContactFormInstance = MedianMountInstance<MedianContactFormProps>;
815
+ /**
816
+ * The same form, for a page that has never heard of React: Vue, Svelte,
817
+ * Rails, a script on plain HTML.
818
+ *
819
+ * const form = mountMedianContactForm({
820
+ * target: "#contact",
821
+ * apiKey: "median_pk_...",
822
+ * fields: [{ name: "name", label: "Name" }],
823
+ * });
824
+ *
825
+ * It takes the component's props as one options object, plus `target`, and
826
+ * renders into a container of its own inside that element with the
827
+ * package's copy of React. Whatever else the element holds is left alone.
828
+ */
829
+ declare function mountMedianContactForm({ target, ...options }: MedianContactFormMountOptions): MedianContactFormInstance;
830
+
831
+ /**
832
+ * What a contact form hands over, ours or a host's own.
833
+ *
834
+ * The shape is deliberately small: the two things a message needs to be
835
+ * answered, who wrote it if they said, and whatever else the form asked as
836
+ * label to answer. Building one from a `<form>` of your own is a few lines.
837
+ */
838
+ type MedianContactMessage = {
839
+ /** Where the reply goes. */
840
+ email: string;
841
+ /** What they wrote. */
842
+ message: string;
843
+ /** Lands on the customer record beside the email. */
844
+ name?: string;
845
+ /**
846
+ * Everything else the form asked, as label to answer. Each goes above the
847
+ * message as a line of its own, so the team reads the form the way it was
848
+ * filled in:
849
+ *
850
+ * { Topic: "Billing", "Order number": "4821" }
851
+ *
852
+ * Empty answers are left off.
853
+ */
854
+ fields?: Record<string, string>;
855
+ };
856
+
857
+ /**
858
+ * Sending a contact message, for a form of the host's own.
859
+ *
860
+ * The same door the widget's composer uses, on the same session token, so
861
+ * what a host's form sends starts the thread the corner would have started:
862
+ * the team answers it from the inbox, the agent goes first, and with no
863
+ * panel open to watch, the reply goes to the email given. Somebody who
864
+ * writes here and later opens the widget finds their own conversation.
865
+ *
866
+ * A stateless HTTP client rather than the subscribing one, because nothing
867
+ * is read back: a form is a thing you hand over, not a thread you watch.
868
+ */
869
+ /** What a send is told about who is sending and from where. */
870
+ type MedianContactOptions = {
871
+ /** The browser-safe median_pk_ id. */
872
+ apiKey: string;
873
+ /**
874
+ * Who is signed in on your site, if anyone. What the form says about the
875
+ * email and the name wins over this; the rest is shown to your team.
876
+ */
877
+ user?: MedianUser;
878
+ /**
879
+ * Whether the message carries what the browser already knows: time zone,
880
+ * language, browser, OS, device, screen, and the page it was sent from.
881
+ * On by default. Pass `false` and none of it is read at all.
882
+ */
883
+ telemetry?: boolean;
884
+ };
885
+ /**
886
+ * Send a contact message from a form of your own, on any page in the
887
+ * browser:
888
+ *
889
+ * await sendMedianContact(
890
+ * { apiKey: "median_pk_..." },
891
+ * { email, message, name, fields: { Topic: topic } },
892
+ * );
893
+ *
894
+ * It resolves once the message is in your inbox and rejects with an `Error`
895
+ * whose `message` is worth showing above your button. Nothing about the
896
+ * form is ours: any `<form>`, any framework, any styling.
897
+ *
898
+ * In React, `useMedianContact()` is the same call with `isSending` and
899
+ * `error` kept for you, and an identity route resolved.
900
+ */
901
+ declare function sendMedianContact(options: MedianContactOptions, message: MedianContactMessage): Promise<void>;
902
+
903
+ type MedianContactHookOptions = {
904
+ /** Who is signed in on your site, if anyone. */
905
+ user?: MedianUser;
906
+ /** Browser context on the message. On by default. */
907
+ telemetry?: boolean;
908
+ /**
909
+ * Watch what goes wrong on the page, so a bug report filed from the
910
+ * message arrives with the error already attached. The same switch
911
+ * `<MedianSupport>` takes, and the same buffer behind it.
912
+ */
913
+ diagnostics?: MedianDiagnostics;
914
+ /** Anything about your own app worth having next to a bug report. */
915
+ appState?: Record<string, string | number | boolean>;
916
+ };
917
+ /**
918
+ * Two ways in, the same two every component takes: the publishable key, or
919
+ * the path of a route built with `medianIdentity`, which answers with the
920
+ * key and whoever is signed in.
921
+ */
922
+ type UseMedianContactOptions = MedianContactHookOptions & ({
923
+ apiKey: string;
924
+ identity?: string;
925
+ } | {
926
+ apiKey?: string;
927
+ identity: string;
928
+ });
929
+ /** What the hook hands back: one call, and the state around it. */
930
+ type MedianContactControl = {
931
+ showBranding: boolean;
932
+ /**
933
+ * Send what the form holds. Resolves once the message is in the inbox.
934
+ * Rejects with an `Error` whose `message` is worth showing, and `error`
935
+ * holds the same words until the next send.
936
+ */
937
+ send: (message: MedianContactMessage) => Promise<void>;
938
+ /** A send is in flight. Hold the button. */
939
+ isSending: boolean;
940
+ /** Why the last send failed, or null. Cleared by the next send. */
941
+ error: string | null;
942
+ /**
943
+ * Whether a send has somewhere to go. False on the server, and for the
944
+ * beat before an identity route has answered with the key.
945
+ */
946
+ isReady: boolean;
947
+ /**
948
+ * Who is typing, as far as the page knows: what you passed, what the
949
+ * identity route said, and the email the widget's own question was
950
+ * answered with on this browser. For filling the boxes in.
951
+ */
952
+ user: MedianUser | undefined;
953
+ };
954
+ /**
955
+ * A contact form of your own, on Median's inbox:
956
+ *
957
+ * const contact = useMedianContact({ apiKey: "median_pk_..." });
958
+ *
959
+ * <form
960
+ * onSubmit={async (event) => {
961
+ * event.preventDefault();
962
+ * await contact.send({ email, message });
963
+ * }}
964
+ * >
965
+ * ...
966
+ * {contact.error && <p>{contact.error}</p>}
967
+ * <button disabled={contact.isSending}>Send</button>
968
+ * </form>
969
+ *
970
+ * Everything about the form is yours. What is sent starts a conversation in
971
+ * your inbox exactly as `<MedianContactForm>` and the widget do, and the
972
+ * reply goes to the email given. No provider, no connection to manage.
973
+ */
974
+ declare function useMedianContact(options: UseMedianContactOptions): MedianContactControl;
975
+
976
+ export { type MedianContactControl, type MedianContactField, type MedianContactFieldKind, MedianContactForm, type MedianContactFormInstance, type MedianContactFormMountOptions, type MedianContactFormProps, type MedianContactMessage, type MedianContactOptions, type MedianDiagnostics, type MedianErrorReportOptions, type MedianErrorReportOutcome, MedianFeedback, type MedianFeedbackControl, type MedianFeedbackInstance, type MedianFeedbackProps, MedianSupport, type MedianSupportControl, type MedianSupportInstance, MedianSupportModal, type MedianSupportModalInstance, type MedianSupportModalProps, type MedianSupportProps, type MedianUser, type MedianDiagnostics as PortalDiagnostics, type MedianErrorReportOptions as PortalErrorReportOptions, type MedianErrorReportOutcome as PortalErrorReportOutcome, MedianFeedback as PortalFeedback, type MedianFeedbackControl as PortalFeedbackControl, type MedianFeedbackInstance as PortalFeedbackInstance, type MedianFeedbackProps as PortalFeedbackProps, MedianSupport as PortalSupport, type MedianSupportControl as PortalSupportControl, type MedianSupportInstance as PortalSupportInstance, type MedianSupportProps as PortalSupportProps, type MedianUser as PortalUser, type SupportLauncherKind, type UseMedianContactOptions, medianFeedback, medianSupport, mountMedianContactForm, mountMedianFeedback, mountMedianSupport, mountMedianSupportModal, mountMedianFeedback as mountPortalFeedback, mountMedianSupport as mountPortalSupport, medianFeedback as portalFeedback, medianSupport as portalSupport, reportError, sendMedianContact, useCanReportError, useMedianContact, useMedianFeedback, useMedianSupport, useMedianFeedback as usePortalFeedback, useMedianSupport as usePortalSupport };