@flamingo-stack/openframe-frontend-core 0.0.691-snapshot.20261001154617 → 0.0.691
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-RLNNV46K.js → chunk-4HZXOQRM.js} +2 -2
- package/dist/{chunk-FTK3S57R.cjs → chunk-4N2JOPAQ.cjs} +13 -13
- package/dist/{chunk-FTK3S57R.cjs.map → chunk-4N2JOPAQ.cjs.map} +1 -1
- package/dist/{chunk-WTYQCT4A.cjs → chunk-5N2ZSB42.cjs} +10 -10
- package/dist/{chunk-WTYQCT4A.cjs.map → chunk-5N2ZSB42.cjs.map} +1 -1
- package/dist/{chunk-A5NHAHV6.js → chunk-6Z26ZMR4.js} +2 -2
- package/dist/{chunk-Z7QCNCBN.js → chunk-7WYWPELF.js} +5 -5
- package/dist/{chunk-RTKF5AYA.cjs → chunk-BXAGR3PO.cjs} +3 -3
- package/dist/{chunk-RTKF5AYA.cjs.map → chunk-BXAGR3PO.cjs.map} +1 -1
- package/dist/{chunk-KDCIYRZV.cjs → chunk-BXE2RZEV.cjs} +17 -17
- package/dist/{chunk-KDCIYRZV.cjs.map → chunk-BXE2RZEV.cjs.map} +1 -1
- package/dist/{chunk-X5XFKI6N.js → chunk-CMYDGVTZ.js} +2 -2
- package/dist/{chunk-EXWS2QSD.cjs → chunk-CWXRZVDI.cjs} +50 -588
- package/dist/chunk-CWXRZVDI.cjs.map +1 -0
- package/dist/{chunk-PKZGXRU2.js → chunk-DC3G4XGP.js} +3 -3
- package/dist/{chunk-CG7OWW7X.cjs → chunk-DDWLF2JV.cjs} +4 -4
- package/dist/{chunk-CG7OWW7X.cjs.map → chunk-DDWLF2JV.cjs.map} +1 -1
- package/dist/{chunk-ZAV2F4VX.js → chunk-DZMDG7PC.js} +2 -2
- package/dist/{chunk-KQZMM3QJ.cjs → chunk-ESFPY5FF.cjs} +51 -51
- package/dist/{chunk-KQZMM3QJ.cjs.map → chunk-ESFPY5FF.cjs.map} +1 -1
- package/dist/{chunk-AVAZ6BD6.js → chunk-FNMQ2J44.js} +2 -2
- package/dist/{chunk-DKJDO7YM.cjs → chunk-H54RDLVC.cjs} +18 -18
- package/dist/{chunk-DKJDO7YM.cjs.map → chunk-H54RDLVC.cjs.map} +1 -1
- package/dist/{chunk-RK47E3PP.js → chunk-JSG7WZKV.js} +2 -2
- package/dist/{chunk-CKO6NKNE.js → chunk-KMW2PXBU.js} +212 -750
- package/dist/chunk-KMW2PXBU.js.map +1 -0
- package/dist/{chunk-MCNQKS6I.js → chunk-KVDLL4LU.js} +2 -2
- package/dist/{chunk-5XPK2QGU.cjs → chunk-LCENRN2N.cjs} +3 -3
- package/dist/{chunk-5XPK2QGU.cjs.map → chunk-LCENRN2N.cjs.map} +1 -1
- package/dist/{chunk-IQTQIBZ3.js → chunk-MJI5GVT7.js} +4 -4
- package/dist/{chunk-DUURFYGS.cjs → chunk-N4S7RBUM.cjs} +3 -3
- package/dist/{chunk-DUURFYGS.cjs.map → chunk-N4S7RBUM.cjs.map} +1 -1
- package/dist/{chunk-7BNWMOPM.cjs → chunk-SA4KQCNB.cjs} +36 -36
- package/dist/{chunk-7BNWMOPM.cjs.map → chunk-SA4KQCNB.cjs.map} +1 -1
- package/dist/{chunk-LYQULSK4.js → chunk-TSNGVWQO.js} +5 -5
- package/dist/{chunk-F6LXGP6S.cjs → chunk-WZYIAKIH.cjs} +3 -3
- package/dist/{chunk-F6LXGP6S.cjs.map → chunk-WZYIAKIH.cjs.map} +1 -1
- package/dist/{chunk-TXFGG47A.js → chunk-XBYF4SGD.js} +2 -2
- package/dist/{chunk-Q25VYAEL.cjs → chunk-Y3BC7TOJ.cjs} +17 -17
- package/dist/{chunk-Q25VYAEL.cjs.map → chunk-Y3BC7TOJ.cjs.map} +1 -1
- package/dist/{chunk-ATREVNIN.cjs → chunk-Y3Q76RAA.cjs} +33 -33
- package/dist/{chunk-ATREVNIN.cjs.map → chunk-Y3Q76RAA.cjs.map} +1 -1
- package/dist/{chunk-LYJJH4D6.js → chunk-Y7ZQZOE6.js} +2 -2
- package/dist/{chunk-TEIHEFXP.js → chunk-Z5FHXCXF.js} +4 -4
- package/dist/{chunk-CKUPANVU.cjs → chunk-ZLYLHP64.cjs} +4 -4
- package/dist/{chunk-CKUPANVU.cjs.map → chunk-ZLYLHP64.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +11 -11
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +6 -6
- package/dist/components/docs/index.js +5 -5
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -34
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.d.ts +0 -1
- package/dist/components/features/index.d.ts.map +1 -1
- package/dist/components/features/index.js +1 -33
- package/dist/components/help-center-pages/index.cjs +49 -49
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +32 -64
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +12 -44
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +22 -22
- package/dist/components/meeting-scheduler/index.js +1 -1
- package/dist/components/navigation/index.cjs +5 -5
- package/dist/components/navigation/index.js +4 -4
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/index.cjs +6 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/features/index.ts +0 -1
- package/dist/chunk-CKO6NKNE.js.map +0 -1
- package/dist/chunk-EXWS2QSD.cjs.map +0 -1
- package/dist/components/features/remote-session/index.d.ts +0 -13
- package/dist/components/features/remote-session/index.d.ts.map +0 -1
- package/dist/components/features/remote-session/remote-access-request-card.d.ts +0 -19
- package/dist/components/features/remote-session/remote-access-request-card.d.ts.map +0 -1
- package/dist/components/features/remote-session/remote-session-block.d.ts +0 -41
- package/dist/components/features/remote-session/remote-session-block.d.ts.map +0 -1
- package/dist/components/features/remote-session/remote-session-chat-panel.d.ts +0 -23
- package/dist/components/features/remote-session/remote-session-chat-panel.d.ts.map +0 -1
- package/dist/components/features/remote-session/remote-session-dialog.d.ts +0 -15
- package/dist/components/features/remote-session/remote-session-dialog.d.ts.map +0 -1
- package/dist/components/features/remote-session/remote-session-overlay.d.ts +0 -12
- package/dist/components/features/remote-session/remote-session-overlay.d.ts.map +0 -1
- package/dist/components/features/remote-session/remote-session-parts.d.ts +0 -34
- package/dist/components/features/remote-session/remote-session-parts.d.ts.map +0 -1
- package/dist/components/features/remote-session/types.d.ts +0 -33
- package/dist/components/features/remote-session/types.d.ts.map +0 -1
- package/src/components/features/__tests__/remote-session.test.tsx +0 -97
- package/src/components/features/remote-session/index.ts +0 -36
- package/src/components/features/remote-session/remote-access-request-card.tsx +0 -102
- package/src/components/features/remote-session/remote-session-block.tsx +0 -182
- package/src/components/features/remote-session/remote-session-chat-panel.tsx +0 -178
- package/src/components/features/remote-session/remote-session-dialog.tsx +0 -77
- package/src/components/features/remote-session/remote-session-overlay.tsx +0 -33
- package/src/components/features/remote-session/remote-session-parts.tsx +0 -138
- package/src/components/features/remote-session/types.ts +0 -41
- package/src/stories/RemoteSessionAccessRequest.stories.tsx +0 -71
- package/src/stories/RemoteSessionBlock.stories.tsx +0 -164
- package/src/stories/RemoteSessionDialog.stories.tsx +0 -49
- package/src/stories/RemoteSessionIndicator.stories.tsx +0 -44
- /package/dist/{chunk-RLNNV46K.js.map → chunk-4HZXOQRM.js.map} +0 -0
- /package/dist/{chunk-A5NHAHV6.js.map → chunk-6Z26ZMR4.js.map} +0 -0
- /package/dist/{chunk-Z7QCNCBN.js.map → chunk-7WYWPELF.js.map} +0 -0
- /package/dist/{chunk-X5XFKI6N.js.map → chunk-CMYDGVTZ.js.map} +0 -0
- /package/dist/{chunk-PKZGXRU2.js.map → chunk-DC3G4XGP.js.map} +0 -0
- /package/dist/{chunk-ZAV2F4VX.js.map → chunk-DZMDG7PC.js.map} +0 -0
- /package/dist/{chunk-AVAZ6BD6.js.map → chunk-FNMQ2J44.js.map} +0 -0
- /package/dist/{chunk-RK47E3PP.js.map → chunk-JSG7WZKV.js.map} +0 -0
- /package/dist/{chunk-MCNQKS6I.js.map → chunk-KVDLL4LU.js.map} +0 -0
- /package/dist/{chunk-IQTQIBZ3.js.map → chunk-MJI5GVT7.js.map} +0 -0
- /package/dist/{chunk-LYQULSK4.js.map → chunk-TSNGVWQO.js.map} +0 -0
- /package/dist/{chunk-TXFGG47A.js.map → chunk-XBYF4SGD.js.map} +0 -0
- /package/dist/{chunk-LYJJH4D6.js.map → chunk-Y7ZQZOE6.js.map} +0 -0
- /package/dist/{chunk-TEIHEFXP.js.map → chunk-Z5FHXCXF.js.map} +0 -0
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { forwardRef, type ReactNode } from 'react';
|
|
4
|
-
import { cn } from '../../../utils/cn';
|
|
5
|
-
import { ChatsIcon, Chevrons03LeftIcon, Chevrons03RightIcon, MonitorOffIcon } from '../../icons-v2-generated';
|
|
6
|
-
import { OpenFrameLogo } from '../../icons/openframe-logo';
|
|
7
|
-
import { Button } from '../../ui/button/button';
|
|
8
|
-
import { SquareAvatar } from '../../ui/square-avatar';
|
|
9
|
-
import {
|
|
10
|
-
REMOTE_SESSION_DRAG_HANDLE_CLASS,
|
|
11
|
-
RemoteSessionDragger,
|
|
12
|
-
RemoteSessionOrgLogo,
|
|
13
|
-
RemoteSessionRecordingTag,
|
|
14
|
-
} from './remote-session-parts';
|
|
15
|
-
import type { RemoteSessionDragHandlers, RemoteSessionParty } from './types';
|
|
16
|
-
|
|
17
|
-
export interface RemoteSessionBlockFrameProps {
|
|
18
|
-
/** The summary or the chat panel. */
|
|
19
|
-
children: ReactNode;
|
|
20
|
-
/** The chat panel has a fixed height; the summary hugs its content. */
|
|
21
|
-
expanded?: boolean;
|
|
22
|
-
onHide: () => void;
|
|
23
|
-
className?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The session block's card: 440 px wide, the open panel on top and the
|
|
28
|
-
* "Hide this Block" bar at the bottom. The ref is the rendered surface, which
|
|
29
|
-
* a desktop host sizes its window to.
|
|
30
|
-
*/
|
|
31
|
-
export const RemoteSessionBlockFrame = forwardRef<HTMLDivElement, RemoteSessionBlockFrameProps>(
|
|
32
|
-
({ children, expanded = false, onHide, className }, ref) => {
|
|
33
|
-
return (
|
|
34
|
-
<div
|
|
35
|
-
ref={ref}
|
|
36
|
-
className={cn(
|
|
37
|
-
'flex w-[440px] shrink-0 flex-col overflow-hidden rounded-md border border-ods-border bg-ods-bg',
|
|
38
|
-
expanded && 'h-[680px]',
|
|
39
|
-
className,
|
|
40
|
-
)}
|
|
41
|
-
>
|
|
42
|
-
{children}
|
|
43
|
-
<button
|
|
44
|
-
type="button"
|
|
45
|
-
className="flex h-12 w-full shrink-0 items-center gap-[var(--spacing-system-xs)] border-t border-ods-border bg-ods-card px-[var(--spacing-system-m)]"
|
|
46
|
-
onClick={onHide}
|
|
47
|
-
>
|
|
48
|
-
<Chevrons03RightIcon size={24} />
|
|
49
|
-
<span className="min-w-0 flex-1 truncate text-left text-ods-text-secondary text-h4">Hide this Block</span>
|
|
50
|
-
<OpenFrameLogo className="size-6 shrink-0" />
|
|
51
|
-
</button>
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
},
|
|
55
|
-
);
|
|
56
|
-
RemoteSessionBlockFrame.displayName = 'RemoteSessionBlockFrame';
|
|
57
|
-
|
|
58
|
-
/** The red End Session action shared by the summary and the chat panel. */
|
|
59
|
-
export function RemoteSessionEndButton({ onEndSession }: { onEndSession: () => void }) {
|
|
60
|
-
return (
|
|
61
|
-
<Button
|
|
62
|
-
variant="outline"
|
|
63
|
-
fullWidth
|
|
64
|
-
leftIcon={<MonitorOffIcon size={24} color="currentColor" className="text-ods-error" />}
|
|
65
|
-
onClick={onEndSession}
|
|
66
|
-
>
|
|
67
|
-
End Session
|
|
68
|
-
</Button>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface RemoteSessionSummaryProps {
|
|
73
|
-
party: RemoteSessionParty;
|
|
74
|
-
/** Elapsed session time, e.g. from `useRemoteSessionTimer`. */
|
|
75
|
-
elapsed: string;
|
|
76
|
-
showRecordingTag?: boolean;
|
|
77
|
-
onOpenChat: () => void;
|
|
78
|
-
onEndSession: () => void;
|
|
79
|
-
/** Makes the header row and the dragger move the window. */
|
|
80
|
-
dragHandlers?: RemoteSessionDragHandlers;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** The compact block: who is connected and for how long, Open Chat / End Session. */
|
|
84
|
-
export function RemoteSessionSummary({
|
|
85
|
-
party,
|
|
86
|
-
elapsed,
|
|
87
|
-
showRecordingTag = false,
|
|
88
|
-
onOpenChat,
|
|
89
|
-
onEndSession,
|
|
90
|
-
dragHandlers,
|
|
91
|
-
}: RemoteSessionSummaryProps) {
|
|
92
|
-
const technician = party.technicianName;
|
|
93
|
-
return (
|
|
94
|
-
<div className="relative flex flex-col gap-[var(--spacing-system-m)] p-[var(--spacing-system-m)]">
|
|
95
|
-
<RemoteSessionDragger dragHandlers={dragHandlers} />
|
|
96
|
-
<div
|
|
97
|
-
className={cn(
|
|
98
|
-
'flex w-full items-center gap-[var(--spacing-system-xs)] pr-[var(--spacing-system-mf)]',
|
|
99
|
-
dragHandlers && REMOTE_SESSION_DRAG_HANDLE_CLASS,
|
|
100
|
-
)}
|
|
101
|
-
{...dragHandlers}
|
|
102
|
-
>
|
|
103
|
-
{technician ? (
|
|
104
|
-
<SquareAvatar
|
|
105
|
-
variant="round"
|
|
106
|
-
sizePx={48}
|
|
107
|
-
src={party.technicianAvatarUrl ?? undefined}
|
|
108
|
-
fallback={technician}
|
|
109
|
-
/>
|
|
110
|
-
) : (
|
|
111
|
-
<RemoteSessionOrgLogo name={party.organizationName} logoUrl={party.organizationLogoUrl} sizePx={48} />
|
|
112
|
-
)}
|
|
113
|
-
<div className="flex min-w-0 flex-1 flex-col">
|
|
114
|
-
<p className="w-full truncate text-ods-text-primary text-h4">
|
|
115
|
-
{technician ? `${technician} connected` : 'IT support team is connected'}{' '}
|
|
116
|
-
<span className="text-ods-text-secondary">({elapsed})</span>
|
|
117
|
-
</p>
|
|
118
|
-
<p className="w-full truncate text-ods-text-secondary text-h4">{party.organizationName}</p>
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
{showRecordingTag && <RemoteSessionRecordingTag />}
|
|
122
|
-
<div className="flex w-full items-stretch gap-[var(--spacing-system-m)]">
|
|
123
|
-
<Button
|
|
124
|
-
variant="outline"
|
|
125
|
-
fullWidth
|
|
126
|
-
leftIcon={<ChatsIcon size={24} color="currentColor" />}
|
|
127
|
-
onClick={onOpenChat}
|
|
128
|
-
>
|
|
129
|
-
Open Chat
|
|
130
|
-
</Button>
|
|
131
|
-
<RemoteSessionEndButton onEndSession={onEndSession} />
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export interface RemoteSessionMinimizedChipProps {
|
|
138
|
-
party: RemoteSessionParty;
|
|
139
|
-
onExpand: () => void;
|
|
140
|
-
/** Lets the chip move the window too; the host tells a drag from a click. */
|
|
141
|
-
dragHandlers?: RemoteSessionDragHandlers;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** The hidden block: the technician's avatar (or the organization's logo) and a chevron to bring it back. */
|
|
145
|
-
export const RemoteSessionMinimizedChip = forwardRef<HTMLButtonElement, RemoteSessionMinimizedChipProps>(
|
|
146
|
-
({ party, onExpand, dragHandlers }, ref) => {
|
|
147
|
-
const technician = party.technicianName;
|
|
148
|
-
return (
|
|
149
|
-
<button
|
|
150
|
-
ref={ref}
|
|
151
|
-
type="button"
|
|
152
|
-
aria-label="Show session block"
|
|
153
|
-
className={cn(
|
|
154
|
-
'flex shrink-0 flex-col overflow-hidden rounded-md border border-ods-border',
|
|
155
|
-
dragHandlers && REMOTE_SESSION_DRAG_HANDLE_CLASS,
|
|
156
|
-
)}
|
|
157
|
-
onClick={onExpand}
|
|
158
|
-
{...dragHandlers}
|
|
159
|
-
>
|
|
160
|
-
{technician ? (
|
|
161
|
-
<SquareAvatar
|
|
162
|
-
sizePx={48}
|
|
163
|
-
src={party.technicianAvatarUrl ?? undefined}
|
|
164
|
-
fallback={technician}
|
|
165
|
-
className="rounded-none"
|
|
166
|
-
/>
|
|
167
|
-
) : (
|
|
168
|
-
<RemoteSessionOrgLogo
|
|
169
|
-
name={party.organizationName}
|
|
170
|
-
logoUrl={party.organizationLogoUrl}
|
|
171
|
-
sizePx={48}
|
|
172
|
-
className="rounded-none border-0"
|
|
173
|
-
/>
|
|
174
|
-
)}
|
|
175
|
-
<span className="flex h-9 w-12 items-center justify-center border-t border-ods-border bg-ods-card">
|
|
176
|
-
<Chevrons03LeftIcon size={24} />
|
|
177
|
-
</span>
|
|
178
|
-
</button>
|
|
179
|
-
);
|
|
180
|
-
},
|
|
181
|
-
);
|
|
182
|
-
RemoteSessionMinimizedChip.displayName = 'RemoteSessionMinimizedChip';
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef } from 'react';
|
|
4
|
-
import { cn } from '../../../utils/cn';
|
|
5
|
-
import { MessageOffIcon, Send03Icon } from '../../icons-v2-generated';
|
|
6
|
-
import { Button } from '../../ui/button/button';
|
|
7
|
-
import { SquareAvatar } from '../../ui/square-avatar';
|
|
8
|
-
import { RemoteSessionEndButton } from './remote-session-block';
|
|
9
|
-
import {
|
|
10
|
-
REMOTE_SESSION_DRAG_HANDLE_CLASS,
|
|
11
|
-
RemoteSessionDragger,
|
|
12
|
-
RemoteSessionRecordingTag,
|
|
13
|
-
} from './remote-session-parts';
|
|
14
|
-
import type { RemoteSessionChatMessage, RemoteSessionDragHandlers, RemoteSessionParty } from './types';
|
|
15
|
-
|
|
16
|
-
export interface RemoteSessionChatPanelProps {
|
|
17
|
-
party: RemoteSessionParty;
|
|
18
|
-
/** Elapsed session time, e.g. from `useRemoteSessionTimer`. */
|
|
19
|
-
elapsed: string;
|
|
20
|
-
messages: RemoteSessionChatMessage[];
|
|
21
|
-
showRecordingTag?: boolean;
|
|
22
|
-
/** The composer's text; owned by the host so it survives hiding the block. */
|
|
23
|
-
draft: string;
|
|
24
|
-
onDraftChange: (draft: string) => void;
|
|
25
|
-
/** Called with the trimmed text; the composer clears itself through `onDraftChange`. */
|
|
26
|
-
onSend: (text: string) => void;
|
|
27
|
-
/** False while the chat is still connecting: the composer is disabled. */
|
|
28
|
-
ready?: boolean;
|
|
29
|
-
error?: string | null;
|
|
30
|
-
onCloseChat: () => void;
|
|
31
|
-
onEndSession: () => void;
|
|
32
|
-
/** Makes the header and the dragger move the window. */
|
|
33
|
-
dragHandlers?: RemoteSessionDragHandlers;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function formatTime(at: Date): string {
|
|
37
|
-
return at.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** The chat with the technician inside the session block. */
|
|
41
|
-
export function RemoteSessionChatPanel({
|
|
42
|
-
party,
|
|
43
|
-
elapsed,
|
|
44
|
-
messages,
|
|
45
|
-
showRecordingTag = false,
|
|
46
|
-
draft,
|
|
47
|
-
onDraftChange,
|
|
48
|
-
onSend,
|
|
49
|
-
ready = true,
|
|
50
|
-
error,
|
|
51
|
-
onCloseChat,
|
|
52
|
-
onEndSession,
|
|
53
|
-
dragHandlers,
|
|
54
|
-
}: RemoteSessionChatPanelProps) {
|
|
55
|
-
const listRef = useRef<HTMLDivElement>(null);
|
|
56
|
-
|
|
57
|
-
// The list is the trigger: the newest row must come into view on every change.
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
const list = listRef.current;
|
|
60
|
-
if (list) list.scrollTop = list.scrollHeight;
|
|
61
|
-
}, [messages]);
|
|
62
|
-
|
|
63
|
-
const handleSend = () => {
|
|
64
|
-
const text = draft.trim();
|
|
65
|
-
if (!text) return;
|
|
66
|
-
onSend(text);
|
|
67
|
-
onDraftChange('');
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<div className="flex min-h-0 flex-1 flex-col">
|
|
72
|
-
<div className="relative flex flex-col gap-[var(--spacing-system-m)] p-[var(--spacing-system-m)]">
|
|
73
|
-
<RemoteSessionDragger dragHandlers={dragHandlers} />
|
|
74
|
-
<div
|
|
75
|
-
className={cn(
|
|
76
|
-
'flex w-full flex-col pr-[var(--spacing-system-mf)]',
|
|
77
|
-
dragHandlers && REMOTE_SESSION_DRAG_HANDLE_CLASS,
|
|
78
|
-
)}
|
|
79
|
-
{...dragHandlers}
|
|
80
|
-
>
|
|
81
|
-
<p className="w-full truncate text-ods-text-primary text-h4">
|
|
82
|
-
Your IT support team is connected <span className="text-ods-text-secondary">({elapsed})</span>
|
|
83
|
-
</p>
|
|
84
|
-
<p className="w-full truncate text-ods-text-secondary text-h4">You can end this session at any time</p>
|
|
85
|
-
</div>
|
|
86
|
-
{showRecordingTag && <RemoteSessionRecordingTag />}
|
|
87
|
-
<div className="flex w-full items-stretch gap-[var(--spacing-system-m)]">
|
|
88
|
-
<Button
|
|
89
|
-
variant="outline"
|
|
90
|
-
fullWidth
|
|
91
|
-
leftIcon={<MessageOffIcon size={24} color="currentColor" />}
|
|
92
|
-
onClick={onCloseChat}
|
|
93
|
-
>
|
|
94
|
-
Close Chat
|
|
95
|
-
</Button>
|
|
96
|
-
<RemoteSessionEndButton onEndSession={onEndSession} />
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
<div
|
|
100
|
-
ref={listRef}
|
|
101
|
-
className="flex min-h-0 flex-1 flex-col overflow-y-auto bg-ods-card p-[var(--spacing-system-m)]"
|
|
102
|
-
>
|
|
103
|
-
{/* mt-auto (not justify-end) keeps the top of an overflowing list scrollable. */}
|
|
104
|
-
<div className="mt-auto flex flex-col gap-[var(--spacing-system-s)]">
|
|
105
|
-
{messages.map(message =>
|
|
106
|
-
message.author === 'system' ? (
|
|
107
|
-
<p key={message.id} className="w-full text-center text-ods-text-secondary text-h6">
|
|
108
|
-
{message.text}
|
|
109
|
-
</p>
|
|
110
|
-
) : (
|
|
111
|
-
<RemoteSessionChatMessageRow key={message.id} message={message} party={party} />
|
|
112
|
-
),
|
|
113
|
-
)}
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
{error && <p className="bg-ods-card px-[var(--spacing-system-m)] text-ods-error text-h6">{error}</p>}
|
|
117
|
-
<form
|
|
118
|
-
className="flex items-center gap-[var(--spacing-system-xs)] bg-ods-card px-[var(--spacing-system-m)] pb-[var(--spacing-system-m)]"
|
|
119
|
-
onSubmit={event => {
|
|
120
|
-
event.preventDefault();
|
|
121
|
-
handleSend();
|
|
122
|
-
}}
|
|
123
|
-
>
|
|
124
|
-
<input
|
|
125
|
-
value={draft}
|
|
126
|
-
onChange={event => onDraftChange(event.target.value)}
|
|
127
|
-
disabled={!ready}
|
|
128
|
-
placeholder={ready ? 'Enter your Message...' : 'Loading chat...'}
|
|
129
|
-
aria-label="Message"
|
|
130
|
-
className="h-12 min-w-0 flex-1 rounded-md border border-ods-border bg-ods-bg px-[var(--spacing-system-s)] text-ods-text-primary outline-none text-h6 placeholder:text-ods-text-secondary"
|
|
131
|
-
/>
|
|
132
|
-
<button
|
|
133
|
-
type="submit"
|
|
134
|
-
aria-label="Send message"
|
|
135
|
-
disabled={!ready}
|
|
136
|
-
className="flex size-12 shrink-0 items-center justify-center"
|
|
137
|
-
>
|
|
138
|
-
<Send03Icon size={24} color="currentColor" className="text-ods-text-primary" />
|
|
139
|
-
</button>
|
|
140
|
-
</form>
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function RemoteSessionChatMessageRow({
|
|
146
|
-
message,
|
|
147
|
-
party,
|
|
148
|
-
}: {
|
|
149
|
-
message: RemoteSessionChatMessage;
|
|
150
|
-
party: RemoteSessionParty;
|
|
151
|
-
}) {
|
|
152
|
-
const isTechnician = message.author === 'technician';
|
|
153
|
-
const technician = message.name ?? party.technicianName ?? 'Technician';
|
|
154
|
-
return (
|
|
155
|
-
<div className="flex w-full flex-col">
|
|
156
|
-
<div className="flex w-full items-center gap-[var(--spacing-system-xs)]">
|
|
157
|
-
{isTechnician && (
|
|
158
|
-
<SquareAvatar
|
|
159
|
-
variant="round"
|
|
160
|
-
sizePx={16}
|
|
161
|
-
src={party.technicianAvatarUrl ?? undefined}
|
|
162
|
-
fallback={technician}
|
|
163
|
-
/>
|
|
164
|
-
)}
|
|
165
|
-
<span
|
|
166
|
-
className={cn(
|
|
167
|
-
'min-w-0 flex-1 truncate text-code',
|
|
168
|
-
isTechnician ? 'text-ods-accent' : 'text-ods-text-secondary',
|
|
169
|
-
)}
|
|
170
|
-
>
|
|
171
|
-
{isTechnician ? technician : 'User'}
|
|
172
|
-
</span>
|
|
173
|
-
<span className="shrink-0 text-ods-text-secondary text-h6">{formatTime(message.at)}</span>
|
|
174
|
-
</div>
|
|
175
|
-
<p className="w-full break-words text-ods-text-primary text-h6">{message.text}</p>
|
|
176
|
-
</div>
|
|
177
|
-
);
|
|
178
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import type { ReactNode } from 'react';
|
|
4
|
-
import { Button } from '../../ui/button/button';
|
|
5
|
-
import type { RemoteSessionDialogVariant } from './types';
|
|
6
|
-
|
|
7
|
-
export interface RemoteSessionDialogProps {
|
|
8
|
-
variant: RemoteSessionDialogVariant;
|
|
9
|
-
/** Named in the "ended" and "connection lost" notices. */
|
|
10
|
-
organizationName: string;
|
|
11
|
-
/** End confirm: the end call is in flight. */
|
|
12
|
-
ending?: boolean;
|
|
13
|
-
/** End confirm: the user confirmed. */
|
|
14
|
-
onEndSession?: () => void;
|
|
15
|
-
/** Cancel (end confirm) or Close (the notices). */
|
|
16
|
-
onClose: () => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const NOTICE_COPY: Record<Exclude<RemoteSessionDialogVariant, 'end-confirm'>, { title: string; suffix: string }> = {
|
|
20
|
-
'session-ended': { title: 'Remote session ended', suffix: 'has disconnected from your device.' },
|
|
21
|
-
'connection-lost': { title: 'Connection lost', suffix: 'lost the connection to your device.' },
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/** The session's terminal dialogs: the end confirm and the "it is over" notices. */
|
|
25
|
-
export function RemoteSessionDialog({
|
|
26
|
-
variant,
|
|
27
|
-
organizationName,
|
|
28
|
-
ending = false,
|
|
29
|
-
onEndSession,
|
|
30
|
-
onClose,
|
|
31
|
-
}: RemoteSessionDialogProps) {
|
|
32
|
-
if (variant === 'end-confirm') {
|
|
33
|
-
return (
|
|
34
|
-
<DialogCard>
|
|
35
|
-
<div className="flex w-full flex-col gap-[var(--spacing-system-xs)]">
|
|
36
|
-
<h1 className="w-full text-ods-text-primary text-h2">End remote session?</h1>
|
|
37
|
-
<p className="w-full text-ods-text-primary text-h4">
|
|
38
|
-
The technician will lose access to your device immediately.
|
|
39
|
-
</p>
|
|
40
|
-
</div>
|
|
41
|
-
<div className="flex w-full items-stretch gap-[var(--spacing-system-l)]">
|
|
42
|
-
<Button variant="outline" fullWidth disabled={ending} onClick={onClose}>
|
|
43
|
-
Cancel
|
|
44
|
-
</Button>
|
|
45
|
-
<Button variant="destructive" fullWidth loading={ending} onClick={onEndSession}>
|
|
46
|
-
End Session
|
|
47
|
-
</Button>
|
|
48
|
-
</div>
|
|
49
|
-
</DialogCard>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const copy = NOTICE_COPY[variant];
|
|
54
|
-
return (
|
|
55
|
-
<DialogCard>
|
|
56
|
-
<div className="flex w-full flex-col gap-[var(--spacing-system-xs)]">
|
|
57
|
-
<h1 className="w-full text-ods-text-primary text-h2">{copy.title}</h1>
|
|
58
|
-
<p className="w-full text-ods-text-primary text-h4">
|
|
59
|
-
<span className="text-ods-accent">{organizationName}</span> {copy.suffix}
|
|
60
|
-
</p>
|
|
61
|
-
</div>
|
|
62
|
-
<div className="flex w-full justify-end">
|
|
63
|
-
<Button variant="outline" className="w-1/2" onClick={onClose}>
|
|
64
|
-
Close
|
|
65
|
-
</Button>
|
|
66
|
-
</div>
|
|
67
|
-
</DialogCard>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function DialogCard({ children }: { children: ReactNode }) {
|
|
72
|
-
return (
|
|
73
|
-
<div className="flex w-full flex-col gap-[var(--spacing-system-l)] rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-xl)]">
|
|
74
|
-
{children}
|
|
75
|
-
</div>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { cn } from '../../../utils/cn';
|
|
4
|
-
import { MonitorOffIcon } from '../../icons-v2-generated';
|
|
5
|
-
|
|
6
|
-
/*
|
|
7
|
-
* The always-visible "your screen is being controlled" indicator: a border
|
|
8
|
-
* around the screen and an End Session pill hanging from its top edge.
|
|
9
|
-
* The colour is the ODS warning token, not the accent: the accent follows
|
|
10
|
-
* tenant branding, and this indicator must stay a recognisable fixed yellow.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/** The warning-yellow frame drawn around a whole screen; it never takes pointer events. */
|
|
14
|
-
export function RemoteSessionBorder({ className }: { className?: string }) {
|
|
15
|
-
return <div className={cn('pointer-events-none size-full border-4 border-ods-warning', className)} />;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* The End Session pill. It sits flush against the top edge of the screen, so
|
|
20
|
-
* only its bottom corners are rounded and it reads as one piece with the border.
|
|
21
|
-
*/
|
|
22
|
-
export function RemoteSessionPill({ onEndSession }: { onEndSession: () => void }) {
|
|
23
|
-
return (
|
|
24
|
-
<button
|
|
25
|
-
type="button"
|
|
26
|
-
className="flex items-center gap-[var(--spacing-system-xsf)] rounded-b-md bg-ods-warning px-[var(--spacing-system-m)] py-[var(--spacing-system-xs)] text-ods-text-on-accent"
|
|
27
|
-
onClick={onEndSession}
|
|
28
|
-
>
|
|
29
|
-
<MonitorOffIcon size={24} color="currentColor" />
|
|
30
|
-
<span className="whitespace-nowrap text-h3">End Session</span>
|
|
31
|
-
</button>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { type CSSProperties, type ReactNode, useEffect, useState } from 'react';
|
|
4
|
-
import { cn } from '../../../utils/cn';
|
|
5
|
-
import { DraggerIcon, RecordingIcon } from '../../icons-v2-generated';
|
|
6
|
-
import { OpenFrameLogo } from '../../icons/openframe-logo';
|
|
7
|
-
import { SquareAvatar } from '../../ui/square-avatar';
|
|
8
|
-
import type { RemoteSessionDragHandlers } from './types';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* The remote session surfaces are fixed-size desktop windows (440 / 600 px
|
|
12
|
-
* wide), so the responsive ODS scale would step their typography and spacing
|
|
13
|
-
* down to the mobile values below the 800 px breakpoint. The scope pins the
|
|
14
|
-
* scale to its desktop values; everything inside keeps the regular utilities.
|
|
15
|
-
*/
|
|
16
|
-
const DESKTOP_SCALE = {
|
|
17
|
-
'--spacing-system-xs': '0.5rem',
|
|
18
|
-
'--spacing-system-s': '0.75rem',
|
|
19
|
-
'--spacing-system-m': '1rem',
|
|
20
|
-
'--spacing-system-l': '1.5rem',
|
|
21
|
-
'--spacing-system-xl': '2.5rem',
|
|
22
|
-
'--font-size-h1-title': '3.5rem',
|
|
23
|
-
'--font-size-h2-sub-title': '2rem',
|
|
24
|
-
'--font-size-h3-body': '1.125rem',
|
|
25
|
-
'--font-size-h4-body': '1.125rem',
|
|
26
|
-
'--font-size-h5-caption': '0.875rem',
|
|
27
|
-
'--font-size-h6-caption': '0.875rem',
|
|
28
|
-
'--font-line-space-h1-main-title': '4rem',
|
|
29
|
-
'--font-line-space-h2-sub-title': '2.5rem',
|
|
30
|
-
'--font-line-space-h3-body': '1.5rem',
|
|
31
|
-
'--font-line-space-h4-body': '1.5rem',
|
|
32
|
-
'--font-line-space-h5-caption': '1.25rem',
|
|
33
|
-
'--font-line-space-h6-caption': '1.25rem',
|
|
34
|
-
} as CSSProperties;
|
|
35
|
-
|
|
36
|
-
export interface RemoteSessionScopeProps {
|
|
37
|
-
children: ReactNode;
|
|
38
|
-
className?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Root of every remote session surface: the desktop type and spacing scale,
|
|
43
|
-
* and the OpenFrame brand whatever the host app's tenant branding is - the
|
|
44
|
-
* "you are being helped" surfaces are OpenFrame-branded by design.
|
|
45
|
-
*/
|
|
46
|
-
export function RemoteSessionScope({ children, className }: RemoteSessionScopeProps) {
|
|
47
|
-
return (
|
|
48
|
-
<div data-app-type="openframe" style={DESKTOP_SCALE} className={className}>
|
|
49
|
-
{children}
|
|
50
|
-
</div>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface RemoteSessionOrgLogoProps {
|
|
55
|
-
name: string;
|
|
56
|
-
logoUrl?: string | null;
|
|
57
|
-
/** Width and height in px. */
|
|
58
|
-
sizePx: number;
|
|
59
|
-
className?: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Organization logo tile; the OpenFrame mark when there is no logo. */
|
|
63
|
-
export function RemoteSessionOrgLogo({ name, logoUrl, sizePx, className }: RemoteSessionOrgLogoProps) {
|
|
64
|
-
if (logoUrl) {
|
|
65
|
-
return (
|
|
66
|
-
<SquareAvatar
|
|
67
|
-
variant="square"
|
|
68
|
-
fit="contain"
|
|
69
|
-
sizePx={sizePx}
|
|
70
|
-
src={logoUrl}
|
|
71
|
-
alt={name}
|
|
72
|
-
fallback={name}
|
|
73
|
-
className={cn('shrink-0 rounded-md border border-ods-border bg-ods-card', className)}
|
|
74
|
-
/>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
return (
|
|
78
|
-
<div
|
|
79
|
-
role="img"
|
|
80
|
-
aria-label={name}
|
|
81
|
-
style={{ width: sizePx, height: sizePx }}
|
|
82
|
-
className={cn(
|
|
83
|
-
'flex shrink-0 items-center justify-center rounded-md border border-ods-border bg-ods-card',
|
|
84
|
-
className,
|
|
85
|
-
)}
|
|
86
|
-
>
|
|
87
|
-
<OpenFrameLogo className="size-1/2" />
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** The green SESSION RECORDING tag. */
|
|
93
|
-
export function RemoteSessionRecordingTag() {
|
|
94
|
-
return (
|
|
95
|
-
<div className="flex h-8 items-center gap-[var(--spacing-system-xs)] self-start rounded-md bg-ods-success-secondary p-[var(--spacing-system-xsf)]">
|
|
96
|
-
<RecordingIcon size={16} color="currentColor" className="text-ods-success" />
|
|
97
|
-
<span className="text-ods-success text-h5">Session Recording</span>
|
|
98
|
-
</div>
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Grab cursor on the surfaces the window is moved by. */
|
|
103
|
-
export const REMOTE_SESSION_DRAG_HANDLE_CLASS = 'cursor-grab active:cursor-grabbing';
|
|
104
|
-
|
|
105
|
-
/** The dragger glyph in the top-right corner of a block header: the visible handle for moving it. */
|
|
106
|
-
export function RemoteSessionDragger({ dragHandlers }: { dragHandlers?: RemoteSessionDragHandlers }) {
|
|
107
|
-
return (
|
|
108
|
-
<span
|
|
109
|
-
aria-hidden
|
|
110
|
-
className={cn(
|
|
111
|
-
'absolute right-[var(--spacing-system-xsf)] top-[var(--spacing-system-xsf)] text-ods-text-secondary',
|
|
112
|
-
dragHandlers && REMOTE_SESSION_DRAG_HANDLE_CLASS,
|
|
113
|
-
)}
|
|
114
|
-
{...dragHandlers}
|
|
115
|
-
>
|
|
116
|
-
<DraggerIcon size={24} color="currentColor" />
|
|
117
|
-
</span>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** `m:ss` for an elapsed duration; `h:mm:ss` from the first hour on. */
|
|
122
|
-
export function formatRemoteSessionElapsed(elapsedMs: number): string {
|
|
123
|
-
const total = Math.max(0, Math.floor(elapsedMs / 1000));
|
|
124
|
-
const hours = Math.floor(total / 3600);
|
|
125
|
-
const minutes = Math.floor((total % 3600) / 60);
|
|
126
|
-
const seconds = String(total % 60).padStart(2, '0');
|
|
127
|
-
return hours > 0 ? `${hours}:${String(minutes).padStart(2, '0')}:${seconds}` : `${minutes}:${seconds}`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** The session's elapsed time since `startedAt` (ISO), ticking once a second. */
|
|
131
|
-
export function useRemoteSessionTimer(startedAt: string): string {
|
|
132
|
-
const [nowMs, setNowMs] = useState(() => Date.now());
|
|
133
|
-
useEffect(() => {
|
|
134
|
-
const timer = setInterval(() => setNowMs(Date.now()), 1_000);
|
|
135
|
-
return () => clearInterval(timer);
|
|
136
|
-
}, []);
|
|
137
|
-
return formatRemoteSessionElapsed(nowMs - Date.parse(startedAt));
|
|
138
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { HTMLAttributes } from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Who is on the other end of a remote session, as the end user's surfaces show
|
|
5
|
-
* it: the organization always, the technician when the backend names one.
|
|
6
|
-
*/
|
|
7
|
-
export interface RemoteSessionParty {
|
|
8
|
-
organizationName: string;
|
|
9
|
-
organizationLogoUrl?: string | null;
|
|
10
|
-
/** Shown under the organization name; offered as a link only when it is https. */
|
|
11
|
-
organizationSiteUrl?: string | null;
|
|
12
|
-
/** Present for the technician variant of the session block; the organization variant otherwise. */
|
|
13
|
-
technicianName?: string | null;
|
|
14
|
-
technicianAvatarUrl?: string | null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** The end user's answer to a remote access request. */
|
|
18
|
-
export type RemoteAccessDecision = 'APPROVED' | 'DENIED';
|
|
19
|
-
|
|
20
|
-
export type RemoteSessionChatAuthor = 'technician' | 'user' | 'system';
|
|
21
|
-
|
|
22
|
-
export interface RemoteSessionChatMessage {
|
|
23
|
-
id: string;
|
|
24
|
-
author: RemoteSessionChatAuthor;
|
|
25
|
-
/** The technician's display name on their rows; the party's technician otherwise. */
|
|
26
|
-
name?: string | null;
|
|
27
|
-
text: string;
|
|
28
|
-
at: Date;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** The terminal dialogs of a session: the end confirm and the two "it is over" notices. */
|
|
32
|
-
export type RemoteSessionDialogVariant = 'end-confirm' | 'session-ended' | 'connection-lost';
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Pointer handlers a host attaches to the surfaces the user moves the window
|
|
36
|
-
* by (the desktop chat starts a native window drag from them).
|
|
37
|
-
*/
|
|
38
|
-
export type RemoteSessionDragHandlers = Pick<
|
|
39
|
-
HTMLAttributes<HTMLElement>,
|
|
40
|
-
'onPointerDown' | 'onPointerMove' | 'onPointerUp'
|
|
41
|
-
>;
|