@burdenoff/microfe-eventfullymanaged 2026.728.1 → 2026.731.1
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/eventfullymanaged/EventfullyManagedRoutes.d.ts.map +1 -1
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js +60 -76
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
- package/dist/eventfullymanaged/api/eventApi.d.ts +245 -1
- package/dist/eventfullymanaged/api/eventApi.d.ts.map +1 -1
- package/dist/eventfullymanaged/api/eventApi.js +482 -264
- package/dist/eventfullymanaged/api/eventApi.js.map +1 -1
- package/dist/eventfullymanaged/hooks/useEvmSubscription.d.ts +18 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.d.ts.map +1 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.js +110 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.js.map +1 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.d.ts +47 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.d.ts.map +1 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.js +209 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.js.map +1 -0
- package/dist/eventfullymanaged/pages/events/EventDetailPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js +79 -71
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.d.ts +2 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.d.ts.map +1 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.js +353 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.js.map +1 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.d.ts +2 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.d.ts.map +1 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.js +336 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.js.map +1 -0
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.js +211 -177
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js +204 -174
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js.map +1 -1
- package/dist/eventfullymanaged/utils/shellBridge.d.ts +46 -0
- package/dist/eventfullymanaged/utils/shellBridge.d.ts.map +1 -0
- package/dist/eventfullymanaged/utils/shellBridge.js +19 -0
- package/dist/eventfullymanaged/utils/shellBridge.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VirtualSessionPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/events/VirtualSessionPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect -- mirrors server-confirmed mute/video state into local toggle state, same established pattern as the fleet's *FormPage.tsx entity-hydration effects */\n/**\n * VirtualSessionPage (BOFF-4318)\n *\n * Real join/host flow for an event's virtual/hybrid session, backed by\n * `wspace-eventfullymanaged-svc`'s bounded browser-native WebRTC core\n * (`VirtualSessionService`). MEETING mode is a full mesh (cap 8);\n * WEBINAR mode is a host/presenter-broadcast star (cap 30) — attendees\n * only peer with the host/presenters, never each other. See\n * `useVirtualCall` for the exact topology and signaling logic.\n *\n * Large-audience broadcast (hundreds+ viewers) needs a real SFU/media\n * server and is explicit deferred follow-up — not built here.\n */\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, Mic, MicOff, PhoneOff, Radio, ScreenShare, Users, Video, VideoOff } from 'lucide-react';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useEndVirtualSessionMutation,\n useEventQuery,\n useHostVirtualSessionMutation,\n useJoinVirtualSessionMutation,\n useLeaveVirtualSessionMutation,\n usePromoteVirtualPresenterMutation,\n useSendVirtualSignalMutation,\n useSetVirtualMuteMutation,\n useSetVirtualVideoMutation,\n useVirtualSessionQuery,\n VIRTUAL_PARTICIPANTS_CHANGED_SUBSCRIPTION_SDL,\n VIRTUAL_SIGNAL_SUBSCRIPTION_SDL,\n type VirtualParticipantGql,\n} from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { useEvmSubscription } from '../../hooks/useEvmSubscription';\nimport { type IncomingVirtualSignal, type VirtualCallRole, useVirtualCall } from '../../hooks/useVirtualCall';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nfunction initials(id: string): string {\n return id.slice(0, 2).toUpperCase();\n}\n\nfunction connectionDotClass(state: RTCPeerConnectionState | undefined): string {\n if (state === 'connected') return 'bg-status-success';\n if (state === 'connecting' || state === 'new') return 'bg-status-warning';\n if (state === 'failed' || state === 'disconnected' || state === 'closed') return 'bg-status-danger';\n return 'bg-text-muted';\n}\n\ninterface VideoTileProps {\n actorId: string;\n role: VirtualCallRole;\n isLocal: boolean;\n stream: MediaStream | null;\n /** Muted/videoEnabled reflect the persisted participant state (server-confirmed for remotes, local toggle for self). */\n muted: boolean;\n videoEnabled: boolean;\n connectionState?: RTCPeerConnectionState;\n}\n\nfunction VideoTile({ actorId, role, isLocal, stream, muted, videoEnabled, connectionState }: VideoTileProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n useEffect(() => {\n if (videoRef.current) videoRef.current.srcObject = stream;\n }, [stream]);\n\n const showVideo = Boolean(stream) && videoEnabled;\n\n return (\n <div className=\"relative aspect-video overflow-hidden rounded-card border border-border-seam bg-bg-sunken\">\n {showVideo ? (\n <video\n ref={videoRef}\n autoPlay\n playsInline\n muted={isLocal}\n className=\"h-full w-full object-cover\"\n style={isLocal ? { transform: 'scaleX(-1)' } : undefined}\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center\">\n <span className=\"flex h-12 w-12 items-center justify-center rounded-full bg-accent-primary/10 text-sm font-semibold text-accent-primary\">\n {initials(actorId)}\n </span>\n </div>\n )}\n <div className=\"absolute inset-x-0 bottom-0 flex items-center justify-between gap-2 bg-gradient-to-t from-black/60 to-transparent p-2\">\n <span className=\"truncate text-xs font-medium text-white\">\n {isLocal ? 'You' : `Participant ${actorId.slice(0, 6)}`}\n {role === 'HOST' ? ' · Host' : role === 'PRESENTER' ? ' · Presenter' : ''}\n </span>\n <span className=\"flex items-center gap-1.5\">\n {muted ? <MicOff className=\"h-3.5 w-3.5 text-white\" /> : null}\n {!isLocal ? (\n <span\n className={`h-2 w-2 rounded-full ${connectionDotClass(connectionState)}`}\n title={connectionState ?? 'connecting'}\n />\n ) : null}\n </span>\n </div>\n </div>\n );\n}\n\nexport default function VirtualSessionPage() {\n const { id: eventId = '' } = useParams<{ id: string }>();\n const { navigate, currentUser } = useEventfullyManaged();\n const selfActorId = currentUser?.id ?? '';\n\n const { data: eventData, loading: eventLoading } = useEventQuery(eventId);\n const sessionQuery = useVirtualSessionQuery({ eventId });\n const session = sessionQuery.data?.virtualSession ?? null;\n\n const [mode, setMode] = useState<'MEETING' | 'WEBINAR'>('MEETING');\n const [hostSession, { loading: hosting }] = useHostVirtualSessionMutation();\n const [joinSession, { loading: joining }] = useJoinVirtualSessionMutation();\n const [leaveSession, { loading: leaving }] = useLeaveVirtualSessionMutation();\n const [endSession, { loading: ending }] = useEndVirtualSessionMutation();\n const [setMuteMutation] = useSetVirtualMuteMutation();\n const [setVideoMutation] = useSetVirtualVideoMutation();\n const [promotePresenter] = usePromoteVirtualPresenterMutation();\n const [sendSignalMutation] = useSendVirtualSignalMutation();\n\n const selfParticipant = useMemo(\n () => session?.participants.find((p) => p.actorId === selfActorId && !p.leftAt) ?? null,\n [session, selfActorId]\n );\n const activeParticipants = useMemo(() => (session?.participants ?? []).filter((p) => !p.leftAt), [session]);\n\n const [localMuted, setLocalMuted] = useState(false);\n const [localVideoOff, setLocalVideoOff] = useState(false);\n useEffect(() => {\n if (selfParticipant) {\n setLocalMuted(selfParticipant.muted);\n setLocalVideoOff(!selfParticipant.videoEnabled);\n }\n }, [selfParticipant?.muted, selfParticipant?.videoEnabled]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const sendSignal = useCallback(\n async (args: { toActorId: string; signalType: 'OFFER' | 'ANSWER' | 'ICE_CANDIDATE'; payload: unknown }) => {\n if (!session) return;\n await sendSignalMutation({\n variables: {\n sessionId: session.id,\n toActorId: args.toActorId,\n signalType: args.signalType,\n payload: args.payload,\n },\n });\n },\n [session, sendSignalMutation]\n );\n\n const call = useVirtualCall({\n sessionId: selfParticipant ? session?.id : null,\n selfActorId,\n mode: session?.mode ?? 'MEETING',\n selfRole: selfParticipant?.role ?? null,\n participants: activeParticipants.map((p) => ({ actorId: p.actorId, role: p.role })),\n sendSignal,\n muted: localMuted,\n videoEnabled: !localVideoOff,\n });\n\n useEvmSubscription<{ eventfullyManagedVirtualSignal: IncomingVirtualSignal }>({\n query: VIRTUAL_SIGNAL_SUBSCRIPTION_SDL,\n variables: { sessionId: session?.id },\n operationName: 'EventfullymanagedVirtualSignal',\n enabled: Boolean(selfParticipant && session),\n onData: (data) => {\n const signal = data.eventfullyManagedVirtualSignal;\n if (signal) call.handleIncomingSignal(signal);\n },\n // No real polling substitute exists for signaling — a missed signal is\n // simply a missed negotiation step. The fallback here just retries the\n // subscription's own reconnect path via re-render, kept as a no-op.\n pollFn: () => {},\n pollIntervalMs: 15_000,\n });\n\n useEvmSubscription<{ eventfullyManagedVirtualParticipantsChanged: unknown }>({\n query: VIRTUAL_PARTICIPANTS_CHANGED_SUBSCRIPTION_SDL,\n variables: { sessionId: session?.id },\n operationName: 'EventfullymanagedVirtualParticipantsChanged',\n enabled: Boolean(session),\n onData: () => {\n void sessionQuery.refetch();\n },\n pollFn: () => {\n void sessionQuery.refetch();\n },\n pollIntervalMs: 5_000,\n });\n\n const onHost = async (chosenMode: 'MEETING' | 'WEBINAR') => {\n try {\n const response = await hostSession({ variables: { eventId, mode: chosenMode } });\n if (!mutationSucceeded(response, response.data?.hostVirtualSession?.id, 'Could not start session')) {\n return;\n }\n notify.success('Session started', chosenMode === 'WEBINAR' ? 'Hosting as a webinar.' : 'Hosting a meeting.');\n await sessionQuery.refetch();\n } catch (error) {\n notify.error('Could not start session', describeError(error));\n }\n };\n\n const onJoin = async () => {\n try {\n const response = await joinSession({ variables: { eventId } });\n if (!mutationSucceeded(response, response.data?.joinVirtualSession?.id, 'Could not join session')) {\n return;\n }\n await sessionQuery.refetch();\n } catch (error) {\n notify.error('Could not join session', describeError(error));\n }\n };\n\n const onLeave = async () => {\n if (!session) return;\n try {\n await leaveSession({ variables: { sessionId: session.id } });\n notify.success('Left session', 'You are no longer in the virtual session.');\n await sessionQuery.refetch();\n } catch (error) {\n notify.error('Could not leave session', describeError(error));\n }\n };\n\n const onEnd = async () => {\n if (!session) return;\n try {\n await endSession({ variables: { sessionId: session.id } });\n notify.success('Session ended', 'The virtual session has been ended for everyone.');\n await sessionQuery.refetch();\n } catch (error) {\n notify.error('Could not end session', describeError(error));\n }\n };\n\n const toggleMute = async () => {\n if (!session) return;\n const next = !localMuted;\n setLocalMuted(next);\n try {\n await setMuteMutation({ variables: { sessionId: session.id, muted: next } });\n } catch (error) {\n setLocalMuted(!next);\n notify.error('Could not update mute state', describeError(error));\n }\n };\n\n const toggleVideo = async () => {\n if (!session) return;\n const next = !localVideoOff;\n setLocalVideoOff(next);\n try {\n await setVideoMutation({ variables: { sessionId: session.id, videoEnabled: !next } });\n } catch (error) {\n setLocalVideoOff(!next);\n notify.error('Could not update camera state', describeError(error));\n }\n };\n\n const onPromote = async (participant: VirtualParticipantGql) => {\n if (!session) return;\n try {\n await promotePresenter({ variables: { sessionId: session.id, actorId: participant.actorId } });\n notify.success('Promoted to presenter', `Participant ${participant.actorId.slice(0, 6)} can now broadcast.`);\n await sessionQuery.refetch();\n } catch (error) {\n notify.error('Could not promote participant', describeError(error));\n }\n };\n\n const isHost = selfParticipant?.role === 'HOST';\n const busy = hosting || joining || leaving || ending;\n\n if (eventLoading || sessionQuery.loading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-8 w-64\" />\n <Skeleton className=\"h-64 w-full\" />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate(`/events/${eventId}`)}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Event\n </Button>\n\n <PagePurpose>\n A virtual session is a real, browser-native WebRTC room for this event — no external meeting link. Hosts start a\n Meeting (small mesh, up to 8 people all connected directly) or a Webinar (up to 30 attendees who see and hear\n only the host/presenters). Signaling is relayed through EventfullyManaged; media never touches our servers.\n </PagePurpose>\n\n <EmphasisPanel className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Virtual Session</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">{eventData?.event?.name ?? 'Event'}</p>\n </div>\n {session ? (\n <Badge variant={session.status === 'ACTIVE' ? 'success' : 'secondary'} className=\"uppercase\">\n {session.mode} · {session.status}\n </Badge>\n ) : null}\n </EmphasisPanel>\n\n {!session || session.status !== 'ACTIVE' ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-8\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No live virtual session\"\n description=\"Start a session to host attendees over WebRTC, or join once someone else has started one.\"\n className=\"py-6\"\n />\n <div className=\"mt-4 flex flex-wrap items-center justify-center gap-2\">\n <Select value={mode} onValueChange={(value) => setMode(value as 'MEETING' | 'WEBINAR')}>\n <SelectTrigger aria-label=\"Session mode\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"MEETING\">Meeting (up to 8)</SelectItem>\n <SelectItem value=\"WEBINAR\">Webinar (up to 30)</SelectItem>\n </SelectContent>\n </Select>\n <Button type=\"button\" onClick={() => onHost(mode)} disabled={busy}>\n <Radio className=\"h-4 w-4\" />\n {hosting ? 'Starting…' : 'Start session'}\n </Button>\n </div>\n </GlassCard>\n ) : !selfParticipant ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-8\">\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"A session is live\"\n description={`${session.participantCount} of ${session.maxParticipants} seats in use. Join to turn on your camera and mic.`}\n className=\"py-6\"\n />\n <div className=\"mt-4 flex justify-center\">\n <Button type=\"button\" onClick={onJoin} disabled={busy}>\n <Video className=\"h-4 w-4\" />\n {joining ? 'Joining…' : 'Join session'}\n </Button>\n </div>\n </GlassCard>\n ) : (\n <>\n {call.mediaError ? (\n <GlassCard\n treatment=\"plain\"\n className=\"border-status-danger/40 bg-status-danger/5 p-4 text-sm text-status-danger\"\n >\n Camera/microphone unavailable: {call.mediaError}. You are still connected to the session and can see other\n participants, but others will not see or hear you.\n </GlassCard>\n ) : null}\n\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n <VideoTile\n actorId={selfActorId}\n role={selfParticipant.role}\n isLocal\n stream={call.localStream}\n muted={localMuted}\n videoEnabled={!localVideoOff}\n />\n {activeParticipants\n .filter((p) => p.actorId !== selfActorId)\n .map((participant) => (\n <VideoTile\n key={participant.actorId}\n actorId={participant.actorId}\n role={participant.role}\n isLocal={false}\n stream={call.remoteStreams.get(participant.actorId) ?? null}\n muted={participant.muted}\n videoEnabled={participant.videoEnabled}\n connectionState={call.connectionStates.get(participant.actorId)}\n />\n ))}\n </div>\n\n <GlassCard\n treatment=\"plain\"\n className=\"flex flex-wrap items-center justify-center gap-2 border-border-seam p-4\"\n >\n <Button type=\"button\" variant={localMuted ? 'outline' : 'secondary'} onClick={toggleMute}>\n {localMuted ? <MicOff className=\"h-4 w-4\" /> : <Mic className=\"h-4 w-4\" />}\n {localMuted ? 'Unmute' : 'Mute'}\n </Button>\n <Button type=\"button\" variant={localVideoOff ? 'outline' : 'secondary'} onClick={toggleVideo}>\n {localVideoOff ? <VideoOff className=\"h-4 w-4\" /> : <Video className=\"h-4 w-4\" />}\n {localVideoOff ? 'Start Video' : 'Stop Video'}\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={onLeave} disabled={busy}>\n <PhoneOff className=\"h-4 w-4\" />\n {leaving ? 'Leaving…' : 'Leave'}\n </Button>\n {isHost ? (\n <Button type=\"button\" variant=\"destructive\" onClick={onEnd} disabled={busy}>\n <ScreenShare className=\"h-4 w-4\" />\n {ending ? 'Ending…' : 'End for everyone'}\n </Button>\n ) : null}\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-4\">\n <h2 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Users className=\"h-4 w-4 text-accent-primary\" />\n Participants ({activeParticipants.length}/{session.maxParticipants})\n </h2>\n <div className=\"mt-3 space-y-2\">\n {activeParticipants.map((participant) => (\n <div key={participant.id} className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <span\n className={`h-2 w-2 rounded-full ${\n participant.actorId === selfActorId\n ? 'bg-status-success'\n : connectionDotClass(call.connectionStates.get(participant.actorId))\n }`}\n />\n {participant.actorId === selfActorId ? 'You' : `Participant ${participant.actorId.slice(0, 6)}`}\n <Badge variant=\"secondary\" className=\"uppercase\">\n {participant.role}\n </Badge>\n </span>\n {isHost && session.mode === 'WEBINAR' && participant.role === 'ATTENDEE' ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => onPromote(participant)}>\n Promote to presenter\n </Button>\n ) : null}\n </div>\n ))}\n </div>\n </GlassCard>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntegrationsPage.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/modules/IntegrationsPage.tsx"],"names":[],"mappings":"AAqQA,MAAM,CAAC,OAAO,UAAU,gBAAgB,4CA8GvC"}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { notify as e } from "../../lib/toast.js";
|
|
2
|
+
import { useCreateIntegrationConnectorMutation as t, useDeleteIntegrationConnectorMutation as n, useEventsQuery as r, useIntegrationConnectorsQuery as i, useIntegrationSyncRunsQuery as a, useSyncAttendeesToConnectorMutation as o } from "../../api/eventApi.js";
|
|
3
|
+
import { ActionMenu as s } from "../../components/ActionMenu.js";
|
|
4
|
+
import { describeError as c, mutationSucceeded as l } from "../../lib/mutationResult.js";
|
|
5
|
+
import { useState as u } from "react";
|
|
6
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
7
|
+
import { CheckCircle2 as p, Plug as m, Plus as h, RefreshCw as g, XCircle as _ } from "lucide-react";
|
|
8
|
+
import { Badge as v, Button as y, EmphasisPanel as b, GlassCard as x, GradientText as S, IllustratedEmptyState as C, Input as w, Label as T, PagePurpose as E, ResponsiveDialog as D, ResponsiveDialogContent as O, ResponsiveDialogDescription as k, ResponsiveDialogFooter as A, ResponsiveDialogHeader as j, ResponsiveDialogTitle as M, Select as N, SelectContent as P, SelectItem as F, SelectTrigger as I, SelectValue as L } from "@burdenoff/fe-libs/ui";
|
|
9
|
+
//#region src/eventfullymanaged/pages/modules/IntegrationsPage.tsx
|
|
10
|
+
var R = [{
|
|
11
|
+
value: "HUBSPOT",
|
|
12
|
+
label: "HubSpot"
|
|
13
|
+
}];
|
|
14
|
+
function z(e) {
|
|
15
|
+
return e === "CONNECTED" ? {
|
|
16
|
+
variant: "success",
|
|
17
|
+
icon: p
|
|
18
|
+
} : e === "ERROR" ? {
|
|
19
|
+
variant: "warning",
|
|
20
|
+
icon: _
|
|
21
|
+
} : {
|
|
22
|
+
variant: "secondary",
|
|
23
|
+
icon: m
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function B({ open: n, onOpenChange: r }) {
|
|
27
|
+
let [i, a] = u(""), [o, s] = u("HUBSPOT"), [p, m] = u(""), [h, { loading: g }] = t();
|
|
28
|
+
return /* @__PURE__ */ d(D, {
|
|
29
|
+
open: n,
|
|
30
|
+
onOpenChange: r,
|
|
31
|
+
children: /* @__PURE__ */ f(O, {
|
|
32
|
+
className: "max-w-lg",
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ f(j, { children: [/* @__PURE__ */ d(M, { children: "New integration connector" }), /* @__PURE__ */ d(k, { children: "Connects to your CRM/marketing-automation account with a private-app access token. The token is encrypted at rest and never shown again after saving." })] }),
|
|
35
|
+
/* @__PURE__ */ f("form", {
|
|
36
|
+
id: "connector-form",
|
|
37
|
+
onSubmit: async (t) => {
|
|
38
|
+
if (t.preventDefault(), !i.trim() || !p.trim()) {
|
|
39
|
+
e.error("Missing details", "Give the connector a name and paste the private-app access token.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
let t = await h({ variables: { input: {
|
|
44
|
+
provider: o,
|
|
45
|
+
name: i.trim(),
|
|
46
|
+
accessToken: p.trim()
|
|
47
|
+
} } });
|
|
48
|
+
if (!l(t, t.data?.createIntegrationConnector?.id, "Could not create connector")) return;
|
|
49
|
+
let n = t.data?.createIntegrationConnector;
|
|
50
|
+
n?.status === "CONNECTED" ? e.success("Connector connected", `${n.name} verified live against ${o}.`) : e.info("Connector created", `${n?.name ?? "Connector"} was saved but could not verify — check the token.`), a(""), m(""), r(!1);
|
|
51
|
+
} catch (t) {
|
|
52
|
+
e.error("Could not create connector", c(t));
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
className: "space-y-4",
|
|
56
|
+
children: [
|
|
57
|
+
/* @__PURE__ */ f("label", {
|
|
58
|
+
className: "space-y-1 text-sm",
|
|
59
|
+
children: [
|
|
60
|
+
/* @__PURE__ */ d("span", {
|
|
61
|
+
className: "font-medium text-text-primary",
|
|
62
|
+
children: "Provider"
|
|
63
|
+
}),
|
|
64
|
+
/* @__PURE__ */ f(N, {
|
|
65
|
+
value: o,
|
|
66
|
+
onValueChange: s,
|
|
67
|
+
children: [/* @__PURE__ */ d(I, {
|
|
68
|
+
"aria-label": "Provider",
|
|
69
|
+
children: /* @__PURE__ */ d(L, {})
|
|
70
|
+
}), /* @__PURE__ */ d(P, { children: R.map((e) => /* @__PURE__ */ d(F, {
|
|
71
|
+
value: e.value,
|
|
72
|
+
children: e.label
|
|
73
|
+
}, e.value)) })]
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ d("p", {
|
|
76
|
+
className: "text-xs text-text-secondary",
|
|
77
|
+
children: "HubSpot is the only live connector today — more providers can be added without a schema change."
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
}),
|
|
81
|
+
/* @__PURE__ */ f("label", {
|
|
82
|
+
className: "space-y-1 text-sm",
|
|
83
|
+
children: [/* @__PURE__ */ d("span", {
|
|
84
|
+
className: "font-medium text-text-primary",
|
|
85
|
+
children: "Connector name"
|
|
86
|
+
}), /* @__PURE__ */ d(w, {
|
|
87
|
+
value: i,
|
|
88
|
+
onChange: (e) => a(e.target.value),
|
|
89
|
+
placeholder: "Marketing HubSpot",
|
|
90
|
+
required: !0
|
|
91
|
+
})]
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ f("label", {
|
|
94
|
+
className: "space-y-1 text-sm",
|
|
95
|
+
children: [
|
|
96
|
+
/* @__PURE__ */ d("span", {
|
|
97
|
+
className: "font-medium text-text-primary",
|
|
98
|
+
children: "Private-app access token"
|
|
99
|
+
}),
|
|
100
|
+
/* @__PURE__ */ d(w, {
|
|
101
|
+
type: "password",
|
|
102
|
+
value: p,
|
|
103
|
+
onChange: (e) => m(e.target.value),
|
|
104
|
+
placeholder: "pat-na1-…",
|
|
105
|
+
autoComplete: "off",
|
|
106
|
+
required: !0
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ f("p", {
|
|
109
|
+
className: "text-xs text-text-secondary",
|
|
110
|
+
children: [
|
|
111
|
+
"From your HubSpot developer sandbox: Settings → Integrations → Private Apps. Needs the",
|
|
112
|
+
" ",
|
|
113
|
+
/* @__PURE__ */ d("code", { children: "crm.objects.contacts.read/write" }),
|
|
114
|
+
" scopes."
|
|
115
|
+
]
|
|
116
|
+
})
|
|
117
|
+
]
|
|
118
|
+
})
|
|
119
|
+
]
|
|
120
|
+
}),
|
|
121
|
+
/* @__PURE__ */ f(A, { children: [/* @__PURE__ */ d(y, {
|
|
122
|
+
type: "button",
|
|
123
|
+
variant: "outline",
|
|
124
|
+
onClick: () => r(!1),
|
|
125
|
+
disabled: g,
|
|
126
|
+
children: "Cancel"
|
|
127
|
+
}), /* @__PURE__ */ d(y, {
|
|
128
|
+
type: "submit",
|
|
129
|
+
form: "connector-form",
|
|
130
|
+
disabled: g,
|
|
131
|
+
children: g ? "Connecting…" : "Create & test connection"
|
|
132
|
+
})] })
|
|
133
|
+
]
|
|
134
|
+
})
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function V({ connector: t, onDelete: n, onSync: r, eventOptions: i }) {
|
|
138
|
+
let [o, c] = u(""), [l, p] = u(!1), { variant: m, icon: h } = z(t.status), _ = a({
|
|
139
|
+
connectorId: t.id,
|
|
140
|
+
limit: 5,
|
|
141
|
+
offset: 0
|
|
142
|
+
}), b = _.data?.integrationSyncRuns ?? [];
|
|
143
|
+
return /* @__PURE__ */ f(x, {
|
|
144
|
+
treatment: "plain",
|
|
145
|
+
className: "border-border-seam p-4",
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ f("div", {
|
|
148
|
+
className: "flex items-start justify-between gap-2",
|
|
149
|
+
children: [/* @__PURE__ */ f("div", {
|
|
150
|
+
className: "min-w-0",
|
|
151
|
+
children: [
|
|
152
|
+
/* @__PURE__ */ f("div", {
|
|
153
|
+
className: "flex items-center gap-2",
|
|
154
|
+
children: [/* @__PURE__ */ d("p", {
|
|
155
|
+
className: "truncate font-medium text-text-primary",
|
|
156
|
+
children: t.name
|
|
157
|
+
}), /* @__PURE__ */ d(v, {
|
|
158
|
+
variant: "secondary",
|
|
159
|
+
className: "uppercase",
|
|
160
|
+
children: t.provider
|
|
161
|
+
})]
|
|
162
|
+
}),
|
|
163
|
+
/* @__PURE__ */ f("p", {
|
|
164
|
+
className: "mt-1 flex items-center gap-1.5 text-xs text-text-secondary",
|
|
165
|
+
children: [
|
|
166
|
+
/* @__PURE__ */ d(h, { className: "h-3.5 w-3.5" }),
|
|
167
|
+
/* @__PURE__ */ d(v, {
|
|
168
|
+
variant: m,
|
|
169
|
+
className: "uppercase",
|
|
170
|
+
children: t.status
|
|
171
|
+
}),
|
|
172
|
+
t.externalAccountName ? /* @__PURE__ */ f("span", { children: ["· ", t.externalAccountName] }) : null
|
|
173
|
+
]
|
|
174
|
+
}),
|
|
175
|
+
t.lastError ? /* @__PURE__ */ d("p", {
|
|
176
|
+
className: "mt-1 text-xs text-status-danger",
|
|
177
|
+
children: t.lastError
|
|
178
|
+
}) : null
|
|
179
|
+
]
|
|
180
|
+
}), /* @__PURE__ */ d(s, {
|
|
181
|
+
ariaLabel: `${t.name} connector actions`,
|
|
182
|
+
entityName: t.name,
|
|
183
|
+
onDelete: n
|
|
184
|
+
})]
|
|
185
|
+
}),
|
|
186
|
+
/* @__PURE__ */ f("div", {
|
|
187
|
+
className: "mt-3 flex flex-wrap items-center gap-2",
|
|
188
|
+
children: [/* @__PURE__ */ f(N, {
|
|
189
|
+
value: o,
|
|
190
|
+
onValueChange: c,
|
|
191
|
+
children: [/* @__PURE__ */ d(I, {
|
|
192
|
+
"aria-label": "Event to sync",
|
|
193
|
+
className: "w-56",
|
|
194
|
+
children: /* @__PURE__ */ d(L, { placeholder: "Select an event" })
|
|
195
|
+
}), /* @__PURE__ */ d(P, { children: i.map((e) => /* @__PURE__ */ d(F, {
|
|
196
|
+
value: e.id,
|
|
197
|
+
children: e.name
|
|
198
|
+
}, e.id)) })]
|
|
199
|
+
}), /* @__PURE__ */ f(y, {
|
|
200
|
+
type: "button",
|
|
201
|
+
size: "sm",
|
|
202
|
+
onClick: async () => {
|
|
203
|
+
if (!o) {
|
|
204
|
+
e.error("Pick an event", "Choose which event to sync attendees from.");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
p(!0);
|
|
208
|
+
try {
|
|
209
|
+
await r(o), await _.refetch();
|
|
210
|
+
} finally {
|
|
211
|
+
p(!1);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
disabled: l || t.status !== "CONNECTED",
|
|
215
|
+
children: [/* @__PURE__ */ d(g, { className: "h-4 w-4" }), l ? "Syncing…" : "Sync attendees"]
|
|
216
|
+
})]
|
|
217
|
+
}),
|
|
218
|
+
b.length > 0 ? /* @__PURE__ */ d("div", {
|
|
219
|
+
className: "mt-3 space-y-1.5 border-t border-border-seam pt-3",
|
|
220
|
+
children: b.map((e) => /* @__PURE__ */ f("div", {
|
|
221
|
+
className: "flex items-center justify-between text-xs text-text-secondary",
|
|
222
|
+
children: [/* @__PURE__ */ d("span", { children: new Date(e.startedAt).toLocaleString() }), /* @__PURE__ */ f("span", {
|
|
223
|
+
className: "flex items-center gap-2",
|
|
224
|
+
children: [/* @__PURE__ */ d(v, {
|
|
225
|
+
variant: e.status === "SUCCESS" ? "success" : e.status === "FAILED" ? "warning" : "info",
|
|
226
|
+
className: "uppercase",
|
|
227
|
+
children: e.status
|
|
228
|
+
}), /* @__PURE__ */ f("span", { children: [
|
|
229
|
+
e.recordsSynced,
|
|
230
|
+
" synced",
|
|
231
|
+
e.recordsFailed > 0 ? `, ${e.recordsFailed} failed` : ""
|
|
232
|
+
] })]
|
|
233
|
+
})]
|
|
234
|
+
}, e.id))
|
|
235
|
+
}) : null
|
|
236
|
+
]
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function H() {
|
|
240
|
+
let [t, a] = u(!1), s = i({
|
|
241
|
+
limit: 50,
|
|
242
|
+
offset: 0
|
|
243
|
+
}), { data: p } = r({
|
|
244
|
+
limit: 100,
|
|
245
|
+
offset: 0
|
|
246
|
+
}), [m] = n(), [g] = o(), _ = s.data?.integrationConnectors ?? [], v = p?.events?.items ?? [], w = async (t) => {
|
|
247
|
+
try {
|
|
248
|
+
let n = await m({ variables: { id: t.id } });
|
|
249
|
+
if (!l(n, n.data?.deleteIntegrationConnector ? t.id : void 0, "Could not remove connector")) return;
|
|
250
|
+
e.success("Connector removed", `${t.name} was disconnected.`);
|
|
251
|
+
} catch (t) {
|
|
252
|
+
e.error("Could not remove connector", c(t));
|
|
253
|
+
}
|
|
254
|
+
}, D = async (t, n) => {
|
|
255
|
+
try {
|
|
256
|
+
let r = await g({ variables: {
|
|
257
|
+
connectorId: t.id,
|
|
258
|
+
eventId: n
|
|
259
|
+
} }), i = r.data?.syncAttendeesToConnector;
|
|
260
|
+
if (!i) {
|
|
261
|
+
e.error("Sync failed", r.error?.message ?? "Please try again.");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
i.status === "SUCCESS" ? e.success("Sync complete", `${i.recordsSynced} attendee${i.recordsSynced === 1 ? "" : "s"} synced to ${t.name}.`) : e.error("Sync failed", i.errorMessage ?? "The provider rejected the sync.");
|
|
265
|
+
} catch (t) {
|
|
266
|
+
e.error("Could not sync attendees", c(t));
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
return /* @__PURE__ */ f("div", {
|
|
270
|
+
className: "space-y-6 p-6",
|
|
271
|
+
children: [
|
|
272
|
+
/* @__PURE__ */ d(E, { children: "Integration connectors sync this workspace's real attendee and lead data to your CRM or marketing-automation platform. Register a connector with a private-app access token, verify the live connection, then sync any event's attendees as CRM contacts on demand." }),
|
|
273
|
+
/* @__PURE__ */ f(b, {
|
|
274
|
+
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
275
|
+
children: [/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("h1", {
|
|
276
|
+
className: "text-2xl font-bold text-text-primary",
|
|
277
|
+
children: /* @__PURE__ */ d(S, { children: "Integrations" })
|
|
278
|
+
}), /* @__PURE__ */ d("p", {
|
|
279
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
280
|
+
children: "CRM and marketing-automation connectors for this workspace."
|
|
281
|
+
})] }), /* @__PURE__ */ f(y, {
|
|
282
|
+
type: "button",
|
|
283
|
+
onClick: () => a(!0),
|
|
284
|
+
children: [/* @__PURE__ */ d(h, { className: "h-4 w-4" }), "New connector"]
|
|
285
|
+
})]
|
|
286
|
+
}),
|
|
287
|
+
s.loading ? null : _.length === 0 ? /* @__PURE__ */ d(x, {
|
|
288
|
+
treatment: "plain",
|
|
289
|
+
className: "border-border-seam p-8",
|
|
290
|
+
children: /* @__PURE__ */ d(C, {
|
|
291
|
+
illustration: "empty-data",
|
|
292
|
+
title: "No connectors yet",
|
|
293
|
+
description: "Connect a CRM or marketing-automation account to sync attendees automatically.",
|
|
294
|
+
className: "py-6"
|
|
295
|
+
})
|
|
296
|
+
}) : /* @__PURE__ */ d("div", {
|
|
297
|
+
className: "grid grid-cols-1 gap-4 md:grid-cols-2",
|
|
298
|
+
children: _.map((e) => /* @__PURE__ */ d(V, {
|
|
299
|
+
connector: e,
|
|
300
|
+
eventOptions: v,
|
|
301
|
+
onDelete: () => w(e),
|
|
302
|
+
onSync: (t) => D(e, t)
|
|
303
|
+
}, e.id))
|
|
304
|
+
}),
|
|
305
|
+
/* @__PURE__ */ d(B, {
|
|
306
|
+
open: t,
|
|
307
|
+
onOpenChange: a
|
|
308
|
+
}),
|
|
309
|
+
/* @__PURE__ */ f(x, {
|
|
310
|
+
treatment: "plain",
|
|
311
|
+
className: "border-border-seam p-4 text-xs text-text-secondary",
|
|
312
|
+
children: [
|
|
313
|
+
/* @__PURE__ */ d(T, {
|
|
314
|
+
className: "text-text-primary",
|
|
315
|
+
children: "Connector framework"
|
|
316
|
+
}),
|
|
317
|
+
/* @__PURE__ */ d("p", {
|
|
318
|
+
className: "mt-1",
|
|
319
|
+
children: "Only HubSpot has a real working client today. Adding a second provider (Salesforce, Mailchimp, …) is a backend-only change — no changes needed here once it ships."
|
|
320
|
+
}),
|
|
321
|
+
/* @__PURE__ */ f("p", {
|
|
322
|
+
className: "mt-2",
|
|
323
|
+
children: [/* @__PURE__ */ d("span", {
|
|
324
|
+
className: "font-medium text-text-primary",
|
|
325
|
+
children: "Not built yet:"
|
|
326
|
+
}), " outbound webhooks and calendar sync are still roadmap items, not shipped — this page only covers CRM/marketing-automation connectors."]
|
|
327
|
+
})
|
|
328
|
+
]
|
|
329
|
+
})
|
|
330
|
+
]
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
//#endregion
|
|
334
|
+
export { H as default };
|
|
335
|
+
|
|
336
|
+
//# sourceMappingURL=IntegrationsPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntegrationsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/IntegrationsPage.tsx"],"sourcesContent":["/**\n * IntegrationsPage (BOFF-4324)\n *\n * Real CRM/marketing-automation integration catalog: register a connector\n * (currently HubSpot — the only provider with a real working client, see\n * `IntegrationService` in `wspace-eventfullymanaged-svc`), see its live\n * connection status, and sync an event's attendees + leads to it as CRM\n * contacts. Sync runs are recorded and shown with real record counts.\n *\n * Replaces the prior `RoadmapPanel` placeholder for this route now that the\n * connector framework is real and shipped.\n */\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n Label,\n PagePurpose,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, Plug, Plus, RefreshCw, XCircle } from 'lucide-react';\nimport { useState } from 'react';\n\nimport {\n useCreateIntegrationConnectorMutation,\n useDeleteIntegrationConnectorMutation,\n useEventsQuery,\n useIntegrationConnectorsQuery,\n useIntegrationSyncRunsQuery,\n useSyncAttendeesToConnectorMutation,\n type IntegrationConnectorGql,\n} from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst PROVIDERS = [{ value: 'HUBSPOT', label: 'HubSpot' }];\n\nfunction statusMeta(status: IntegrationConnectorGql['status']): {\n variant: 'success' | 'warning' | 'secondary' | 'info';\n icon: typeof CheckCircle2;\n} {\n if (status === 'CONNECTED') return { variant: 'success', icon: CheckCircle2 };\n if (status === 'ERROR') return { variant: 'warning', icon: XCircle };\n return { variant: 'secondary', icon: Plug };\n}\n\ninterface CreateConnectorDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nfunction CreateConnectorDialog({ open, onOpenChange }: CreateConnectorDialogProps) {\n const [name, setName] = useState('');\n const [provider, setProvider] = useState('HUBSPOT');\n const [accessToken, setAccessToken] = useState('');\n const [createConnector, { loading: saving }] = useCreateIntegrationConnectorMutation();\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!name.trim() || !accessToken.trim()) {\n notify.error('Missing details', 'Give the connector a name and paste the private-app access token.');\n return;\n }\n try {\n const response = await createConnector({\n variables: { input: { provider, name: name.trim(), accessToken: accessToken.trim() } },\n });\n if (!mutationSucceeded(response, response.data?.createIntegrationConnector?.id, 'Could not create connector')) {\n return;\n }\n const created = response.data?.createIntegrationConnector;\n if (created?.status === 'CONNECTED') {\n notify.success('Connector connected', `${created.name} verified live against ${provider}.`);\n } else {\n notify.info(\n 'Connector created',\n `${created?.name ?? 'Connector'} was saved but could not verify — check the token.`\n );\n }\n setName('');\n setAccessToken('');\n onOpenChange(false);\n } catch (error) {\n notify.error('Could not create connector', describeError(error));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New integration connector</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Connects to your CRM/marketing-automation account with a private-app access token. The token is encrypted at\n rest and never shown again after saving.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form id=\"connector-form\" onSubmit={onSubmit} className=\"space-y-4\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Provider</span>\n <Select value={provider} onValueChange={setProvider}>\n <SelectTrigger aria-label=\"Provider\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {PROVIDERS.map((p) => (\n <SelectItem key={p.value} value={p.value}>\n {p.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-text-secondary\">\n HubSpot is the only live connector today — more providers can be added without a schema change.\n </p>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Connector name</span>\n <Input value={name} onChange={(e) => setName(e.target.value)} placeholder=\"Marketing HubSpot\" required />\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Private-app access token</span>\n <Input\n type=\"password\"\n value={accessToken}\n onChange={(e) => setAccessToken(e.target.value)}\n placeholder=\"pat-na1-…\"\n autoComplete=\"off\"\n required\n />\n <p className=\"text-xs text-text-secondary\">\n From your HubSpot developer sandbox: Settings → Integrations → Private Apps. Needs the{' '}\n <code>crm.objects.contacts.read/write</code> scopes.\n </p>\n </label>\n </form>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)} disabled={saving}>\n Cancel\n </Button>\n <Button type=\"submit\" form=\"connector-form\" disabled={saving}>\n {saving ? 'Connecting…' : 'Create & test connection'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\ninterface ConnectorCardProps {\n connector: IntegrationConnectorGql;\n onDelete: () => void;\n onSync: (eventId: string) => Promise<void>;\n eventOptions: Array<{ id: string; name: string }>;\n}\n\nfunction ConnectorCard({ connector, onDelete, onSync, eventOptions }: ConnectorCardProps) {\n const [syncEventId, setSyncEventId] = useState('');\n const [syncing, setSyncing] = useState(false);\n const { variant, icon: StatusIcon } = statusMeta(connector.status);\n const runsQuery = useIntegrationSyncRunsQuery({ connectorId: connector.id, limit: 5, offset: 0 });\n const runs = runsQuery.data?.integrationSyncRuns ?? [];\n\n const onSyncClick = async () => {\n if (!syncEventId) {\n notify.error('Pick an event', 'Choose which event to sync attendees from.');\n return;\n }\n setSyncing(true);\n try {\n await onSync(syncEventId);\n await runsQuery.refetch();\n } finally {\n setSyncing(false);\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <p className=\"truncate font-medium text-text-primary\">{connector.name}</p>\n <Badge variant=\"secondary\" className=\"uppercase\">\n {connector.provider}\n </Badge>\n </div>\n <p className=\"mt-1 flex items-center gap-1.5 text-xs text-text-secondary\">\n <StatusIcon className=\"h-3.5 w-3.5\" />\n <Badge variant={variant} className=\"uppercase\">\n {connector.status}\n </Badge>\n {connector.externalAccountName ? <span>· {connector.externalAccountName}</span> : null}\n </p>\n {connector.lastError ? <p className=\"mt-1 text-xs text-status-danger\">{connector.lastError}</p> : null}\n </div>\n <ActionMenu ariaLabel={`${connector.name} connector actions`} entityName={connector.name} onDelete={onDelete} />\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <Select value={syncEventId} onValueChange={setSyncEventId}>\n <SelectTrigger aria-label=\"Event to sync\" className=\"w-56\">\n <SelectValue placeholder=\"Select an event\" />\n </SelectTrigger>\n <SelectContent>\n {eventOptions.map((event) => (\n <SelectItem key={event.id} value={event.id}>\n {event.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Button type=\"button\" size=\"sm\" onClick={onSyncClick} disabled={syncing || connector.status !== 'CONNECTED'}>\n <RefreshCw className=\"h-4 w-4\" />\n {syncing ? 'Syncing…' : 'Sync attendees'}\n </Button>\n </div>\n\n {runs.length > 0 ? (\n <div className=\"mt-3 space-y-1.5 border-t border-border-seam pt-3\">\n {runs.map((run) => (\n <div key={run.id} className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>{new Date(run.startedAt).toLocaleString()}</span>\n <span className=\"flex items-center gap-2\">\n <Badge\n variant={run.status === 'SUCCESS' ? 'success' : run.status === 'FAILED' ? 'warning' : 'info'}\n className=\"uppercase\"\n >\n {run.status}\n </Badge>\n <span>\n {run.recordsSynced} synced{run.recordsFailed > 0 ? `, ${run.recordsFailed} failed` : ''}\n </span>\n </span>\n </div>\n ))}\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nexport default function IntegrationsPage() {\n const [createOpen, setCreateOpen] = useState(false);\n const connectorsQuery = useIntegrationConnectorsQuery({ limit: 50, offset: 0 });\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n const [deleteConnector] = useDeleteIntegrationConnectorMutation();\n const [syncAttendees] = useSyncAttendeesToConnectorMutation();\n\n const connectors = connectorsQuery.data?.integrationConnectors ?? [];\n const eventOptions = eventsData?.events?.items ?? [];\n\n const onDelete = async (connector: IntegrationConnectorGql) => {\n try {\n const response = await deleteConnector({ variables: { id: connector.id } });\n if (\n !mutationSucceeded(\n response,\n response.data?.deleteIntegrationConnector ? connector.id : undefined,\n 'Could not remove connector'\n )\n ) {\n return;\n }\n notify.success('Connector removed', `${connector.name} was disconnected.`);\n } catch (error) {\n notify.error('Could not remove connector', describeError(error));\n }\n };\n\n const onSync = async (connector: IntegrationConnectorGql, eventId: string) => {\n try {\n const response = await syncAttendees({ variables: { connectorId: connector.id, eventId } });\n const run = response.data?.syncAttendeesToConnector;\n if (!run) {\n notify.error('Sync failed', response.error?.message ?? 'Please try again.');\n return;\n }\n if (run.status === 'SUCCESS') {\n notify.success(\n 'Sync complete',\n `${run.recordsSynced} attendee${run.recordsSynced === 1 ? '' : 's'} synced to ${connector.name}.`\n );\n } else {\n notify.error('Sync failed', run.errorMessage ?? 'The provider rejected the sync.');\n }\n } catch (error) {\n notify.error('Could not sync attendees', describeError(error));\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Integration connectors sync this workspace's real attendee and lead data to your CRM or\n marketing-automation platform. Register a connector with a private-app access token, verify the live connection,\n then sync any event's attendees as CRM contacts on demand.\n </PagePurpose>\n\n <EmphasisPanel className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Integrations</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n CRM and marketing-automation connectors for this workspace.\n </p>\n </div>\n <Button type=\"button\" onClick={() => setCreateOpen(true)}>\n <Plus className=\"h-4 w-4\" />\n New connector\n </Button>\n </EmphasisPanel>\n\n {connectorsQuery.loading ? null : connectors.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-8\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No connectors yet\"\n description=\"Connect a CRM or marketing-automation account to sync attendees automatically.\"\n className=\"py-6\"\n />\n </GlassCard>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {connectors.map((connector) => (\n <ConnectorCard\n key={connector.id}\n connector={connector}\n eventOptions={eventOptions}\n onDelete={() => onDelete(connector)}\n onSync={(eventId) => onSync(connector, eventId)}\n />\n ))}\n </div>\n )}\n\n <CreateConnectorDialog open={createOpen} onOpenChange={setCreateOpen} />\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-4 text-xs text-text-secondary\">\n <Label className=\"text-text-primary\">Connector framework</Label>\n <p className=\"mt-1\">\n Only HubSpot has a real working client today. Adding a second provider (Salesforce, Mailchimp, …) is a\n backend-only change — no changes needed here once it ships.\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Not built yet:</span> outbound webhooks and calendar sync are\n still roadmap items, not shipped — this page only covers CRM/marketing-automation connectors.\n </p>\n </GlassCard>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkingPage.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/modules/NetworkingPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NetworkingPage.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/modules/NetworkingPage.tsx"],"names":[],"mappings":"AA6MA,MAAM,CAAC,OAAO,UAAU,cAAc,4CA6ZrC"}
|