@schandlergarcia/sf-web-components 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,292 @@
1
+ """
2
+ Generated schema for Engine Travel Command Center
3
+ Source: src/data/engine-sample-data.js field names
4
+ Note: This is a minimal schema for GraphQL tooling when introspection is broken
5
+ """
6
+
7
+ schema {
8
+ query: Query
9
+ }
10
+
11
+ type Query {
12
+ uiapi: UIAPI!
13
+ }
14
+
15
+ type UIAPI {
16
+ query: UIAPIQuery!
17
+ }
18
+
19
+ type UIAPIQuery {
20
+ Contact(first: Int, after: String, where: ContactFilter): ContactConnection
21
+ Trip__c(first: Int, after: String, where: Trip__cFilter): Trip__cConnection
22
+ Flight__c(first: Int, after: String, where: Flight__cFilter): Flight__cConnection
23
+ Booking__c(first: Int, after: String, where: Booking__cFilter): Booking__cConnection
24
+ Disruption__c(first: Int, after: String, where: Disruption__cFilter): Disruption__cConnection
25
+ Rebooking_Action__c(first: Int, after: String, where: Rebooking_Action__cFilter): Rebooking_Action__cConnection
26
+ Travel_Policy__c(first: Int, after: String, where: Travel_Policy__cFilter): Travel_Policy__cConnection
27
+ }
28
+
29
+ # Field value wrappers for FLS
30
+ type FieldValueString {
31
+ value: String
32
+ displayValue: String
33
+ }
34
+
35
+ type FieldValueFloat {
36
+ value: Float
37
+ displayValue: String
38
+ }
39
+
40
+ type FieldValueBoolean {
41
+ value: Boolean
42
+ displayValue: String
43
+ }
44
+
45
+ type FieldValueID {
46
+ value: ID
47
+ displayValue: String
48
+ }
49
+
50
+ # Contact (Travelers)
51
+ type Contact {
52
+ Id: ID!
53
+ FirstName: FieldValueString
54
+ LastName: FieldValueString
55
+ Department: FieldValueString
56
+ Home_Airport__c: FieldValueString
57
+ Travel_Policy_Tier__c: FieldValueString
58
+ Is_Active_Traveler__c: FieldValueBoolean
59
+ Role__c: FieldValueString
60
+ }
61
+
62
+ type ContactEdge {
63
+ node: Contact!
64
+ cursor: String!
65
+ }
66
+
67
+ type ContactConnection {
68
+ edges: [ContactEdge!]!
69
+ pageInfo: PageInfo!
70
+ totalCount: Int
71
+ }
72
+
73
+ input ContactFilter {
74
+ Id: IDFilter
75
+ FirstName: StringFilter
76
+ LastName: StringFilter
77
+ Department: StringFilter
78
+ }
79
+
80
+ # Trip__c
81
+ type Trip__c {
82
+ Id: ID!
83
+ Trip_Name__c: FieldValueString
84
+ Contact__c: FieldValueString
85
+ Origin_City__c: FieldValueString
86
+ Origin_Airport__c: FieldValueString
87
+ Destination_City__c: FieldValueString
88
+ Destination_Airport__c: FieldValueString
89
+ Start_Date__c: FieldValueString
90
+ End_Date__c: FieldValueString
91
+ Status__c: FieldValueString
92
+ Total_Cost__c: FieldValueFloat
93
+ In_Policy__c: FieldValueBoolean
94
+ Has_Disruption__c: FieldValueBoolean
95
+ }
96
+
97
+ type Trip__cEdge {
98
+ node: Trip__c!
99
+ cursor: String!
100
+ }
101
+
102
+ type Trip__cConnection {
103
+ edges: [Trip__cEdge!]!
104
+ pageInfo: PageInfo!
105
+ totalCount: Int
106
+ }
107
+
108
+ input Trip__cFilter {
109
+ Id: IDFilter
110
+ Trip_Name__c: StringFilter
111
+ Status__c: StringFilter
112
+ }
113
+
114
+ # Flight__c
115
+ type Flight__c {
116
+ Id: ID!
117
+ Flight_Number__c: FieldValueString
118
+ Airline__c: FieldValueString
119
+ Departure_Airport__c: FieldValueString
120
+ Departure_City__c: FieldValueString
121
+ Departure_Longitude__c: FieldValueFloat
122
+ Departure_Latitude__c: FieldValueFloat
123
+ Arrival_Airport__c: FieldValueString
124
+ Arrival_City__c: FieldValueString
125
+ Arrival_Longitude__c: FieldValueFloat
126
+ Arrival_Latitude__c: FieldValueFloat
127
+ Departure_DateTime__c: FieldValueString
128
+ Flight_Status__c: FieldValueString
129
+ Delay_Minutes__c: FieldValueFloat
130
+ Cabin_Class__c: FieldValueString
131
+ Contact__c: FieldValueString
132
+ }
133
+
134
+ type Flight__cEdge {
135
+ node: Flight__c!
136
+ cursor: String!
137
+ }
138
+
139
+ type Flight__cConnection {
140
+ edges: [Flight__cEdge!]!
141
+ pageInfo: PageInfo!
142
+ totalCount: Int
143
+ }
144
+
145
+ input Flight__cFilter {
146
+ Id: IDFilter
147
+ Flight_Number__c: StringFilter
148
+ Flight_Status__c: StringFilter
149
+ }
150
+
151
+ # Booking__c
152
+ type Booking__c {
153
+ Id: ID!
154
+ Contact__c: FieldValueString
155
+ Booking_Type__c: FieldValueString
156
+ Status__c: FieldValueString
157
+ Cost__c: FieldValueFloat
158
+ In_Policy__c: FieldValueBoolean
159
+ }
160
+
161
+ type Booking__cEdge {
162
+ node: Booking__c!
163
+ cursor: String!
164
+ }
165
+
166
+ type Booking__cConnection {
167
+ edges: [Booking__cEdge!]!
168
+ pageInfo: PageInfo!
169
+ totalCount: Int
170
+ }
171
+
172
+ input Booking__cFilter {
173
+ Id: IDFilter
174
+ Status__c: StringFilter
175
+ }
176
+
177
+ # Disruption__c
178
+ type Disruption__c {
179
+ Id: ID!
180
+ Flight_Number__c: FieldValueString
181
+ Disruption_Type__c: FieldValueString
182
+ Severity__c: FieldValueString
183
+ Status__c: FieldValueString
184
+ Impacted_Flight__c: FieldValueString
185
+ Trip__c: FieldValueString
186
+ City__c: FieldValueString
187
+ Affected_Traveler_Count__c: FieldValueFloat
188
+ Auto_Rebook_Eligible__c: FieldValueBoolean
189
+ Recommended_Action__c: FieldValueString
190
+ Description__c: FieldValueString
191
+ }
192
+
193
+ type Disruption__cEdge {
194
+ node: Disruption__c!
195
+ cursor: String!
196
+ }
197
+
198
+ type Disruption__cConnection {
199
+ edges: [Disruption__cEdge!]!
200
+ pageInfo: PageInfo!
201
+ totalCount: Int
202
+ }
203
+
204
+ input Disruption__cFilter {
205
+ Id: IDFilter
206
+ Severity__c: StringFilter
207
+ Status__c: StringFilter
208
+ }
209
+
210
+ # Rebooking_Action__c
211
+ type Rebooking_Action__c {
212
+ Id: ID!
213
+ Action_Type__c: FieldValueString
214
+ Status__c: FieldValueString
215
+ Handled_By__c: FieldValueString
216
+ Contact__c: FieldValueString
217
+ Original_Flight__c: FieldValueString
218
+ New_Flight__c: FieldValueString
219
+ Cost_Difference__c: FieldValueFloat
220
+ Notes__c: FieldValueString
221
+ Created_DateTime__c: FieldValueString
222
+ }
223
+
224
+ type Rebooking_Action__cEdge {
225
+ node: Rebooking_Action__c!
226
+ cursor: String!
227
+ }
228
+
229
+ type Rebooking_Action__cConnection {
230
+ edges: [Rebooking_Action__cEdge!]!
231
+ pageInfo: PageInfo!
232
+ totalCount: Int
233
+ }
234
+
235
+ input Rebooking_Action__cFilter {
236
+ Id: IDFilter
237
+ Status__c: StringFilter
238
+ }
239
+
240
+ # Travel_Policy__c
241
+ type Travel_Policy__c {
242
+ Id: ID!
243
+ Name: FieldValueString
244
+ Description__c: FieldValueString
245
+ Active__c: FieldValueBoolean
246
+ Policy_Tier__c: FieldValueString
247
+ Max_Flight_Cost__c: FieldValueFloat
248
+ Max_Hotel_Rate__c: FieldValueFloat
249
+ Advance_Booking_Days__c: FieldValueFloat
250
+ Preferred_Airlines__c: FieldValueString
251
+ Preferred_Hotel_Chains__c: FieldValueString
252
+ }
253
+
254
+ type Travel_Policy__cEdge {
255
+ node: Travel_Policy__c!
256
+ cursor: String!
257
+ }
258
+
259
+ type Travel_Policy__cConnection {
260
+ edges: [Travel_Policy__cEdge!]!
261
+ pageInfo: PageInfo!
262
+ totalCount: Int
263
+ }
264
+
265
+ input Travel_Policy__cFilter {
266
+ Id: IDFilter
267
+ Policy_Tier__c: StringFilter
268
+ }
269
+
270
+ # Pagination
271
+ type PageInfo {
272
+ hasNextPage: Boolean!
273
+ hasPreviousPage: Boolean!
274
+ startCursor: String
275
+ endCursor: String
276
+ }
277
+
278
+ # Filter input types
279
+ input IDFilter {
280
+ eq: ID
281
+ ne: ID
282
+ in: [ID!]
283
+ nin: [ID!]
284
+ }
285
+
286
+ input StringFilter {
287
+ eq: String
288
+ ne: String
289
+ like: String
290
+ in: [String!]
291
+ nin: [String!]
292
+ }
@@ -0,0 +1,49 @@
1
+ import { useState, useEffect } from "react";
2
+ import * as live from "@/data/engine-live-data";
3
+
4
+ const SIMULATED_LOAD_MS = 2000;
5
+
6
+ export interface EngineLiveData {
7
+ loading: boolean;
8
+ mapMarkers: typeof live.MAP_MARKERS;
9
+ mapArcs: typeof live.MAP_ARCS;
10
+ mapOverlays: typeof live.MAP_OVERLAYS;
11
+ flightStatusList: typeof live.FLIGHT_STATUS_LIST;
12
+ travelerCards: typeof live.TRAVELER_CARDS;
13
+ disruptionCards: typeof live.DISRUPTION_CARDS;
14
+ escalationCards: typeof live.ESCALATION_CARDS;
15
+ monthlySpend: typeof live.MONTHLY_SPEND;
16
+ metrics: typeof live.METRICS;
17
+ }
18
+
19
+ /**
20
+ * Provides the live dataset for the Engine Travel Command Center.
21
+ *
22
+ * Simulates a 2-second network fetch, then resolves with all live data.
23
+ * Does NOT control the data mode — that's handled by ENABLE_SAMPLE_DATA_CACHE
24
+ * in src/lib/dataStrategy.ts. When the flag is false, every useDataSource call
25
+ * returns its live prop (which this hook provides) instead of its sample prop.
26
+ */
27
+ export function useEngineLiveData(): EngineLiveData {
28
+ const [loading, setLoading] = useState(true);
29
+
30
+ useEffect(() => {
31
+ const timer = setTimeout(() => {
32
+ setLoading(false);
33
+ }, SIMULATED_LOAD_MS);
34
+ return () => clearTimeout(timer);
35
+ }, []);
36
+
37
+ return {
38
+ loading,
39
+ mapMarkers: live.MAP_MARKERS,
40
+ mapArcs: live.MAP_ARCS,
41
+ mapOverlays: live.MAP_OVERLAYS,
42
+ flightStatusList: live.FLIGHT_STATUS_LIST,
43
+ travelerCards: live.TRAVELER_CARDS,
44
+ disruptionCards: live.DISRUPTION_CARDS,
45
+ escalationCards: live.ESCALATION_CARDS,
46
+ monthlySpend: live.MONTHLY_SPEND,
47
+ metrics: live.METRICS,
48
+ };
49
+ }
@@ -0,0 +1,288 @@
1
+ /**
2
+ * useEvaAgent — React hook for the Agentforce Agent REST API
3
+ *
4
+ * Handles the full lifecycle:
5
+ * 1. OAuth client-credentials → access_token
6
+ * 2. POST …/sessions → sessionId + message href
7
+ * 3. POST …/messages → agent response text
8
+ * 4. DELETE …/sessions → cleanup on unmount
9
+ *
10
+ * All HTTP calls go through the Vite proxy (see vite.config.ts):
11
+ * /sf-oauth/* → myDomainUrl
12
+ * /sf-agent/* → agentApiBaseUrl
13
+ */
14
+
15
+ import { useCallback, useEffect, useRef, useState } from "react";
16
+ import { AGENT_API_CONFIG } from "@/config/agentApi";
17
+
18
+ export interface AgentMessage {
19
+ role: "user" | "agent";
20
+ text: string;
21
+ id: string;
22
+ timestamp: string;
23
+ }
24
+
25
+ interface SessionState {
26
+ accessToken: string;
27
+ sessionId: string;
28
+ messagesHref: string;
29
+ endHref: string;
30
+ }
31
+
32
+ export default function useEvaAgent() {
33
+ const [messages, setMessages] = useState<AgentMessage[]>([]);
34
+ const [isConnecting, setIsConnecting] = useState(false);
35
+ const [isReady, setIsReady] = useState(false);
36
+ const [isSending, setIsSending] = useState(false);
37
+ const [error, setError] = useState<string | null>(null);
38
+
39
+ const sessionRef = useRef<SessionState | null>(null);
40
+ const sequenceRef = useRef(1);
41
+ const abortRef = useRef<AbortController | null>(null);
42
+
43
+ /* ── Step 1: OAuth token ──────────────────────────────── */
44
+ async function authenticate(signal: AbortSignal): Promise<string> {
45
+ const { clientId, clientSecret } = AGENT_API_CONFIG;
46
+
47
+ const body = new URLSearchParams({
48
+ grant_type: "client_credentials",
49
+ client_id: clientId,
50
+ client_secret: clientSecret,
51
+ });
52
+
53
+ const res = await fetch("/sf-oauth/services/oauth2/token", {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
56
+ body,
57
+ signal,
58
+ });
59
+
60
+ if (!res.ok) {
61
+ const text = await res.text();
62
+ throw new Error(`OAuth failed (${res.status}): ${text}`);
63
+ }
64
+
65
+ const data = await res.json();
66
+ return data.access_token;
67
+ }
68
+
69
+ /* ── Step 2: Create session ───────────────────────────── */
70
+ async function createSession(
71
+ accessToken: string,
72
+ signal: AbortSignal
73
+ ): Promise<Omit<SessionState, "accessToken">> {
74
+ const { agentId, bypassUser, demoTraveler, myDomainUrl } =
75
+ AGENT_API_CONFIG;
76
+
77
+ const payload = {
78
+ externalSessionKey: crypto.randomUUID(),
79
+ instanceConfig: { endpoint: myDomainUrl },
80
+ streamingCapabilities: { chunkTypes: ["Text"] },
81
+ bypassUser,
82
+ variables: [
83
+ {
84
+ name: "$Context.EndUserId",
85
+ type: "Text",
86
+ value: demoTraveler.contactId,
87
+ },
88
+ {
89
+ name: "$Context.EndUserEmail",
90
+ type: "Text",
91
+ value: demoTraveler.email,
92
+ },
93
+ {
94
+ name: "$Context.EndUserFirstName",
95
+ type: "Text",
96
+ value: demoTraveler.firstName,
97
+ },
98
+ {
99
+ name: "$Context.EndUserLastName",
100
+ type: "Text",
101
+ value: demoTraveler.lastName,
102
+ },
103
+ ],
104
+ };
105
+
106
+ const res = await fetch(
107
+ `/sf-agent/einstein/ai-agent/v1/agents/${agentId}/sessions`,
108
+ {
109
+ method: "POST",
110
+ headers: {
111
+ Authorization: `Bearer ${accessToken}`,
112
+ "Content-Type": "application/json",
113
+ },
114
+ body: JSON.stringify(payload),
115
+ signal,
116
+ }
117
+ );
118
+
119
+ if (!res.ok) {
120
+ const text = await res.text();
121
+ throw new Error(`Session creation failed (${res.status}): ${text}`);
122
+ }
123
+
124
+ const data = await res.json();
125
+ return {
126
+ sessionId: data.sessionId,
127
+ messagesHref: data._links?.messages?.href ?? "",
128
+ endHref: data._links?.end?.href ?? "",
129
+ };
130
+ }
131
+
132
+ /* ── Step 3: Send message ─────────────────────────────── */
133
+ const sendMessage = useCallback(async (text: string) => {
134
+ const session = sessionRef.current;
135
+ if (!session) return;
136
+
137
+ const userMsg: AgentMessage = {
138
+ role: "user",
139
+ text,
140
+ id: `user-${Date.now()}`,
141
+ timestamp: new Date().toISOString(),
142
+ };
143
+ setMessages((prev) => [...prev, userMsg]);
144
+ setIsSending(true);
145
+
146
+ try {
147
+ const seqId = sequenceRef.current++;
148
+
149
+ const messagesUrl = session.messagesHref.startsWith("http")
150
+ ? `/sf-agent${new URL(session.messagesHref).pathname}`
151
+ : `/sf-agent${session.messagesHref}`;
152
+
153
+ const res = await fetch(messagesUrl, {
154
+ method: "POST",
155
+ headers: {
156
+ Authorization: `Bearer ${session.accessToken}`,
157
+ "Content-Type": "application/json",
158
+ Accept: "application/json",
159
+ },
160
+ body: JSON.stringify({
161
+ message: { sequenceId: seqId, type: "Text", text },
162
+ }),
163
+ });
164
+
165
+ if (!res.ok) {
166
+ const errText = await res.text();
167
+ throw new Error(`Agent message failed (${res.status}): ${errText}`);
168
+ }
169
+
170
+ const data = await res.json();
171
+
172
+ const agentTexts: string[] = [];
173
+ if (Array.isArray(data.messages)) {
174
+ for (const m of data.messages) {
175
+ if (m.type === "Inform" && m.message) {
176
+ agentTexts.push(m.message);
177
+ } else if (m.type === "Text" && m.message) {
178
+ agentTexts.push(m.message);
179
+ }
180
+ }
181
+ }
182
+
183
+ const responseText =
184
+ agentTexts.length > 0
185
+ ? agentTexts.join("\n\n")
186
+ : data.message ?? "No response from agent.";
187
+
188
+ const agentMsg: AgentMessage = {
189
+ role: "agent",
190
+ text: responseText,
191
+ id: `agent-${Date.now()}`,
192
+ timestamp: new Date().toISOString(),
193
+ };
194
+ setMessages((prev) => [...prev, agentMsg]);
195
+ } catch (err: unknown) {
196
+ const msg = err instanceof Error ? err.message : "Unknown error";
197
+ setError(msg);
198
+ const errMsg: AgentMessage = {
199
+ role: "agent",
200
+ text: `Error: ${msg}`,
201
+ id: `error-${Date.now()}`,
202
+ timestamp: new Date().toISOString(),
203
+ };
204
+ setMessages((prev) => [...prev, errMsg]);
205
+ } finally {
206
+ setIsSending(false);
207
+ }
208
+ }, []);
209
+
210
+ /* ── Step 4: End session ──────────────────────────────── */
211
+ const endSession = useCallback(async () => {
212
+ const session = sessionRef.current;
213
+ if (!session) return;
214
+
215
+ try {
216
+ const endUrl = session.endHref.startsWith("http")
217
+ ? `/sf-agent${new URL(session.endHref).pathname}`
218
+ : `/sf-agent${session.endHref}`;
219
+
220
+ await fetch(endUrl, {
221
+ method: "DELETE",
222
+ headers: { Authorization: `Bearer ${session.accessToken}` },
223
+ });
224
+ } catch {
225
+ /* best-effort cleanup */
226
+ }
227
+
228
+ sessionRef.current = null;
229
+ setIsReady(false);
230
+ }, []);
231
+
232
+ /* ── Init: authenticate + create session on mount ─────── */
233
+ const connect = useCallback(async () => {
234
+ if (sessionRef.current) return;
235
+
236
+ abortRef.current = new AbortController();
237
+ const { signal } = abortRef.current;
238
+
239
+ setIsConnecting(true);
240
+ setError(null);
241
+
242
+ try {
243
+ const accessToken = await authenticate(signal);
244
+ const session = await createSession(accessToken, signal);
245
+
246
+ sessionRef.current = { accessToken, ...session };
247
+ sequenceRef.current = 1;
248
+ setIsReady(true);
249
+ } catch (err: unknown) {
250
+ if ((err as Error).name !== "AbortError") {
251
+ const msg = err instanceof Error ? err.message : "Connection failed";
252
+ setError(msg);
253
+ }
254
+ } finally {
255
+ setIsConnecting(false);
256
+ }
257
+ }, []);
258
+
259
+ /* ── Cleanup on unmount ───────────────────────────────── */
260
+ useEffect(() => {
261
+ return () => {
262
+ abortRef.current?.abort();
263
+ if (sessionRef.current) {
264
+ const session = sessionRef.current;
265
+ const endUrl = session.endHref.startsWith("http")
266
+ ? `/sf-agent${new URL(session.endHref).pathname}`
267
+ : `/sf-agent${session.endHref}`;
268
+
269
+ fetch(endUrl, {
270
+ method: "DELETE",
271
+ headers: { Authorization: `Bearer ${session.accessToken}` },
272
+ }).catch(() => {});
273
+ sessionRef.current = null;
274
+ }
275
+ };
276
+ }, []);
277
+
278
+ return {
279
+ messages,
280
+ isConnecting,
281
+ isReady,
282
+ isSending,
283
+ error,
284
+ connect,
285
+ sendMessage,
286
+ endSession,
287
+ };
288
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schandlergarcia/sf-web-components",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Reusable Salesforce web components library with Tailwind CSS v4 and shadcn/ui",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -27,6 +27,7 @@
27
27
  "files": [
28
28
  "dist",
29
29
  "scripts",
30
+ "brands",
30
31
  "src/templates",
31
32
  "src/components",
32
33
  "src/lib",
@@ -50,7 +51,10 @@
50
51
  "postinstall": "node scripts/postinstall.mjs",
51
52
  "reset:command-center": "bash scripts/reset-command-center.sh",
52
53
  "validate:dashboard": "bash scripts/validate-dashboard.sh",
53
- "graphql:schema": "node scripts/get-graphql-schema.mjs"
54
+ "graphql:schema": "node scripts/get-graphql-schema.mjs",
55
+ "brand:engine": "node node_modules/@schandlergarcia/sf-web-components/scripts/apply-brand.mjs engine",
56
+ "brand:reset": "node node_modules/@schandlergarcia/sf-web-components/scripts/apply-brand.mjs --reset",
57
+ "brand:list": "node node_modules/@schandlergarcia/sf-web-components/scripts/apply-brand.mjs --list"
54
58
  },
55
59
  "peerDependencies": {
56
60
  "@heroicons/react": "^2.0.0",