@flamingo-stack/openframe-frontend-core 0.0.718 → 0.0.720
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/{chunk-MIQLUUVF.cjs → chunk-2C72EWQR.cjs} +93 -29
- package/dist/{chunk-IWCPXJEE.js → chunk-3HZ6XSZC.js} +2 -4
- package/dist/{chunk-ZXCZYMN4.cjs → chunk-4SIZCHVZ.cjs} +13 -15
- package/dist/{chunk-BOTM373R.cjs → chunk-5XDCCQ4A.cjs} +15 -15
- package/dist/{chunk-DXCBOZVT.cjs → chunk-6BLN5WDS.cjs} +41 -41
- package/dist/{chunk-DCD5WZUF.js → chunk-6W46LVK4.js} +5 -5
- package/dist/{chunk-JHQ334WS.js → chunk-742BGXNX.js} +2 -0
- package/dist/{chunk-AECTRMGQ.cjs → chunk-7GRPR7XA.cjs} +13 -1
- package/dist/{chunk-45F7PXUI.js → chunk-7HYEQKN6.js} +6 -6
- package/dist/{chunk-N2PF5CFX.js → chunk-7IOU6BJE.js} +12 -11
- package/dist/{chunk-BWIN42ZA.cjs → chunk-7LPR4RZ3.cjs} +34 -36
- package/dist/chunk-AFTRPEXO.js +13 -0
- package/dist/{chunk-JPD4NXH6.js → chunk-AZRI4XBP.js} +1 -1
- package/dist/{chunk-4QFWGLZO.js → chunk-B2N4YX66.js} +1 -1
- package/dist/{chunk-QXZCLQNV.js → chunk-B5LNX4GZ.js} +2 -2
- package/dist/{chunk-GN4A7PMB.cjs → chunk-DHNJSQGX.cjs} +91 -91
- package/dist/{chunk-NDZ5ZXOQ.cjs → chunk-DOPOHO6H.cjs} +20 -20
- package/dist/{chunk-Z3CVSPO3.cjs → chunk-DYDDAA6O.cjs} +519 -518
- package/dist/{chunk-2RKFMUNW.cjs → chunk-EVMM3JE6.cjs} +64 -63
- package/dist/chunk-EWPR7662.cjs +13 -0
- package/dist/{chunk-AGVXKD4K.js → chunk-FSQQZFVG.js} +12 -0
- package/dist/{chunk-TTVOLEUS.js → chunk-GANIUMRU.js} +89 -2
- package/dist/{chunk-S2FUFMLW.cjs → chunk-HFEYEPOG.cjs} +27 -27
- package/dist/{chunk-LE5WKGJV.cjs → chunk-HTS46FFY.cjs} +3 -3
- package/dist/{chunk-6A43ZDWV.cjs → chunk-IL3ZWNM2.cjs} +4 -4
- package/dist/{chunk-PSVSEN4J.cjs → chunk-IMTY3GRR.cjs} +2 -0
- package/dist/{chunk-YC5COB5V.cjs → chunk-ISCZ6UNF.cjs} +85 -89
- package/dist/{chunk-XGHGGBM3.js → chunk-K2BYVPGR.js} +3 -3
- package/dist/{chunk-HWVUR735.cjs → chunk-KSGMZHVV.cjs} +2 -2
- package/dist/{chunk-SGFTTJKO.js → chunk-LO3KCLEF.js} +1 -1
- package/dist/{chunk-MA4Q5RBP.cjs → chunk-NYSMDM4J.cjs} +2 -2
- package/dist/{chunk-67QBMN4R.js → chunk-O3MKJU6A.js} +6 -6
- package/dist/{chunk-APJZBDQN.js → chunk-PSMJYZMV.js} +27 -26
- package/dist/{chunk-JH2SIWPQ.js → chunk-PXAKSPLZ.js} +1586 -1263
- package/dist/{chunk-TBCZCRQ5.js → chunk-Q5327DXK.js} +13 -17
- package/dist/{chunk-EZC2F5OT.cjs → chunk-QF464PPL.cjs} +39 -39
- package/dist/{chunk-UWQWMGJ7.cjs → chunk-RDP5UZXQ.cjs} +888 -565
- package/dist/{chunk-ETYLOZOB.cjs → chunk-RFUOIQSG.cjs} +100 -13
- package/dist/{chunk-DC4RTYWK.cjs → chunk-RZQGIWK3.cjs} +3 -3
- package/dist/{chunk-PQ6HBLZ7.js → chunk-TORERAXD.js} +105 -41
- package/dist/{chunk-6YPTUHM7.js → chunk-URW23QUM.js} +1 -1
- package/dist/{chunk-QBCDSTLE.cjs → chunk-UWJMDFDO.cjs} +18 -18
- package/dist/{chunk-YEMCCYIF.js → chunk-V6B2FBZK.js} +5 -5
- package/dist/{chunk-JIWMBIVZ.js → chunk-VBHGJFA3.js} +10 -10
- package/dist/{chunk-Z5UWW3Z2.cjs → chunk-VF2CJXZ2.cjs} +81 -65
- package/dist/{chunk-DEZDMTBM.js → chunk-VYVPII4W.js} +8 -10
- package/dist/{chunk-MM7MCIXL.js → chunk-VYZCKY5K.js} +9 -9
- package/dist/{chunk-E7YCPY7K.cjs → chunk-XSM46FHD.cjs} +3 -3
- package/dist/{chunk-NZ5BHDFZ.js → chunk-XW3Q623Q.js} +1 -1
- package/dist/{chunk-LOYPRJWA.js → chunk-YK3X74SF.js} +67 -51
- package/dist/{chunk-WALCXC7J.cjs → chunk-ZHD3F2TH.cjs} +3 -3
- package/dist/{chunk-KBJJQFYU.js → chunk-ZPHOQDAE.js} +1 -1
- package/dist/{cloud-tenants-TAKOWXLD.js → cloud-tenants-RKQRSDNE.js} +9 -9
- package/dist/{cloud-tenants-PFDF7O5U.cjs → cloud-tenants-UQZASXUB.cjs} +10 -10
- package/dist/components/case-studies/index.cjs +23 -23
- package/dist/components/case-studies/index.js +10 -10
- package/dist/components/chat/ask-prompts.d.ts +10 -2
- package/dist/components/chat/ask-prompts.d.ts.map +1 -1
- package/dist/components/chat/assistant-ask-prompts.d.ts +27 -0
- package/dist/components/chat/assistant-ask-prompts.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/hooks/index.d.ts +1 -0
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-ask-prompts.d.ts +63 -0
- package/dist/components/chat/hooks/use-ask-prompts.d.ts.map +1 -0
- package/dist/components/chat/index.cjs +23 -6
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +22 -5
- package/dist/components/chat/utils/icon-library.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +11 -11
- package/dist/components/contact/index.js +10 -10
- package/dist/components/docs/index.cjs +14 -14
- package/dist/components/docs/index.js +13 -13
- package/dist/components/embeds/index.cjs +11 -11
- package/dist/components/embeds/index.js +10 -10
- package/dist/components/faq/faq-ask-card.d.ts +44 -0
- package/dist/components/faq/faq-ask-card.d.ts.map +1 -0
- package/dist/components/faq/faq-document-page.d.ts +5 -1
- package/dist/components/faq/faq-document-page.d.ts.map +1 -1
- package/dist/components/faq/faq-section.d.ts +18 -5
- package/dist/components/faq/faq-section.d.ts.map +1 -1
- package/dist/components/faq/index.cjs +13 -12
- package/dist/components/faq/index.d.ts +1 -0
- package/dist/components/faq/index.d.ts.map +1 -1
- package/dist/components/faq/index.js +12 -11
- package/dist/components/features/auth/auth-benefits-panel.d.ts.map +1 -1
- package/dist/components/features/index.cjs +10 -10
- package/dist/components/features/index.js +9 -9
- package/dist/components/features/remote-session/remote-desktop-view.d.ts +6 -2
- package/dist/components/features/remote-session/remote-desktop-view.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +104 -105
- package/dist/components/help-center-pages/index.js +28 -29
- package/dist/components/help-center-pages/trust-center-page.d.ts +4 -1
- package/dist/components/help-center-pages/trust-center-page.d.ts.map +1 -1
- package/dist/components/index.cjs +109 -197
- package/dist/components/index.js +228 -316
- package/dist/components/meeting-scheduler/index.cjs +50 -51
- package/dist/components/meeting-scheduler/index.js +12 -13
- package/dist/components/navigation/index.cjs +13 -13
- package/dist/components/navigation/index.js +12 -12
- package/dist/components/navigation/mingo-ai-button.d.ts +1 -1
- package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
- package/dist/components/onboarding-guides/index.cjs +13 -14
- package/dist/components/onboarding-guides/index.js +12 -13
- package/dist/components/product-demo/index.cjs +7 -7
- package/dist/components/product-demo/index.js +7 -7
- package/dist/components/related-content/index.cjs +5 -7
- package/dist/components/related-content/index.js +4 -6
- package/dist/components/related-content/related-content-section.d.ts +6 -6
- package/dist/components/related-content/related-content-section.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +15 -15
- package/dist/components/tickets/index.js +14 -14
- package/dist/components/ui/index.cjs +26 -10
- package/dist/components/ui/index.js +25 -9
- package/dist/contexts/assistant-runtime-context.d.ts +74 -0
- package/dist/contexts/assistant-runtime-context.d.ts.map +1 -0
- package/dist/contexts/index.cjs +7 -1
- package/dist/contexts/index.d.ts +1 -0
- package/dist/contexts/index.d.ts.map +1 -1
- package/dist/contexts/index.js +6 -0
- package/dist/{devices-KRCGZMGA.cjs → devices-7Q2EUM7B.cjs} +11 -11
- package/dist/{devices-LHEX7O5T.js → devices-U3Y4IIW4.js} +9 -9
- package/dist/hooks/index.cjs +3 -4
- package/dist/hooks/index.js +2 -3
- package/dist/hooks/use-self-fetch.d.ts +3 -0
- package/dist/hooks/use-self-fetch.d.ts.map +1 -1
- package/dist/index.cjs +34 -14
- package/dist/index.js +33 -13
- package/dist/{knowledge-RZSFNR33.js → knowledge-NST53EXE.js} +9 -9
- package/dist/{knowledge-KLVPVEYZ.cjs → knowledge-UYIZ74MA.cjs} +11 -11
- package/dist/{logs-OX3XTVL2.cjs → logs-HTH7X7KV.cjs} +18 -18
- package/dist/{logs-E5DOV5JO.js → logs-JVTRG232.js} +9 -9
- package/dist/{policies-MC2S4S5G.js → policies-DTXTRK56.js} +9 -9
- package/dist/{policies-FOSCIZGK.cjs → policies-MXK2HB4W.cjs} +14 -14
- package/dist/{remote-session-JQ2BPIE3.js → remote-session-LAJZKQBN.js} +3 -3
- package/dist/{remote-session-DRFE5XZE.cjs → remote-session-SKKF4PBV.cjs} +5 -5
- package/dist/{software-update-D4OEIH65.js → software-update-6RHMPM3W.js} +9 -9
- package/dist/{software-update-HOBSTWVO.cjs → software-update-P6BSM6HX.cjs} +14 -14
- package/dist/utils/index.cjs +26 -2
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +25 -3
- package/dist/utils/list-url.d.ts.map +1 -1
- package/dist/utils/source-icons.d.ts.map +1 -1
- package/dist/utils/trial.d.ts +18 -0
- package/dist/utils/trial.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/chat/ask-prompts.tsx +25 -3
- package/src/components/chat/assistant-ask-prompts.tsx +78 -0
- package/src/components/chat/entity-cards/dispatch.tsx +38 -4
- package/src/components/chat/hooks/index.ts +12 -0
- package/src/components/chat/hooks/use-ask-prompts.ts +188 -0
- package/src/components/chat/index.ts +1 -0
- package/src/components/chat/utils/icon-library.tsx +6 -0
- package/src/components/faq/faq-ask-card.tsx +115 -0
- package/src/components/faq/faq-document-page.tsx +6 -1
- package/src/components/faq/faq-section.tsx +27 -5
- package/src/components/faq/index.ts +1 -0
- package/src/components/features/auth/auth-benefits-panel.tsx +5 -4
- package/src/components/features/remote-session/remote-desktop-view.tsx +86 -63
- package/src/components/help-center-pages/trust-center-page.tsx +5 -2
- package/src/components/navigation/mingo-ai-button.tsx +27 -6
- package/src/components/related-content/related-content-section.tsx +46 -47
- package/src/contexts/assistant-runtime-context.tsx +84 -0
- package/src/contexts/index.ts +9 -0
- package/src/hooks/use-self-fetch.ts +7 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/list-url.ts +4 -0
- package/src/utils/source-icons.ts +10 -2
- package/src/utils/trial.ts +19 -0
- package/dist/chunk-AUMKE74M.cjs +0 -16
- package/dist/chunk-G3GRYDMS.js +0 -16
- package/dist/chunk-GEDY7HQI.cjs +0 -85
- package/dist/chunk-YBQQNDC7.js +0 -85
|
@@ -4,6 +4,7 @@ import type { ReactNode } from 'react';
|
|
|
4
4
|
import {
|
|
5
5
|
ChatOffIcon,
|
|
6
6
|
ChatTextIcon,
|
|
7
|
+
Chevron02DownIcon,
|
|
7
8
|
Collapse02Icon,
|
|
8
9
|
Expand02Icon,
|
|
9
10
|
MonitorIcon,
|
|
@@ -19,8 +20,12 @@ import { RemoteDesktopFullscreenToolbar } from './remote-desktop-fullscreen-tool
|
|
|
19
20
|
/** The page chrome both the view and its skeleton render in. */
|
|
20
21
|
const PAGE_CLASS = 'h-full overflow-hidden px-[var(--spacing-system-l)] pb-[var(--spacing-system-l)]';
|
|
21
22
|
|
|
23
|
+
/** The header card: the device row, and under it the display row when there is one to pick. */
|
|
22
24
|
const CONTROLS_BAR_CLASS =
|
|
23
|
-
'flex flex-shrink-0
|
|
25
|
+
'flex flex-shrink-0 flex-col overflow-hidden rounded-md border border-ods-border bg-ods-card';
|
|
26
|
+
|
|
27
|
+
const CONTROLS_ROW_CLASS =
|
|
28
|
+
'flex items-center justify-between gap-[var(--spacing-system-mf)] px-[var(--spacing-system-mf)] py-[var(--spacing-system-xs)]';
|
|
24
29
|
|
|
25
30
|
export interface RemoteDesktopViewProps {
|
|
26
31
|
/** First line of the device header. */
|
|
@@ -36,9 +41,13 @@ export interface RemoteDesktopViewProps {
|
|
|
36
41
|
fullscreen?: boolean;
|
|
37
42
|
onEnterFullscreen: () => void;
|
|
38
43
|
onExitFullscreen: () => void;
|
|
39
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* The display switcher's menu, one entry per separate display. The switcher is
|
|
46
|
+
* the full-width row under the device row (Figma 2155:109503), and it is left
|
|
47
|
+
* out while this is empty: one display, or an agent that reports none.
|
|
48
|
+
*/
|
|
40
49
|
displayMenuGroups?: ActionsMenuGroup[];
|
|
41
|
-
/** The switcher's label, e.g. "Display
|
|
50
|
+
/** The switcher's label, e.g. "Display 2", or "Display" before the agent has named one. */
|
|
42
51
|
currentDisplayLabel: string;
|
|
43
52
|
actionsMenuGroups: ActionsMenuGroup[];
|
|
44
53
|
onOpenSettings: () => void;
|
|
@@ -86,61 +95,73 @@ export function RemoteDesktopView({
|
|
|
86
95
|
}: RemoteDesktopViewProps) {
|
|
87
96
|
const controlsBar = (
|
|
88
97
|
<div className={CONTROLS_BAR_CLASS}>
|
|
89
|
-
<div className=
|
|
90
|
-
<div className="flex-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
<div className={CONTROLS_ROW_CLASS}>
|
|
99
|
+
<div className="flex min-w-0 items-center gap-[var(--spacing-system-mf)]">
|
|
100
|
+
<div className="flex-shrink-0 rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-xsf)]">
|
|
101
|
+
<MonitorIcon className="h-4 w-4 text-ods-text-primary" />
|
|
102
|
+
</div>
|
|
103
|
+
<div className="flex min-w-0 flex-col">
|
|
104
|
+
<TruncateText>{deviceName}</TruncateText>
|
|
105
|
+
<TruncateText
|
|
106
|
+
variant="h6"
|
|
107
|
+
tone="secondary"
|
|
108
|
+
>{`Desktop • ${organizationName || 'Unknown Customer'}`}</TruncateText>
|
|
109
|
+
</div>
|
|
99
110
|
</div>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
<div className="flex flex-shrink-0 items-center gap-[var(--spacing-system-xs)]">
|
|
112
|
+
{onToggleChat && (
|
|
113
|
+
<Button
|
|
114
|
+
variant="outline"
|
|
115
|
+
onClick={onToggleChat}
|
|
116
|
+
leftIcon={
|
|
117
|
+
chatOpen ? (
|
|
118
|
+
<ChatOffIcon className="h-4 w-4 md:h-6 md:w-6" />
|
|
119
|
+
) : (
|
|
120
|
+
<ChatTextIcon className="h-4 w-4 md:h-6 md:w-6" />
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
>
|
|
124
|
+
{/* A narrow page keeps the icon and drops the words: the bar never pushes the device's name out. */}
|
|
125
|
+
<span className="sr-only content-md:not-sr-only">{chatOpen ? 'Close Chat' : 'Open Chat'}</span>
|
|
126
|
+
</Button>
|
|
127
|
+
)}
|
|
128
|
+
<ActionsMenuDropdown groups={actionsMenuGroups} triggerAriaLabel="Actions" />
|
|
129
|
+
<Button
|
|
130
|
+
variant="outline"
|
|
131
|
+
size="icon"
|
|
132
|
+
aria-label="Settings"
|
|
133
|
+
onClick={onOpenSettings}
|
|
134
|
+
leftIcon={<Settings01Icon />}
|
|
111
135
|
/>
|
|
112
|
-
)}
|
|
113
|
-
{onToggleChat && (
|
|
114
136
|
<Button
|
|
115
137
|
variant="outline"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
)
|
|
123
|
-
}
|
|
124
|
-
>
|
|
125
|
-
<span className="sr-only content-md:not-sr-only">{chatOpen ? 'Close Chat' : 'Open Chat'}</span>
|
|
126
|
-
</Button>
|
|
127
|
-
)}
|
|
128
|
-
<ActionsMenuDropdown groups={actionsMenuGroups} triggerAriaLabel="Actions" />
|
|
129
|
-
<Button
|
|
130
|
-
variant="outline"
|
|
131
|
-
size="icon"
|
|
132
|
-
aria-label="Settings"
|
|
133
|
-
onClick={onOpenSettings}
|
|
134
|
-
leftIcon={<Settings01Icon />}
|
|
135
|
-
/>
|
|
136
|
-
<Button
|
|
137
|
-
variant="outline"
|
|
138
|
-
size="icon"
|
|
139
|
-
aria-label={fullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
|
|
140
|
-
onClick={fullscreen ? onExitFullscreen : onEnterFullscreen}
|
|
141
|
-
leftIcon={fullscreen ? <Collapse02Icon /> : <Expand02Icon />}
|
|
142
|
-
/>
|
|
138
|
+
size="icon"
|
|
139
|
+
aria-label={fullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
|
|
140
|
+
onClick={fullscreen ? onExitFullscreen : onEnterFullscreen}
|
|
141
|
+
leftIcon={fullscreen ? <Collapse02Icon /> : <Expand02Icon />}
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
143
144
|
</div>
|
|
145
|
+
{/* The monitor selector is a row of its own under the device row, the full
|
|
146
|
+
width of the card (Figma 2155:109503): the label never competes with the
|
|
147
|
+
device's name for the header's width. Hidden with one display or none. */}
|
|
148
|
+
{displayMenuGroups.length > 0 && (
|
|
149
|
+
<ActionsMenuDropdown
|
|
150
|
+
groups={displayMenuGroups}
|
|
151
|
+
align="start"
|
|
152
|
+
customTrigger={
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
aria-label="Switch display"
|
|
156
|
+
className="flex w-full items-center gap-[var(--spacing-system-xs)] border-t border-ods-border p-[var(--spacing-system-sf)] text-left text-ods-text-primary outline-none transition-colors hover:bg-ods-bg-hover focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ods-focus"
|
|
157
|
+
>
|
|
158
|
+
<MonitorIcon className="h-6 w-6 shrink-0" />
|
|
159
|
+
<span className="min-w-0 flex-1 truncate text-h4">{currentDisplayLabel}</span>
|
|
160
|
+
<Chevron02DownIcon className="h-6 w-6 shrink-0" />
|
|
161
|
+
</button>
|
|
162
|
+
}
|
|
163
|
+
/>
|
|
164
|
+
)}
|
|
144
165
|
</div>
|
|
145
166
|
);
|
|
146
167
|
|
|
@@ -189,17 +210,19 @@ export function RemoteDesktopViewSkeleton({ onBack }: Pick<RemoteDesktopViewProp
|
|
|
189
210
|
return (
|
|
190
211
|
<PageLayout className={PAGE_CLASS} backButton={onBack ? { label: 'Back', onClick: onBack } : undefined}>
|
|
191
212
|
<div className={CONTROLS_BAR_CLASS}>
|
|
192
|
-
<div className=
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
<
|
|
196
|
-
|
|
213
|
+
<div className={CONTROLS_ROW_CLASS}>
|
|
214
|
+
<div className="flex min-w-0 items-center gap-[var(--spacing-system-mf)]">
|
|
215
|
+
<Skeleton className="h-9 w-9 flex-shrink-0 rounded-md" />
|
|
216
|
+
<div className="flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]">
|
|
217
|
+
<Skeleton className="h-5 w-48" />
|
|
218
|
+
<Skeleton className="h-4 w-36" />
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
<div className="flex flex-shrink-0 items-center gap-[var(--spacing-system-xs)]">
|
|
222
|
+
<Skeleton className="h-11 w-11 rounded-lg md:h-12 md:w-12" />
|
|
223
|
+
<Skeleton className="h-11 w-11 rounded-lg md:h-12 md:w-12" />
|
|
224
|
+
<Skeleton className="h-11 w-11 rounded-lg md:h-12 md:w-12" />
|
|
197
225
|
</div>
|
|
198
|
-
</div>
|
|
199
|
-
<div className="flex flex-shrink-0 items-center gap-[var(--spacing-system-xs)]">
|
|
200
|
-
<Skeleton className="h-11 w-11 rounded-lg md:h-12 md:w-12" />
|
|
201
|
-
<Skeleton className="h-11 w-11 rounded-lg md:h-12 md:w-12" />
|
|
202
|
-
<Skeleton className="h-11 w-11 rounded-lg md:h-12 md:w-12" />
|
|
203
226
|
</div>
|
|
204
227
|
</div>
|
|
205
228
|
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
} from '../../types/trust-center';
|
|
46
46
|
import { STICKY_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav';
|
|
47
47
|
import { useScrollSpy } from '../docs/use-scroll-spy';
|
|
48
|
-
import { FaqSection } from '../faq/faq-section';
|
|
48
|
+
import { FaqSection, type FaqSectionProps } from '../faq/faq-section';
|
|
49
49
|
import { PageShell } from '../layout/article-detail-layout';
|
|
50
50
|
import { PageLayout } from '../layout/page-layout';
|
|
51
51
|
import { StickySectionNav } from '../navigation/sticky-section-nav';
|
|
@@ -83,6 +83,8 @@ export interface TrustCenterPageProps {
|
|
|
83
83
|
title?: string;
|
|
84
84
|
/** Page subtitle. Default the brand-neutral `TRUST_CENTER_TAGLINE` (a host adds its brand here). */
|
|
85
85
|
subtitle?: string;
|
|
86
|
+
/** The FAQ section's "ask the assistant" card (`FaqSection`'s `ask`): the host states its topic. Absent: no card. */
|
|
87
|
+
ask?: FaqSectionProps['ask'];
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
/** One section's page content. Its `h2` is the `TRUST_CENTER_SECTIONS` label — never a literal here. */
|
|
@@ -149,6 +151,7 @@ export function TrustCenterPage({
|
|
|
149
151
|
backButton = false,
|
|
150
152
|
title = TRUST_CENTER_TITLE,
|
|
151
153
|
subtitle = TRUST_CENTER_TAGLINE,
|
|
154
|
+
ask,
|
|
152
155
|
}: TrustCenterPageProps) {
|
|
153
156
|
const { data, isLoading, error, reload } = useSelfFetch<TrustCenterPublic>(endpoint, {
|
|
154
157
|
initialData,
|
|
@@ -232,7 +235,7 @@ export function TrustCenterPage({
|
|
|
232
235
|
lead: TRUST_SECTION_LEADS.subprocessors,
|
|
233
236
|
render: d => <SubprocessorsSection subprocessors={d.subprocessors} />,
|
|
234
237
|
},
|
|
235
|
-
faq: { render: d => <FaqSection initialFaqs={d.faqs} heading={null} /> },
|
|
238
|
+
faq: { render: d => <FaqSection initialFaqs={d.faqs} heading={null} ask={ask} /> },
|
|
236
239
|
contact: { render: () => <ContactSection onContact={() => openRequest(null)} /> },
|
|
237
240
|
};
|
|
238
241
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type React from 'react';
|
|
4
|
-
import { useEffect } from 'react';
|
|
4
|
+
import { useEffect, useRef } from 'react';
|
|
5
|
+
import { useAssistantRuntime } from '../../contexts/assistant-runtime-context';
|
|
5
6
|
import { useVisitorOs } from '../../hooks/ui/use-visitor-os';
|
|
6
7
|
import { cn } from '../../utils';
|
|
7
8
|
import { shortcutLabel, usesCommandKey } from '../../utils/visitor-os';
|
|
@@ -78,8 +79,8 @@ export function openAskAi(source?: string, options?: { prompt?: string }): void
|
|
|
78
79
|
*/
|
|
79
80
|
export function MingoAiButton({
|
|
80
81
|
source,
|
|
81
|
-
icon,
|
|
82
|
-
label
|
|
82
|
+
icon: iconProp,
|
|
83
|
+
label: labelProp,
|
|
83
84
|
shortcutHint = false,
|
|
84
85
|
variant = 'inline',
|
|
85
86
|
className,
|
|
@@ -93,6 +94,22 @@ export function MingoAiButton({
|
|
|
93
94
|
const inPage = variant === 'button';
|
|
94
95
|
|
|
95
96
|
const commandKey = visitor.known && usesCommandKey(visitor.os);
|
|
97
|
+
// The launcher is the assistant's: its name, glyph and the chat it opens are
|
|
98
|
+
// the assistant runtime's (the host's server-resolved identity and its own
|
|
99
|
+
// opener), so no launcher types a name or assumes which chat exists. A prop
|
|
100
|
+
// still wins (a header given its own). The lib holds no name of its own:
|
|
101
|
+
// with none from either, there is no launcher to render (below). With no
|
|
102
|
+
// runtime opener the open is the `ask-ai:open` event of `source`.
|
|
103
|
+
const assistant = useAssistantRuntime();
|
|
104
|
+
const label = labelProp ?? assistant?.name;
|
|
105
|
+
const icon = iconProp ?? assistant?.icon;
|
|
106
|
+
const runtimeOpen = assistant?.open;
|
|
107
|
+
const eventSource = source ?? assistant?.source;
|
|
108
|
+
// The latest opener, for the shortcut listener: a host's inline callback must not re-bind it every render.
|
|
109
|
+
const openRef = useRef<() => void>(() => undefined);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
openRef.current = () => (runtimeOpen ? runtimeOpen({}) : openAskAi(eventSource));
|
|
112
|
+
});
|
|
96
113
|
|
|
97
114
|
// The shortcut the key cap shows opens the same chat the click opens.
|
|
98
115
|
useEffect(() => {
|
|
@@ -105,11 +122,14 @@ export function MingoAiButton({
|
|
|
105
122
|
const modifier = commandKey ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
|
|
106
123
|
if (event.key.toLowerCase() !== 'k' || !modifier) return;
|
|
107
124
|
event.preventDefault();
|
|
108
|
-
|
|
125
|
+
openRef.current();
|
|
109
126
|
};
|
|
110
127
|
window.addEventListener('keydown', onKeyDown);
|
|
111
128
|
return () => window.removeEventListener('keydown', onKeyDown);
|
|
112
|
-
}, [shortcutHint,
|
|
129
|
+
}, [shortcutHint, commandKey]);
|
|
130
|
+
|
|
131
|
+
// No name from the host or the runtime: nothing to launch under. The lib invents none.
|
|
132
|
+
if (!label) return null;
|
|
113
133
|
|
|
114
134
|
return (
|
|
115
135
|
<Button
|
|
@@ -121,7 +141,8 @@ export function MingoAiButton({
|
|
|
121
141
|
aria-label={label}
|
|
122
142
|
aria-keyshortcuts={shortcutHint && visitor.known ? (commandKey ? 'Meta+K' : 'Control+K') : undefined}
|
|
123
143
|
onClick={e => {
|
|
124
|
-
|
|
144
|
+
if (runtimeOpen) runtimeOpen({});
|
|
145
|
+
else openAskAi(eventSource);
|
|
125
146
|
onClick?.(e);
|
|
126
147
|
}}
|
|
127
148
|
className={cn(
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
* `renderSkeletonForType` so the placeholder height matches the loaded card
|
|
29
29
|
* exactly (zero layout shift on resolve).
|
|
30
30
|
*
|
|
31
|
-
* One API call per content type
|
|
32
|
-
* (`buildListUrl` — injectable; defaults to the
|
|
33
|
-
* builder prefixed with `apiBaseUrl`). Fetching uses
|
|
34
|
-
* fetch, NO react-query) so third-party embedders need
|
|
35
|
-
* cards are imported via DEEP module paths (not the
|
|
36
|
-
* chunk never reaches `@tanstack/react-query`.
|
|
31
|
+
* One API call per page of a content type, carrying only that page's ids, via
|
|
32
|
+
* the shared list-URL builder (`buildListUrl` — injectable; defaults to the
|
|
33
|
+
* lib's byte-parity-tested builder prefixed with `apiBaseUrl`). Fetching uses
|
|
34
|
+
* `useSelfFetch` (plain fetch, NO react-query) so third-party embedders need
|
|
35
|
+
* no QueryClientProvider; cards are imported via DEEP module paths (not the
|
|
36
|
+
* chat barrel) so this chunk never reaches `@tanstack/react-query`.
|
|
37
37
|
*
|
|
38
38
|
* The per-type card + skeleton dispatch lives in `./card-registry`
|
|
39
39
|
* (`RELATED_CARD_REGISTRY`): ONE entry per content type, each carrying that
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
*/
|
|
50
50
|
|
|
51
51
|
import type React from 'react';
|
|
52
|
-
import {
|
|
52
|
+
import { useMemo, useState } from 'react';
|
|
53
53
|
import { useSelfFetch } from '../../hooks/use-self-fetch';
|
|
54
54
|
import type { ContentRef, ContentRefWithReason } from '../../types/content-ref';
|
|
55
55
|
import {
|
|
@@ -246,23 +246,29 @@ function CardForType({
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
// =============================================================================
|
|
249
|
-
// Fetch
|
|
250
|
-
//
|
|
251
|
-
// react-query usage: `enabled`
|
|
252
|
-
// `
|
|
253
|
-
// react-query: no retry/backoff, no focus
|
|
249
|
+
// Fetch ONE page of a group — only the ids on screen go into the list URL.
|
|
250
|
+
// Pages already fetched are kept by url, so paging back renders without a
|
|
251
|
+
// request. `useSelfFetch` replaces the hub's old react-query usage: `enabled`
|
|
252
|
+
// ≙ `url === null` (no list API → `items: null`), `!res.ok`/network error ≙
|
|
253
|
+
// an empty page. Accepted deltas vs react-query: no retry/backoff, no focus
|
|
254
|
+
// refetch, no cross-mount cache.
|
|
254
255
|
// =============================================================================
|
|
255
256
|
|
|
256
257
|
function useGroupItems(type: string, refs: ContentRef[], buildUrl: (type: string, ids: string[]) => string | null) {
|
|
257
258
|
const ids = refs.map(r => r.id);
|
|
258
259
|
const url = ids.length > 0 ? buildUrl(type, ids) : null;
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
|
|
260
|
+
const [pages, setPages] = useState<ReadonlyMap<string, unknown[]>>(() => new Map());
|
|
261
|
+
const cached = url !== null ? pages.get(url) : undefined;
|
|
262
|
+
const { data, dataUrl, error } = useSelfFetch<unknown>(cached ? null : url);
|
|
263
|
+
if (data != null && dataUrl !== null && !pages.has(dataUrl)) {
|
|
264
|
+
setPages(new Map(pages).set(dataUrl, extractItems(data)));
|
|
265
|
+
}
|
|
266
|
+
if (url === null) return { items: null, isLoading: false };
|
|
267
|
+
return { items: cached ?? (error ? [] : null), isLoading: !cached && !error };
|
|
262
268
|
}
|
|
263
269
|
|
|
264
270
|
// =============================================================================
|
|
265
|
-
// Per-group renderer — one API call
|
|
271
|
+
// Per-group renderer — one API call per page, then render cards via the
|
|
266
272
|
// dispatcher with per-type skeletons + per-type layout from CONTENT_REF_GROUPS.
|
|
267
273
|
// =============================================================================
|
|
268
274
|
|
|
@@ -325,41 +331,33 @@ function ContentGroup({
|
|
|
325
331
|
* its heading too — no orphaned titles. */
|
|
326
332
|
heading: React.ReactNode;
|
|
327
333
|
}) {
|
|
328
|
-
const { items, isLoading } = useGroupItems(type, refs, buildUrl);
|
|
329
334
|
const config = resolveGroupConfig(type);
|
|
330
335
|
const isListLayout = config.layout === 'list';
|
|
331
336
|
const cardSize = config.gridSize;
|
|
332
337
|
|
|
333
338
|
// Per-group pagination for big groups (author pages): GROUP_PAGE_SIZE items
|
|
334
|
-
// per page with the standard Pagination control below the group
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
// refetch) can never strand the view past the last page, and RESET when the
|
|
339
|
-
// ref set actually changes (shrink→grow must not return to a stale page).
|
|
339
|
+
// per page with the standard Pagination control below the group, each page
|
|
340
|
+
// fetched on its own. Back to page 1 whenever the ref set changes (a
|
|
341
|
+
// suggestion refetch can shrink it) — reset while rendering, so the stale
|
|
342
|
+
// page is never requested.
|
|
340
343
|
const [page, setPage] = useState(1);
|
|
341
344
|
const refsKey = refs.map(r => r.id).join('|');
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
}, [refsKey]);
|
|
345
|
+
const [pagedRefsKey, setPagedRefsKey] = useState(refsKey);
|
|
346
|
+
if (pagedRefsKey !== refsKey) {
|
|
347
|
+
setPagedRefsKey(refsKey);
|
|
348
|
+
setPage(1);
|
|
349
|
+
}
|
|
349
350
|
const totalGroupPages = pageCount(refs.length, GROUP_PAGE_SIZE);
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
refs.length > GROUP_PAGE_SIZE ? refs.slice((safePage - 1) * GROUP_PAGE_SIZE, safePage * GROUP_PAGE_SIZE) : refs;
|
|
351
|
+
const pageRefs = refs.slice((page - 1) * GROUP_PAGE_SIZE, page * GROUP_PAGE_SIZE);
|
|
352
|
+
const { items, isLoading } = useGroupItems(type, pageRefs, buildUrl);
|
|
353
353
|
const groupPagination =
|
|
354
|
-
totalGroupPages > 1 ?
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (isLoading && !items) {
|
|
362
|
-
const skeletons = visibleGroupRefs.map(r => (
|
|
354
|
+
totalGroupPages > 1 ? <Pagination currentPage={page} totalPages={totalGroupPages} onPageChange={setPage} /> : null;
|
|
355
|
+
|
|
356
|
+
// Skeletons until this page's rows land — SSR HTML and the client's first
|
|
357
|
+
// paint render identical skeletons (useSelfFetch starts loading on both
|
|
358
|
+
// sides). The pager stays in place while an unfetched page loads.
|
|
359
|
+
if (isLoading) {
|
|
360
|
+
const skeletons = pageRefs.map(r => (
|
|
363
361
|
<div key={r.id}>{renderSkeletonForType(type, cardSize, adminCampaignCard)}</div>
|
|
364
362
|
));
|
|
365
363
|
return (
|
|
@@ -370,11 +368,12 @@ function ContentGroup({
|
|
|
370
368
|
) : (
|
|
371
369
|
<div className={gridClassFor(columns)}>{skeletons}</div>
|
|
372
370
|
)}
|
|
371
|
+
{groupPagination}
|
|
373
372
|
</div>
|
|
374
373
|
);
|
|
375
374
|
}
|
|
376
375
|
|
|
377
|
-
if (!items
|
|
376
|
+
if (!items) return null;
|
|
378
377
|
|
|
379
378
|
// Index fetched rows by id, then render in REF order — refs carry the
|
|
380
379
|
// intended sequence (suggestion mode: the engine's tier order, so
|
|
@@ -393,7 +392,7 @@ function ContentGroup({
|
|
|
393
392
|
};
|
|
394
393
|
const itemById = new Map(items.map(it => [rowKey(it), it]));
|
|
395
394
|
|
|
396
|
-
const cards =
|
|
395
|
+
const cards = pageRefs
|
|
397
396
|
.map(contentRef => {
|
|
398
397
|
const itemId = String(contentRef.id);
|
|
399
398
|
const item = itemById.get(itemId);
|
|
@@ -426,9 +425,9 @@ function ContentGroup({
|
|
|
426
425
|
.filter(Boolean);
|
|
427
426
|
|
|
428
427
|
if (cards.length === 0) {
|
|
429
|
-
// Current PAGE resolved zero cards (
|
|
430
|
-
// and the
|
|
431
|
-
// pager exists the user must keep the controls
|
|
428
|
+
// Current PAGE resolved zero cards (its fetch failed, rows were deleted
|
|
429
|
+
// between the ref fetch and the page fetch, or a stricter list-API gate
|
|
430
|
+
// dropped them). When a pager exists the user must keep the controls —
|
|
432
431
|
// dropping the whole group would strand them. A genuinely empty group
|
|
433
432
|
// (no pager) still vanishes with its heading.
|
|
434
433
|
if (groupPagination) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Assistant runtime context: what a PAGE needs to offer the host's chat
|
|
5
|
+
* assistant ("Ask Mingo"): whether a chat is there to open, the assistant's
|
|
6
|
+
* name and glyph, where its questions are read from, and HOW the chat is
|
|
7
|
+
* opened and asked.
|
|
8
|
+
*
|
|
9
|
+
* A sibling of `ChatRuntimeContext` (which configures the chat panel itself)
|
|
10
|
+
* and `EndpointsRuntimeContext`. It is its own context because its values
|
|
11
|
+
* change with the visitor (signed in or not, chat mounted or not), and a new
|
|
12
|
+
* `ChatRuntime` identity re-renders the whole chat tree.
|
|
13
|
+
*
|
|
14
|
+
* The NEAREST provider wins, so a page region that carries its own chat (an
|
|
15
|
+
* embedded chat beside the content) mounts a provider whose `open` opens THAT
|
|
16
|
+
* chat: every "ask" surface under it (`FaqSection`'s card, `AskPrompts`,
|
|
17
|
+
* `AskCard`) then opens and fills it, with no prop on any of them.
|
|
18
|
+
*
|
|
19
|
+
* No provider, or `available: false`: the "ask" surfaces render nothing.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createContext, useContext, type ReactNode } from 'react';
|
|
23
|
+
|
|
24
|
+
/** What an "ask" surface asks the chat to do. */
|
|
25
|
+
export interface AssistantOpenRequest {
|
|
26
|
+
/** A question to send once the chat is open. Absent: the chat only opens. */
|
|
27
|
+
prompt?: string;
|
|
28
|
+
/** The topic of the surface that asked (a FAQ's topic, a page section). */
|
|
29
|
+
topic?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** What an "ask" surface reports after a question was sent (analytics). */
|
|
33
|
+
export interface AssistantAskEvent {
|
|
34
|
+
/** The id of the question that was asked. */
|
|
35
|
+
promptId: string;
|
|
36
|
+
/** The topic of the surface that asked. */
|
|
37
|
+
topic?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The wording of the card beside a FAQ. `{assistant}` is replaced by the assistant's name. */
|
|
41
|
+
export interface AssistantAskCardCopy {
|
|
42
|
+
title?: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
/** How many questions the card offers. */
|
|
45
|
+
count?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AssistantRuntime {
|
|
49
|
+
/** A chat is mounted for this visitor: an "ask" click has something to open. */
|
|
50
|
+
available: boolean;
|
|
51
|
+
/** The assistant's configured name. */
|
|
52
|
+
name?: string | null;
|
|
53
|
+
/** The assistant's configured glyph. Absent: the packaged Mingo mark. */
|
|
54
|
+
icon?: ReactNode;
|
|
55
|
+
/** The chat source the default `open` addresses its `ask-ai:open` event to. */
|
|
56
|
+
source?: string;
|
|
57
|
+
/**
|
|
58
|
+
* GET endpoint the questions are read from:
|
|
59
|
+
* `<url>?count=<n>§ion=<topic>&exclude=<id,id>` → `{ prompts: AskPrompt[] }`
|
|
60
|
+
* `section` is ANY string: the host decides what topics exist (the hub: the
|
|
61
|
+
* `section` attribute of a quick action, set in chat config), so a new topic
|
|
62
|
+
* needs no change here. Hub: '/api/quick-actions/questions'. Absent: an "ask"
|
|
63
|
+
* surface shows its launcher with no questions.
|
|
64
|
+
*/
|
|
65
|
+
askPromptsUrl?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Open the chat and, with `prompt`, ask it. Absent: the `ask-ai:open` event
|
|
68
|
+
* of `source` (`openAskAi`), which the mounted `EmbeddableChat` listens for.
|
|
69
|
+
* A host whose chat is opened another way (an embedded chat, a different
|
|
70
|
+
* panel) supplies its own.
|
|
71
|
+
*/
|
|
72
|
+
open?: (request: AssistantOpenRequest) => void;
|
|
73
|
+
/** After a question was sent (analytics). */
|
|
74
|
+
onAsk?: (event: AssistantAskEvent) => void;
|
|
75
|
+
/** The wording of the card beside a FAQ. Absent: the lib's own. */
|
|
76
|
+
askCard?: AssistantAskCardCopy;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const AssistantRuntimeContext = createContext<AssistantRuntime | null>(null);
|
|
80
|
+
|
|
81
|
+
/** The nearest assistant runtime, or null when no provider is mounted. */
|
|
82
|
+
export function useAssistantRuntime(): AssistantRuntime | null {
|
|
83
|
+
return useContext(AssistantRuntimeContext);
|
|
84
|
+
}
|
package/src/contexts/index.ts
CHANGED
|
@@ -30,4 +30,13 @@ export {
|
|
|
30
30
|
|
|
31
31
|
export { ChatRuntimeContext, useChatRuntime, useRequiredChatRuntime, type ChatRuntime } from './chat-runtime-context';
|
|
32
32
|
|
|
33
|
+
export {
|
|
34
|
+
AssistantRuntimeContext,
|
|
35
|
+
useAssistantRuntime,
|
|
36
|
+
type AssistantAskCardCopy,
|
|
37
|
+
type AssistantAskEvent,
|
|
38
|
+
type AssistantOpenRequest,
|
|
39
|
+
type AssistantRuntime,
|
|
40
|
+
} from './assistant-runtime-context';
|
|
41
|
+
|
|
33
42
|
export { useOuterOrDefault } from './use-outer-or-default';
|
|
@@ -5,6 +5,9 @@ import { contentFetch } from '../utils/embed-content-fetch';
|
|
|
5
5
|
|
|
6
6
|
export interface UseSelfFetchResult<T> {
|
|
7
7
|
data: T | null;
|
|
8
|
+
/** The url `data` came from (`null` until something lands). While a new url
|
|
9
|
+
* loads, `data` still holds the previous one — compare before trusting it. */
|
|
10
|
+
dataUrl: string | null;
|
|
8
11
|
/** Imperatively patch the fetched data (e.g. optimistic vote updates). */
|
|
9
12
|
setData: Dispatch<SetStateAction<T | null>>;
|
|
10
13
|
isLoading: boolean;
|
|
@@ -48,6 +51,7 @@ export function useSelfFetch<T>(
|
|
|
48
51
|
const initialData = options?.initialData;
|
|
49
52
|
const revalidateOnVisibleAfterMs = options?.revalidateOnVisibleAfterMs;
|
|
50
53
|
const [data, setData] = useState<T | null>(initialData ?? null);
|
|
54
|
+
const [dataUrl, setDataUrl] = useState<string | null>(initialData !== undefined ? url : null);
|
|
51
55
|
const [isLoading, setIsLoading] = useState(initialData === undefined && url !== null);
|
|
52
56
|
const [error, setError] = useState(false);
|
|
53
57
|
const [reloadKey, setReloadKey] = useState(0);
|
|
@@ -78,6 +82,7 @@ export function useSelfFetch<T>(
|
|
|
78
82
|
// `initialData` is `undefined`.
|
|
79
83
|
if (syncedProps.initialData !== initialData && initialData !== undefined) {
|
|
80
84
|
setData(initialData);
|
|
85
|
+
setDataUrl(url);
|
|
81
86
|
}
|
|
82
87
|
// Fetching just got disabled — nothing will land to clear the spinner.
|
|
83
88
|
if (syncedProps.url !== url && url === null) {
|
|
@@ -101,6 +106,7 @@ export function useSelfFetch<T>(
|
|
|
101
106
|
const json = (await res.json()) as T;
|
|
102
107
|
if (!cancelled) {
|
|
103
108
|
setData(json);
|
|
109
|
+
setDataUrl(url);
|
|
104
110
|
dataUrlRef.current = url; // remember the url we now hold data for
|
|
105
111
|
dataAtRef.current = Date.now();
|
|
106
112
|
}
|
|
@@ -149,6 +155,7 @@ export function useSelfFetch<T>(
|
|
|
149
155
|
|
|
150
156
|
return {
|
|
151
157
|
data,
|
|
158
|
+
dataUrl,
|
|
152
159
|
setData,
|
|
153
160
|
isLoading,
|
|
154
161
|
error,
|
package/src/utils/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export {
|
|
|
17
17
|
// source of truth). Re-exported here so existing callers that pull from
|
|
18
18
|
// the barrel keep working without changing imports.
|
|
19
19
|
export { formatDate, formatNumber, formatPrice, formatBytes } from './format';
|
|
20
|
+
export { TRIAL_DAYS, TRIAL_LABELS } from './trial';
|
|
20
21
|
export {
|
|
21
22
|
VENDOR_CLASSIFICATIONS,
|
|
22
23
|
OPENFRAME_CLASSIFICATIONS,
|
package/src/utils/list-url.ts
CHANGED
|
@@ -116,6 +116,10 @@ const BUILDERS: Record<string, (ids: string[], base: string) => string> = {
|
|
|
116
116
|
design_doc: (ids, b) => `${b}/api/design-docs?ids=${ids.join(',')}`,
|
|
117
117
|
openframe_tenant: (ids, b) => `${b}/api/openframe-tenants?ids=${ids.join(',')}`,
|
|
118
118
|
prospect_call: (ids, b) => `${b}/api/prospect-calls?ids=${ids.join(',')}`,
|
|
119
|
+
// The public website's own pages (live source; ChatRef-shaped items like the objects above).
|
|
120
|
+
site_page: (ids, b) => `${b}/api/site-pages?ids=${ids.join(',')}`,
|
|
121
|
+
// The OpenMSP vendor directory (indexed source).
|
|
122
|
+
vendor: (ids, b) => `${b}/api/vendors/cards?ids=${ids.join(',')}`,
|
|
119
123
|
// Code intelligence — per-object card hydration, same `handleEntityCardList`
|
|
120
124
|
// shape as the internal objects above.
|
|
121
125
|
code_rule: (ids, b) => `${b}/api/code-rules/cards?ids=${ids.join(',')}`,
|