@flamingo-stack/openframe-frontend-core 0.0.841 → 0.0.843
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/build/export-map.json +1 -1
- package/dist/{case-study-card-TK7ZCNLM.cjs → case-study-card-VN3JBNF5.cjs} +3 -3
- package/dist/{chunk-RSJYTQEA.cjs → chunk-23DVVS4I.cjs} +104 -104
- package/dist/{chunk-IELPVUPR.cjs → chunk-2H5D2PUC.cjs} +23 -23
- package/dist/{chunk-275AQ6LC.cjs → chunk-2JZFXGHE.cjs} +4 -4
- package/dist/{chunk-DYDQLS2R.cjs → chunk-2WGA2IRN.cjs} +370 -298
- package/dist/{chunk-VBH2RCFM.cjs → chunk-3IXW2EKO.cjs} +78 -78
- package/dist/{chunk-DGY5KEPY.cjs → chunk-4ASUUB3A.cjs} +83 -83
- package/dist/{chunk-WCOSGDGA.cjs → chunk-4R4HAFXI.cjs} +2 -2
- package/dist/{chunk-INBFKUZA.cjs → chunk-5CPEMPNP.cjs} +15 -15
- package/dist/{chunk-CCRXBFNL.cjs → chunk-6HFTSVVC.cjs} +8 -8
- package/dist/{chunk-FHI4I3TQ.cjs → chunk-6M764K5Z.cjs} +12 -12
- package/dist/{chunk-SOCCHWDK.cjs → chunk-BHOVF4KP.cjs} +2 -2
- package/dist/{chunk-WVNUCKB7.cjs → chunk-CNMXP77I.cjs} +4 -4
- package/dist/{chunk-HXTZZE3R.cjs → chunk-DUB2UVYS.cjs} +61 -72
- package/dist/{chunk-TD4CC4KV.cjs → chunk-EUEPGHY4.cjs} +6 -6
- package/dist/{chunk-Q6WUHYOG.cjs → chunk-HLMA6CDT.cjs} +85 -85
- package/dist/{chunk-ZAO5SYBJ.cjs → chunk-MWQOM2EI.cjs} +4 -4
- package/dist/{chunk-WI7EXAZT.cjs → chunk-NMLNZCK2.cjs} +2 -2
- package/dist/{chunk-GUKFMMDZ.cjs → chunk-OGINOWMT.cjs} +3 -3
- package/dist/{chunk-APNONOHY.cjs → chunk-PZNWIJRZ.cjs} +13 -10
- package/dist/{chunk-GMK4JGC2.cjs → chunk-QBZ73IK7.cjs} +44 -44
- package/dist/{chunk-Z35PL3UR.cjs → chunk-QJRZVIQP.cjs} +34 -34
- package/dist/{chunk-SMZWXY4X.cjs → chunk-QKZQ5Q6E.cjs} +6 -1
- package/dist/{chunk-MYAPG3FQ.cjs → chunk-QPJUQ5YU.cjs} +21 -21
- package/dist/{chunk-JUEAZCD6.cjs → chunk-RSZZTB5T.cjs} +54 -54
- package/dist/{chunk-7WKRMJI6.cjs → chunk-SC36S7DJ.cjs} +2 -2
- package/dist/{chunk-AT6D7BAZ.cjs → chunk-SOQVKIWM.cjs} +3 -3
- package/dist/{chunk-UZPYYHF5.cjs → chunk-SOTTMZ6U.cjs} +19 -19
- package/dist/{chunk-E7M54JWW.cjs → chunk-SSSI2RBV.cjs} +14 -14
- package/dist/{chunk-EV6BPF47.cjs → chunk-WPMJUYW3.cjs} +143 -143
- package/dist/{chunk-FRL2ZN4O.cjs → chunk-XCVH5BCZ.cjs} +6 -6
- package/dist/{chunk-JM2XA7VY.cjs → chunk-ZXEWBJUE.cjs} +15 -15
- package/dist/{cloud-tenants-PV63RZ73.cjs → cloud-tenants-VPYROTKH.cjs} +11 -11
- package/dist/components/case-studies/index.cjs +26 -26
- package/dist/components/chat/index.cjs +8 -8
- package/dist/components/contact/index.cjs +12 -12
- package/dist/components/docs/index.cjs +15 -15
- package/dist/components/embeds/index.cjs +12 -12
- package/dist/components/faq/index.cjs +13 -13
- package/dist/components/features/devices/device-unread-indicator.d.ts +6 -0
- package/dist/components/features/devices/device-unread-indicator.d.ts.map +1 -0
- package/dist/components/features/devices/device-unread-indicator.js +12 -0
- package/dist/components/features/devices/devices-grid.d.ts.map +1 -1
- package/dist/components/features/devices/devices-grid.js +5 -1
- package/dist/components/features/devices/devices-table-columns.d.ts.map +1 -1
- package/dist/components/features/devices/devices-table-columns.js +3 -1
- package/dist/components/features/devices/devices-view-config.d.ts +6 -4
- package/dist/components/features/devices/devices-view-config.d.ts.map +1 -1
- package/dist/components/features/devices/index.d.ts +1 -0
- package/dist/components/features/devices/index.d.ts.map +1 -1
- package/dist/components/features/devices/index.js +2 -0
- package/dist/components/features/devices/types.d.ts +2 -0
- package/dist/components/features/devices/types.d.ts.map +1 -1
- package/dist/components/features/index.cjs +13 -11
- package/dist/components/help-center-pages/index.cjs +105 -105
- package/dist/components/index.cjs +73 -65
- package/dist/components/meeting-scheduler/index.cjs +50 -50
- package/dist/components/navigation/count-pill.d.ts +10 -0
- package/dist/components/navigation/count-pill.d.ts.map +1 -0
- package/dist/components/navigation/count-pill.js +18 -0
- package/dist/components/navigation/index.cjs +20 -14
- package/dist/components/navigation/index.d.ts +4 -0
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +5 -0
- package/dist/components/navigation/navigation-sidebar-grouped.d.ts.map +1 -1
- package/dist/components/navigation/navigation-sidebar-grouped.js +1 -12
- package/dist/components/navigation/unread-notifications-indicator.d.ts +19 -0
- package/dist/components/navigation/unread-notifications-indicator.d.ts.map +1 -0
- package/dist/components/navigation/unread-notifications-indicator.js +45 -0
- package/dist/components/onboarding-guides/index.cjs +14 -14
- package/dist/components/product-demo/fixtures/devices.d.ts.map +1 -1
- package/dist/components/product-demo/fixtures/devices.js +6 -1
- package/dist/components/product-demo/index.cjs +20 -20
- package/dist/components/related-content/index.cjs +7 -7
- package/dist/components/tickets/index.cjs +18 -18
- package/dist/components/ui/index.cjs +11 -11
- package/dist/{devices-VECG5JNC.cjs → devices-W76BAABE.cjs} +14 -14
- package/dist/{embeddable-chat-VRYAQQF6.cjs → embeddable-chat-C6DUH2JL.cjs} +78 -78
- package/dist/hooks/index.cjs +4 -4
- package/dist/index.cjs +23 -15
- package/dist/{knowledge-YJSDJIXT.cjs → knowledge-ILAIWOSY.cjs} +12 -12
- package/dist/{logs-42JG3NZ5.cjs → logs-VCPRFJGB.cjs} +18 -18
- package/dist/platform-domains.cjs +14 -10
- package/dist/platform-domains.d.ts +12 -7
- package/dist/platform-domains.d.ts.map +1 -1
- package/dist/platform-domains.js +14 -10
- package/dist/{policies-D4QNVJ26.cjs → policies-3BNAW4M5.cjs} +15 -15
- package/dist/{scripts-4VFYBXBE.cjs → scripts-VFFSJDYV.cjs} +16 -16
- package/dist/{software-update-OSDZQ5CP.cjs → software-update-VTBTLCZO.cjs} +16 -16
- package/dist/utils/index.cjs +13 -10
- package/package.json +1 -1
- package/src/components/features/devices/device-unread-indicator.tsx +14 -0
- package/src/components/features/devices/devices-grid.tsx +9 -1
- package/src/components/features/devices/devices-table-columns.tsx +3 -0
- package/src/components/features/devices/devices-view-config.tsx +6 -4
- package/src/components/features/devices/index.ts +1 -0
- package/src/components/features/devices/types.ts +2 -0
- package/src/components/navigation/count-pill.tsx +25 -0
- package/src/components/navigation/index.ts +4 -0
- package/src/components/navigation/navigation-sidebar-grouped.tsx +1 -14
- package/src/components/navigation/unread-notifications-indicator.tsx +69 -0
- package/src/components/product-demo/fixtures/devices.ts +7 -0
- package/src/platform-domains.ts +28 -20
package/dist/utils/index.cjs
CHANGED
|
@@ -80150,16 +80150,7 @@ function getPlatformByHostname(hostname) {
|
|
|
80150
80150
|
function isPreviewHost(hostname) {
|
|
80151
80151
|
return hostname.endsWith(".vercel.app");
|
|
80152
80152
|
}
|
|
80153
|
-
function
|
|
80154
|
-
if (typeof window === "undefined") return [];
|
|
80155
|
-
const hostname = window.location.hostname;
|
|
80156
|
-
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("127.")) {
|
|
80157
|
-
return [];
|
|
80158
|
-
}
|
|
80159
|
-
const previewEnv = process.env.VERCEL_ENV === "preview" || process.env.NEXT_PUBLIC_VERCEL_ENV === "preview" || isPreviewHost(hostname);
|
|
80160
|
-
if (previewEnv) {
|
|
80161
|
-
return [".vercel.app", "vercel.app"];
|
|
80162
|
-
}
|
|
80153
|
+
function getPlatformBaseDomains() {
|
|
80163
80154
|
const baseDomains = /* @__PURE__ */ new Set();
|
|
80164
80155
|
for (const entry of PLATFORM_DOMAINS) {
|
|
80165
80156
|
if (entry.pseudo) continue;
|
|
@@ -80173,6 +80164,18 @@ function getAllPlatformBaseDomains() {
|
|
|
80173
80164
|
}
|
|
80174
80165
|
return Array.from(baseDomains);
|
|
80175
80166
|
}
|
|
80167
|
+
function getAllPlatformBaseDomains() {
|
|
80168
|
+
if (typeof window === "undefined") return [];
|
|
80169
|
+
const hostname = window.location.hostname;
|
|
80170
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("127.")) {
|
|
80171
|
+
return [];
|
|
80172
|
+
}
|
|
80173
|
+
const previewEnv = process.env.VERCEL_ENV === "preview" || process.env.NEXT_PUBLIC_VERCEL_ENV === "preview" || isPreviewHost(hostname);
|
|
80174
|
+
if (previewEnv) {
|
|
80175
|
+
return [".vercel.app", "vercel.app"];
|
|
80176
|
+
}
|
|
80177
|
+
return getPlatformBaseDomains();
|
|
80178
|
+
}
|
|
80176
80179
|
var _orderIdx = (k) => PLATFORM_DOMAINS.findIndex((e) => e.key === k);
|
|
80177
80180
|
if (_orderIdx("flamingo") > _orderIdx("universal") || getPlatformByHostname("flamingo.cx") !== "flamingo" || getPlatformByHostname("hub.openframe.ai") !== "openframe") {
|
|
80178
80181
|
console.error(
|
package/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { UnreadNotificationsIndicator } from '../../navigation/unread-notifications-indicator';
|
|
4
|
+
import { useDevicesViewConfig } from './devices-view-config';
|
|
5
|
+
import type { DeviceRow } from './types';
|
|
6
|
+
|
|
7
|
+
/** The viewer's unread notifications about a device, opening them where the host lists them. Nothing at 0. */
|
|
8
|
+
export function DeviceUnreadIndicator({ device }: { device: DeviceRow }) {
|
|
9
|
+
const { getDeviceNotificationsHref } = useDevicesViewConfig();
|
|
10
|
+
const count = device.unreadNotificationCount ?? 0;
|
|
11
|
+
return (
|
|
12
|
+
<UnreadNotificationsIndicator count={count} href={count > 0 ? getDeviceNotificationsHref?.(device) : undefined} />
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -7,6 +7,7 @@ import { DeviceCard } from '../../ui';
|
|
|
7
7
|
import { LoadingItems, useLoadingScope } from '../../ui/loading-scope';
|
|
8
8
|
import { getDeviceName } from './device-name';
|
|
9
9
|
import { getDeviceOperatingSystem, getDeviceStatusConfig } from './device-status';
|
|
10
|
+
import { DeviceUnreadIndicator } from './device-unread-indicator';
|
|
10
11
|
import { useDevicesViewConfig } from './devices-view-config';
|
|
11
12
|
import type { DeviceRow } from './types';
|
|
12
13
|
|
|
@@ -102,7 +103,14 @@ export function DevicesGrid<T extends DeviceRow = DeviceRow>({
|
|
|
102
103
|
: undefined
|
|
103
104
|
}
|
|
104
105
|
onDeviceClick={() => handleDeviceClick(device)}
|
|
105
|
-
actions={
|
|
106
|
+
actions={
|
|
107
|
+
device.unreadNotificationCount
|
|
108
|
+
? {
|
|
109
|
+
...GRID_CARD_ACTIONS,
|
|
110
|
+
detailsButton: { component: <DeviceUnreadIndicator device={device} /> },
|
|
111
|
+
}
|
|
112
|
+
: GRID_CARD_ACTIONS
|
|
113
|
+
}
|
|
106
114
|
className="h-full"
|
|
107
115
|
// One card of a read list: how many there are is the record's (the height gate counts them).
|
|
108
116
|
data-loading-item
|
|
@@ -27,6 +27,7 @@ import { getDeviceName } from './device-name';
|
|
|
27
27
|
import { getDeviceStatusConfig } from './device-status';
|
|
28
28
|
import { DEFAULT_VISIBLE_STATUSES } from './device-statuses';
|
|
29
29
|
import { DeviceTypeTile } from './device-type-tile';
|
|
30
|
+
import { DeviceUnreadIndicator } from './device-unread-indicator';
|
|
30
31
|
import { useDeviceImageUrl, useDevicesViewConfig } from './devices-view-config';
|
|
31
32
|
import type { DeviceFilters, DeviceRow } from './types';
|
|
32
33
|
|
|
@@ -341,6 +342,8 @@ export function getDeviceTableColumns<T extends DeviceRow = DeviceRow>(
|
|
|
341
342
|
<div className="min-w-0 flex-1">
|
|
342
343
|
<TruncateText>{getDeviceName(device)}</TruncateText>
|
|
343
344
|
</div>
|
|
345
|
+
{/* In the name's cell, not the open column: that one is dropped on a narrow page. */}
|
|
346
|
+
<DeviceUnreadIndicator device={device} />
|
|
344
347
|
</div>
|
|
345
348
|
);
|
|
346
349
|
},
|
|
@@ -5,14 +5,16 @@ import type { DeviceRow } from './types';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* What the device views cannot know by themselves, because it belongs to the
|
|
8
|
-
* host: where a device's page
|
|
9
|
-
* the page runs inside the mobile shell. The product
|
|
10
|
-
* every page; without a provider the views render as a
|
|
11
|
-
* image URLs used as given).
|
|
8
|
+
* host: where a device's page and its notifications live, where images are
|
|
9
|
+
* served from, and whether the page runs inside the mobile shell. The product
|
|
10
|
+
* provides it once, above every page; without a provider the views render as a
|
|
11
|
+
* picture (no links, image URLs used as given).
|
|
12
12
|
*/
|
|
13
13
|
export interface DevicesViewConfig {
|
|
14
14
|
/** The device's page. Absent: rows and cards are not links. */
|
|
15
15
|
getDeviceHref?: (device: DeviceRow) => string | undefined;
|
|
16
|
+
/** The viewer's notifications about the device. Absent: the unread indicator is a counter, not a button. */
|
|
17
|
+
getDeviceNotificationsHref?: (device: DeviceRow) => string | undefined;
|
|
16
18
|
/** A customer image's full URL from its stored path and content hash. */
|
|
17
19
|
resolveImageUrl?: (imageUrl: string | null | undefined, hash?: string | null) => string | undefined;
|
|
18
20
|
/** The page runs inside the mobile shell: options nobody can pick there are dropped. */
|
|
@@ -38,6 +38,7 @@ export {
|
|
|
38
38
|
export { DeviceTagsFilterButton } from './device-tags-filter-button';
|
|
39
39
|
export { renderDeviceTypeIcon } from './device-type-icon';
|
|
40
40
|
export { DeviceTypeTile } from './device-type-tile';
|
|
41
|
+
export { DeviceUnreadIndicator } from './device-unread-indicator';
|
|
41
42
|
export { DevicesFilterToolbar, type DevicesFilterToolbarProps } from './devices-filter-toolbar';
|
|
42
43
|
export { DevicesGrid, type DevicesGridProps } from './devices-grid';
|
|
43
44
|
export { DevicesGridFilters, type DevicesGridFiltersProps } from './devices-grid-filters';
|
|
@@ -34,6 +34,8 @@ export interface DeviceRow extends DeviceNameSource {
|
|
|
34
34
|
organizationImageUrl?: string | null;
|
|
35
35
|
organizationImageHash?: string | null;
|
|
36
36
|
tags?: DeviceTag[];
|
|
37
|
+
/** The viewer's unread notifications about this device. Absent or 0: no indicator. */
|
|
38
|
+
unreadNotificationCount?: number;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
export interface DeviceFilterValue {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
|
|
5
|
+
export interface CountPillProps {
|
|
6
|
+
count: number;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The navigation's unread count: a 24px accent pill, widening for "99+". The grouped sidebar ends its rows with it,
|
|
12
|
+
* and a list row that counts the same notifications draws the same pill, so the two read alike.
|
|
13
|
+
*/
|
|
14
|
+
export function CountPill({ count, className }: CountPillProps) {
|
|
15
|
+
return (
|
|
16
|
+
<span
|
|
17
|
+
className={cn(
|
|
18
|
+
'flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md bg-ods-accent px-[var(--spacing-system-xxs)]',
|
|
19
|
+
className,
|
|
20
|
+
)}
|
|
21
|
+
>
|
|
22
|
+
<span className="text-ods-text-on-accent text-h5">{count > 99 ? '99+' : count}</span>
|
|
23
|
+
</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -90,6 +90,10 @@ export { HeaderButton } from './header-button';
|
|
|
90
90
|
export type { HeaderButtonProps } from './header-button';
|
|
91
91
|
export { UnreadDot, UnreadCountBadge } from './unread-dot';
|
|
92
92
|
export type { UnreadDotProps, UnreadCountBadgeProps } from './unread-dot';
|
|
93
|
+
export { CountPill } from './count-pill';
|
|
94
|
+
export type { CountPillProps } from './count-pill';
|
|
95
|
+
export { UnreadNotificationsIndicator, unreadNotificationsLabel } from './unread-notifications-indicator';
|
|
96
|
+
export type { UnreadNotificationsIndicatorProps } from './unread-notifications-indicator';
|
|
93
97
|
export { NavigationItemBadge } from './navigation-item-badge';
|
|
94
98
|
export type { NavigationItemBadgeProps } from './navigation-item-badge';
|
|
95
99
|
export { TicketAlertsButton } from './ticket-alerts-button';
|
|
@@ -7,6 +7,7 @@ import type { NavigationSidebarItem } from '../../types/navigation';
|
|
|
7
7
|
import { cn } from '../../utils';
|
|
8
8
|
import { UnifiedSkeleton } from '../loading/unified-skeleton';
|
|
9
9
|
import { FloatingTooltip } from '../ui/floating-tooltip';
|
|
10
|
+
import { CountPill } from './count-pill';
|
|
10
11
|
import { NavigationItemBadge } from './navigation-item-badge';
|
|
11
12
|
import { UnreadDot } from './unread-dot';
|
|
12
13
|
|
|
@@ -133,20 +134,6 @@ interface RowStateProps {
|
|
|
133
134
|
disabled: boolean;
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
/** The count pill: 24px, widening for "99+". */
|
|
137
|
-
function CountPill({ count, className }: { count: number; className?: string }) {
|
|
138
|
-
return (
|
|
139
|
-
<span
|
|
140
|
-
className={cn(
|
|
141
|
-
'flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md bg-ods-accent px-[var(--spacing-system-xxs)]',
|
|
142
|
-
className,
|
|
143
|
-
)}
|
|
144
|
-
>
|
|
145
|
-
<span className="text-ods-text-on-accent text-h5">{count > 99 ? '99+' : count}</span>
|
|
146
|
-
</span>
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
137
|
/** The row's end, flush right where every row's count sits. */
|
|
151
138
|
function RowEnd({ count }: { count: number }) {
|
|
152
139
|
return count > 0 ? <CountPill count={count} className="ml-auto" /> : null;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { MouseEvent } from 'react';
|
|
4
|
+
import { useChatRuntime } from '../../contexts/chat-runtime-context';
|
|
5
|
+
import { cn } from '../../utils/cn';
|
|
6
|
+
import { isModifierClick } from '../chat/utils/chat-nav-resolution';
|
|
7
|
+
import { executeNavigationImperative, executeNewTabNavigation } from '../chat/utils/execute-navigation';
|
|
8
|
+
import { CountPill } from './count-pill';
|
|
9
|
+
|
|
10
|
+
/** What the indicator says to a screen reader: the count, in words. */
|
|
11
|
+
export function unreadNotificationsLabel(count: number): string {
|
|
12
|
+
return `${count} unread ${count === 1 ? 'notification' : 'notifications'}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UnreadNotificationsIndicatorProps {
|
|
16
|
+
/** The viewer's unread notifications about one item. Nothing is drawn at 0. */
|
|
17
|
+
count: number;
|
|
18
|
+
/** Where those notifications are listed. Absent: the count alone, not a button. */
|
|
19
|
+
href?: string;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The viewer's unread notifications about one item of a list (a device, an incident), drawn with the sidebar's own
|
|
25
|
+
* count pill so a row and its section's badge read alike.
|
|
26
|
+
*
|
|
27
|
+
* A button, not a link: the row or card it sits in is usually a link itself, and a link cannot hold another
|
|
28
|
+
* (`data-no-row-click` keeps a table row from following its own). It goes through the lib's navigation executor,
|
|
29
|
+
* so the host decides how `href` opens, and a modifier click asks for a new tab as it would on a link.
|
|
30
|
+
*/
|
|
31
|
+
export function UnreadNotificationsIndicator({ count, href, className }: UnreadNotificationsIndicatorProps) {
|
|
32
|
+
const runtime = useChatRuntime();
|
|
33
|
+
if (count <= 0) return null;
|
|
34
|
+
|
|
35
|
+
const label = unreadNotificationsLabel(count);
|
|
36
|
+
|
|
37
|
+
if (!href) {
|
|
38
|
+
return (
|
|
39
|
+
<span data-loading-optional role="img" aria-label={label} className={cn('inline-flex shrink-0', className)}>
|
|
40
|
+
<CountPill count={count} />
|
|
41
|
+
</span>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const open = (event: MouseEvent) => {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
event.stopPropagation();
|
|
48
|
+
if (isModifierClick(event)) executeNewTabNavigation({ runtime, href });
|
|
49
|
+
else executeNavigationImperative({ runtime, href });
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
data-loading-optional
|
|
56
|
+
data-no-row-click
|
|
57
|
+
onClick={open}
|
|
58
|
+
aria-label={label}
|
|
59
|
+
title={label}
|
|
60
|
+
className={cn(
|
|
61
|
+
'pointer-events-auto inline-flex shrink-0 rounded-md transition-opacity hover:opacity-80',
|
|
62
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus',
|
|
63
|
+
className,
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
<CountPill count={count} />
|
|
67
|
+
</button>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -4,6 +4,12 @@ import { type DEMO_DEVICES, DEMO_FLEET, DEMO_FLEET_SEARCH_OS, demoMinutesAgo } f
|
|
|
4
4
|
|
|
5
5
|
type DemoDevice = (typeof DEMO_DEVICES)[keyof typeof DEMO_DEVICES];
|
|
6
6
|
|
|
7
|
+
/** The viewer's unread notifications per device: the offline reception desktop's recordings failed overnight. */
|
|
8
|
+
const DEMO_UNREAD_NOTIFICATIONS: Partial<Record<DemoDevice['id'], number>> = {
|
|
9
|
+
'device-reception': 2,
|
|
10
|
+
'device-leo': 1,
|
|
11
|
+
};
|
|
12
|
+
|
|
7
13
|
function device(
|
|
8
14
|
cast: DemoCast,
|
|
9
15
|
demo: DemoDevice,
|
|
@@ -24,6 +30,7 @@ function device(
|
|
|
24
30
|
organizationId: organization.id,
|
|
25
31
|
organization: organization.name,
|
|
26
32
|
organizationImageUrl: organization.logoUrl,
|
|
33
|
+
unreadNotificationCount: DEMO_UNREAD_NOTIFICATIONS[demo.id],
|
|
27
34
|
};
|
|
28
35
|
}
|
|
29
36
|
|
package/src/platform-domains.ts
CHANGED
|
@@ -309,18 +309,37 @@ export function isPreviewHost(hostname: string): boolean {
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
/**
|
|
312
|
-
*
|
|
312
|
+
* The registrable base of every site in the registry, each as `.base` and bare `base`: for each non-pseudo platform,
|
|
313
|
+
* the base of its resolved host. It reads the registry only, so it answers the same on a server and in a browser;
|
|
314
|
+
* a server's cookie rule reads it directly.
|
|
315
|
+
*
|
|
316
|
+
* Because `getPlatformProductionUrl` always yields a host (override OR default),
|
|
317
|
+
* `.flamingo.so` / `.flamingo.run` / `.openmsp.ai` / `.tmcg.miami` / `.openframe.ai`
|
|
318
|
+
* are ALWAYS present → cross-hub SSO is byte-identical to today.
|
|
319
|
+
*/
|
|
320
|
+
export function getPlatformBaseDomains(): string[] {
|
|
321
|
+
const baseDomains = new Set<string>();
|
|
322
|
+
for (const entry of PLATFORM_DOMAINS) {
|
|
323
|
+
if (entry.pseudo) continue;
|
|
324
|
+
const host = hostOf(getPlatformProductionUrl(entry.key));
|
|
325
|
+
if (!host) continue;
|
|
326
|
+
const base = toRegistrableBaseDomain(host);
|
|
327
|
+
if (base) {
|
|
328
|
+
baseDomains.add(`.${base}`);
|
|
329
|
+
baseDomains.add(base);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return Array.from(baseDomains);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* ALL unique cookie base domains for the PAGE this runs on (the cross-subdomain SSO mechanism).
|
|
313
337
|
*
|
|
314
338
|
* NON-PURE (the sole such export): reads `typeof window` + `process.env` to
|
|
315
339
|
* preserve byte-identical cookie behavior. Keeps the original three branches:
|
|
316
340
|
* 1. localhost / private IP → [] (host-only cookies)
|
|
317
341
|
* 2. Vercel preview (env OR host) → ['.vercel.app','vercel.app']
|
|
318
|
-
* 3. production →
|
|
319
|
-
* host, emitted as both `.base` and bare `base`.
|
|
320
|
-
*
|
|
321
|
-
* Because `getPlatformProductionUrl` always yields a host (override OR default),
|
|
322
|
-
* `.flamingo.so` / `.flamingo.run` / `.openmsp.ai` / `.tmcg.miami` / `.openframe.ai`
|
|
323
|
-
* are ALWAYS present → cross-hub SSO is byte-identical to today.
|
|
342
|
+
* 3. production → `getPlatformBaseDomains`.
|
|
324
343
|
*/
|
|
325
344
|
export function getAllPlatformBaseDomains(): string[] {
|
|
326
345
|
if (typeof window === 'undefined') return [];
|
|
@@ -339,19 +358,8 @@ export function getAllPlatformBaseDomains(): string[] {
|
|
|
339
358
|
return ['.vercel.app', 'vercel.app'];
|
|
340
359
|
}
|
|
341
360
|
|
|
342
|
-
// Case 3: production
|
|
343
|
-
|
|
344
|
-
for (const entry of PLATFORM_DOMAINS) {
|
|
345
|
-
if (entry.pseudo) continue;
|
|
346
|
-
const host = hostOf(getPlatformProductionUrl(entry.key));
|
|
347
|
-
if (!host) continue;
|
|
348
|
-
const base = toRegistrableBaseDomain(host);
|
|
349
|
-
if (base) {
|
|
350
|
-
baseDomains.add(`.${base}`);
|
|
351
|
-
baseDomains.add(base);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return Array.from(baseDomains);
|
|
361
|
+
// Case 3: production
|
|
362
|
+
return getPlatformBaseDomains();
|
|
355
363
|
}
|
|
356
364
|
|
|
357
365
|
// ── Cookie-domain guard + match (single owner; shared by the client cookie-domain.ts +
|