@salesforce/templates 66.7.13 → 66.9.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.
Files changed (105) hide show
  1. package/lib/generators/lightningEmbeddingGenerator.d.ts +6 -0
  2. package/lib/generators/lightningEmbeddingGenerator.js +68 -0
  3. package/lib/generators/lightningEmbeddingGenerator.js.map +1 -0
  4. package/lib/i18n/i18n.d.ts +6 -0
  5. package/lib/i18n/i18n.js +6 -0
  6. package/lib/i18n/i18n.js.map +1 -1
  7. package/lib/index.d.ts +1 -0
  8. package/lib/index.js +1 -0
  9. package/lib/index.js.map +1 -1
  10. package/lib/templates/lightningembedding/default/default.css +5 -0
  11. package/lib/templates/lightningembedding/default/default.html +7 -0
  12. package/lib/templates/lightningembedding/default/default.js +5 -0
  13. package/lib/templates/lightningembedding/default/default.js-meta.xml +12 -0
  14. package/lib/templates/project/reactexternalapp/AGENT.md +3 -3
  15. package/lib/templates/project/reactexternalapp/CHANGELOG.md +428 -0
  16. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +792 -2031
  17. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +4 -4
  18. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +7 -9
  19. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +7 -15
  20. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +14 -19
  21. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +9 -5
  22. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +5 -3
  23. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +5 -3
  24. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useAsyncData.ts +11 -4
  25. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/api/userProfileApi.ts +12 -11
  26. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/authenticationConfig.ts +9 -9
  27. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/context/AuthContext.tsx +21 -4
  28. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/forms/auth-form.tsx +15 -1
  29. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/hooks/form.tsx +1 -1
  30. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/layouts/privateRouteLayout.tsx +2 -11
  31. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/ChangePassword.tsx +21 -5
  32. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/ForgotPassword.tsx +20 -5
  33. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Login.tsx +20 -5
  34. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Profile.tsx +80 -43
  35. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Register.tsx +16 -5
  36. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/ResetPassword.tsx +20 -5
  37. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/utils/helpers.ts +15 -52
  38. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +13 -13
  39. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/alerts/status-alert.tsx +11 -8
  40. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/input.tsx +1 -1
  41. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/hooks/useAsyncData.ts +67 -0
  42. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +7 -9
  43. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +7 -15
  44. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +19 -25
  45. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.json +4 -6
  46. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/vite-env.d.ts +0 -3
  47. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleAuthUtils.cls-meta.xml +1 -1
  48. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleChangePassword.cls-meta.xml +1 -1
  49. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleForgotPassword.cls-meta.xml +1 -1
  50. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleLogin.cls-meta.xml +1 -1
  51. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleRegistration.cls-meta.xml +1 -1
  52. package/lib/templates/project/reactexternalapp/_p_/_m_/package.xml +1 -1
  53. package/lib/templates/project/reactexternalapp/package.json +1 -1
  54. package/lib/templates/project/reactexternalapp/scripts/org-setup.config.json +0 -1
  55. package/lib/templates/project/reactexternalapp/scripts/org-setup.mjs +528 -44
  56. package/lib/templates/project/reactexternalapp/sfdx-project.json +1 -1
  57. package/lib/templates/project/reactinternalapp/AGENT.md +3 -3
  58. package/lib/templates/project/reactinternalapp/CHANGELOG.md +428 -0
  59. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +784 -2036
  60. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +5 -5
  61. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/reactinternalapp.uibundle-meta.xml +1 -0
  62. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +7 -9
  63. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +7 -15
  64. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +14 -19
  65. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +9 -5
  66. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +5 -3
  67. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +5 -3
  68. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useAsyncData.ts +11 -4
  69. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +13 -13
  70. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/AgentforceConversationClient.tsx +40 -44
  71. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/alerts/status-alert.tsx +11 -8
  72. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/input.tsx +1 -1
  73. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/hooks/useAsyncData.ts +67 -0
  74. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +7 -9
  75. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +7 -15
  76. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/types/conversation.ts +9 -0
  77. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/types/globals.d.ts +13 -0
  78. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.json +4 -6
  79. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/vite-env.d.ts +0 -3
  80. package/lib/templates/project/reactinternalapp/_p_/_m_/applications/reactinternalapp.app-meta.xml +17 -0
  81. package/lib/templates/project/reactinternalapp/_p_/_m_/permissionsets/reactinternalapp_Access.permissionset-meta.xml +9 -0
  82. package/lib/templates/project/reactinternalapp/package.json +1 -1
  83. package/lib/templates/project/reactinternalapp/scripts/org-setup.config.json +6 -3
  84. package/lib/templates/project/reactinternalapp/scripts/org-setup.mjs +528 -44
  85. package/lib/templates/project/reactinternalapp/sfdx-project.json +1 -1
  86. package/lib/templates/uiBundles/reactbasic/package-lock.json +1040 -593
  87. package/lib/templates/uiBundles/reactbasic/package.json +3 -3
  88. package/lib/templates/uiBundles/reactbasic/src/api/graphqlClient.ts +13 -13
  89. package/lib/templates/uiBundles/reactbasic/src/components/alerts/status-alert.tsx +11 -8
  90. package/lib/templates/uiBundles/reactbasic/src/components/ui/input.tsx +1 -1
  91. package/lib/templates/uiBundles/reactbasic/src/hooks/useAsyncData.ts +67 -0
  92. package/lib/templates/uiBundles/reactbasic/tsconfig.json +4 -6
  93. package/lib/templates/uiBundles/reactbasic/vite-env.d.ts +0 -3
  94. package/lib/tsconfig.tsbuildinfo +1 -1
  95. package/lib/utils/lightningEmbedding.d.ts +12 -0
  96. package/lib/utils/lightningEmbedding.js +50 -0
  97. package/lib/utils/lightningEmbedding.js.map +1 -0
  98. package/lib/utils/types.d.ts +15 -6
  99. package/lib/utils/types.js +8 -5
  100. package/lib/utils/types.js.map +1 -1
  101. package/package.json +6 -6
  102. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useCachedAsyncData.ts +0 -188
  103. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/layout/card-skeleton.tsx +0 -38
  104. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/layouts/authenticationRouteLayout.tsx +0 -21
  105. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useCachedAsyncData.ts +0 -188
@@ -18,8 +18,9 @@
18
18
  "graphql:schema": "node scripts/get-graphql-schema.mjs"
19
19
  },
20
20
  "dependencies": {
21
- "@salesforce/sdk-data": "^1.135.0",
22
- "@salesforce/ui-bundle": "^1.135.0",
21
+ "@salesforce/agentforce-conversation-client": "file:../../../../../../../../../agentforceConversationClient",
22
+ "@salesforce/platform-sdk": "file:../../../../../../../../../sdk/platform-sdk",
23
+ "@salesforce/ui-bundle": "file:../../../../../../../../../ui-bundle",
23
24
  "@tailwindcss/vite": "^4.1.17",
24
25
  "class-variance-authority": "^0.7.1",
25
26
  "clsx": "^2.1.1",
@@ -34,8 +35,7 @@
34
35
  "sonner": "^1.7.0",
35
36
  "tailwind-merge": "^3.5.0",
36
37
  "tailwindcss": "^4.1.17",
37
- "tw-animate-css": "^1.4.0",
38
- "@salesforce/agentforce-conversation-client": "^1.116.9"
38
+ "tw-animate-css": "^1.4.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@eslint/js": "^9.39.1",
@@ -45,7 +45,7 @@
45
45
  "@graphql-eslint/eslint-plugin": "^4.1.0",
46
46
  "@graphql-tools/utils": "^11.0.0",
47
47
  "@playwright/test": "^1.49.0",
48
- "@salesforce/vite-plugin-ui-bundle": "^1.135.0",
48
+ "@salesforce/vite-plugin-ui-bundle": "file:../../../../../../../../../vite-plugin-ui-bundle",
49
49
  "@testing-library/jest-dom": "^6.6.3",
50
50
  "@testing-library/react": "^16.1.0",
51
51
  "@testing-library/user-event": "^14.5.2",
@@ -4,4 +4,5 @@
4
4
  <description>A Salesforce UI Bundle.</description>
5
5
  <isActive>true</isActive>
6
6
  <version>1</version>
7
+ <target>CustomApplication</target>
7
8
  </UIBundle>
@@ -1,6 +1,6 @@
1
1
  import { useState } from "react";
2
2
  import { useParams, useNavigate } from "react-router";
3
- import { createDataSDK } from "@salesforce/sdk-data";
3
+ import { createDataSDK } from "@salesforce/platform-sdk";
4
4
  import { AlertCircle, ChevronDown, ChevronRight, FileQuestion } from "lucide-react";
5
5
  import GET_ACCOUNT_DETAIL from "../api/query/getAccountDetail.graphql?raw";
6
6
  import type {
@@ -18,7 +18,7 @@ import {
18
18
  } from "../../../../components/ui/collapsible";
19
19
  import { Separator } from "../../../../components/ui/separator";
20
20
  import { Skeleton } from "../../../../components/ui/skeleton";
21
- import { useCachedAsyncData } from "../../hooks/useCachedAsyncData";
21
+ import { useAsyncData } from "../../hooks/useAsyncData";
22
22
  import { ObjectBreadcrumb } from "../../components/ObjectBreadcrumb";
23
23
 
24
24
  type AccountNode = NonNullable<
@@ -29,16 +29,16 @@ type AccountNode = NonNullable<
29
29
 
30
30
  async function fetchAccountDetail(recordId: string): Promise<AccountNode | null | undefined> {
31
31
  const data = await createDataSDK();
32
- const response = await data.graphql?.<GetAccountDetailQuery, GetAccountDetailQueryVariables>({
32
+ const result = await data.graphql!.query<GetAccountDetailQuery, GetAccountDetailQueryVariables>({
33
33
  query: GET_ACCOUNT_DETAIL,
34
34
  variables: { id: recordId },
35
35
  });
36
36
 
37
- if (response?.errors?.length) {
38
- throw new Error(response.errors.map((e) => e.message).join("; "));
37
+ if (result.errors?.length) {
38
+ throw new Error(result.errors.map((e) => e.message).join("; "));
39
39
  }
40
40
 
41
- return response?.data?.uiapi?.query?.Account?.edges?.[0]?.node;
41
+ return result.data?.uiapi?.query?.Account?.edges?.[0]?.node;
42
42
  }
43
43
 
44
44
  export default function AccountObjectDetail() {
@@ -49,9 +49,7 @@ export default function AccountObjectDetail() {
49
49
  data: account,
50
50
  loading,
51
51
  error,
52
- } = useCachedAsyncData(() => fetchAccountDetail(recordId!), [recordId], {
53
- key: `account:${recordId}`,
54
- });
52
+ } = useAsyncData(() => fetchAccountDetail(recordId!), [recordId]);
55
53
 
56
54
  return (
57
55
  <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
@@ -6,7 +6,7 @@ import {
6
6
  fetchDistinctIndustries,
7
7
  fetchDistinctTypes,
8
8
  } from "../api/accountSearchService";
9
- import { useCachedAsyncData } from "../../hooks/useCachedAsyncData";
9
+ import { useAsyncData } from "../../hooks/useAsyncData";
10
10
  import { fieldValue } from "../../utils/fieldUtils";
11
11
  import { useObjectSearchParams } from "../../hooks/useObjectSearchParams";
12
12
  import { Alert, AlertTitle, AlertDescription } from "../../../../components/ui/alert";
@@ -42,8 +42,8 @@ import PaginationControls from "../../components/PaginationControls";
42
42
  import type { PaginationConfig } from "../../hooks/useObjectSearchParams";
43
43
 
44
44
  const PAGINATION_CONFIG: PaginationConfig = {
45
- defaultPageSize: 6,
46
- validPageSizes: [6, 12, 24, 48],
45
+ defaultPageSize: 7,
46
+ validPageSizes: [7, 14, 28, 42],
47
47
  };
48
48
 
49
49
  type AccountNode = NonNullable<
@@ -77,22 +77,15 @@ const ACCOUNT_SORT_CONFIGS: SortFieldConfig<keyof Account_OrderBy>[] = [
77
77
 
78
78
  export default function AccountSearch() {
79
79
  const [filtersOpen, setFiltersOpen] = useState(true);
80
- const { data: industryOptions } = useCachedAsyncData(fetchDistinctIndustries, [], {
81
- key: "distinctIndustries",
82
- ttl: 300_000,
83
- });
84
- const { data: typeOptions } = useCachedAsyncData(fetchDistinctTypes, [], {
85
- key: "distinctTypes",
86
- ttl: 300_000,
87
- });
80
+ const { data: industryOptions } = useAsyncData(fetchDistinctIndustries, []);
81
+ const { data: typeOptions } = useAsyncData(fetchDistinctTypes, []);
88
82
 
89
83
  const { filters, sort, query, pagination, resetAll } = useObjectSearchParams<
90
84
  Account_Filter,
91
85
  Account_OrderBy
92
86
  >(FILTER_CONFIGS, ACCOUNT_SORT_CONFIGS, PAGINATION_CONFIG);
93
87
 
94
- const searchKey = `accounts:${JSON.stringify({ where: query.where, orderBy: query.orderBy, first: pagination.pageSize, after: pagination.afterCursor })}`;
95
- const { data, loading, error } = useCachedAsyncData(
88
+ const { data, loading, error } = useAsyncData(
96
89
  () =>
97
90
  searchAccounts({
98
91
  where: query.where,
@@ -101,7 +94,6 @@ export default function AccountSearch() {
101
94
  after: pagination.afterCursor,
102
95
  }),
103
96
  [query.where, query.orderBy, pagination.pageSize, pagination.afterCursor],
104
- { key: searchKey },
105
97
  );
106
98
 
107
99
  const pageInfo = data?.pageInfo;
@@ -196,7 +188,7 @@ export default function AccountSearch() {
196
188
  <ActiveFilters filters={filters.active} onRemove={filters.remove} />
197
189
  </div>
198
190
 
199
- <div className="min-h-112">
191
+ <div className="min-h-132">
200
192
  {/* Loading state */}
201
193
  {loading && (
202
194
  <>
@@ -1,4 +1,4 @@
1
- import { createDataSDK } from "@salesforce/sdk-data";
1
+ import { createDataSDK } from "@salesforce/platform-sdk";
2
2
 
3
3
  export interface ObjectSearchOptions<TWhere, TOrderBy> {
4
4
  where?: TWhere;
@@ -21,24 +21,20 @@ export async function searchObjects<TResult, TQuery, TVariables>(
21
21
  const { where, orderBy, first = 20, after } = options;
22
22
 
23
23
  const data = await createDataSDK();
24
- const response = await data.graphql?.<TQuery, TVariables>({
25
- query,
26
- variables: {
27
- first,
28
- after,
29
- where,
30
- orderBy,
31
- } as TVariables,
24
+ const variables = { first, after, where, orderBy } as TVariables;
25
+ const result = await data.graphql!.query<TQuery, TVariables>({
26
+ query: query,
27
+ variables: variables,
32
28
  });
33
29
 
34
- if (response?.errors?.length) {
35
- throw new Error(response.errors.map((e) => e.message).join("; "));
30
+ if (result.errors?.length) {
31
+ throw new Error(result.errors.map((e) => e.message).join("; "));
36
32
  }
37
33
 
38
- const result = (response?.data as Record<string, unknown> | undefined)?.uiapi as
34
+ const uiapi = (result.data as Record<string, unknown> | undefined)?.uiapi as
39
35
  | Record<string, unknown>
40
36
  | undefined;
41
- const queryResult = (result?.query as Record<string, unknown> | undefined)?.[objectName] as
37
+ const queryResult = (uiapi?.query as Record<string, unknown> | undefined)?.[objectName] as
42
38
  | TResult
43
39
  | undefined;
44
40
 
@@ -59,17 +55,16 @@ export async function fetchDistinctValues<TQuery>(
59
55
  fieldName: string,
60
56
  ): Promise<PicklistOption[]> {
61
57
  const data = await createDataSDK();
62
- const response = await data.graphql?.<TQuery>({ query });
63
- const errors = response?.errors;
58
+ const result = await data.graphql!.query<TQuery>({ query: query });
64
59
 
65
- if (errors?.length) {
66
- throw new Error(errors.map((e) => e.message).join("; "));
60
+ if (result.errors?.length) {
61
+ throw new Error(result.errors.map((e) => e.message).join("; "));
67
62
  }
68
63
 
69
- const result = (response?.data as Record<string, unknown> | undefined)?.uiapi as
64
+ const uiapi = (result.data as Record<string, unknown> | undefined)?.uiapi as
70
65
  | Record<string, unknown>
71
66
  | undefined;
72
- const aggregate = (result?.aggregate as Record<string, unknown> | undefined)?.[objectName] as
67
+ const aggregate = (uiapi?.aggregate as Record<string, unknown> | undefined)?.[objectName] as
73
68
  | { edges?: Array<{ node?: { aggregate?: Record<string, unknown> } }> }
74
69
  | undefined;
75
70
 
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from "react";
1
+ import { useState } from "react";
2
2
  import { Input } from "../../../../components/ui/input";
3
3
 
4
4
  import { useFilterField } from "../FilterContext";
@@ -67,12 +67,16 @@ export function NumericRangeFilterInputs({
67
67
 
68
68
  const externalMin = value?.min ?? "";
69
69
  const externalMax = value?.max ?? "";
70
- useEffect(() => {
70
+ const [prevExternalMin, setPrevExternalMin] = useState(externalMin);
71
+ const [prevExternalMax, setPrevExternalMax] = useState(externalMax);
72
+ if (prevExternalMin !== externalMin) {
73
+ setPrevExternalMin(externalMin);
71
74
  setLocalMin(externalMin);
72
- }, [externalMin]);
73
- useEffect(() => {
75
+ }
76
+ if (prevExternalMax !== externalMax) {
77
+ setPrevExternalMax(externalMax);
74
78
  setLocalMax(externalMax);
75
- }, [externalMax]);
79
+ }
76
80
 
77
81
  const isOutOfBounds = (v: string) => {
78
82
  if (v === "") return false;
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from "react";
1
+ import { useState } from "react";
2
2
 
3
3
  import { SearchBar } from "../SearchBar";
4
4
  import { useFilterField } from "../FilterContext";
@@ -22,9 +22,11 @@ export function SearchFilter({
22
22
  const [localValue, setLocalValue] = useState(value?.value ?? "");
23
23
 
24
24
  const externalValue = value?.value ?? "";
25
- useEffect(() => {
25
+ const [prevExternalValue, setPrevExternalValue] = useState(externalValue);
26
+ if (prevExternalValue !== externalValue) {
27
+ setPrevExternalValue(externalValue);
26
28
  setLocalValue(externalValue);
27
- }, [externalValue]);
29
+ }
28
30
 
29
31
  const debouncedOnChange = useDebouncedCallback((v: string) => {
30
32
  if (v) {
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from "react";
1
+ import { useState } from "react";
2
2
  import { Input } from "../../../../components/ui/input";
3
3
  import { cn } from "../../../../lib/utils";
4
4
  import { useFilterField } from "../FilterContext";
@@ -62,9 +62,11 @@ export function TextFilterInput({
62
62
  const [localValue, setLocalValue] = useState(value?.value ?? "");
63
63
 
64
64
  const externalValue = value?.value ?? "";
65
- useEffect(() => {
65
+ const [prevExternalValue, setPrevExternalValue] = useState(externalValue);
66
+ if (prevExternalValue !== externalValue) {
67
+ setPrevExternalValue(externalValue);
66
68
  setLocalValue(externalValue);
67
- }, [externalValue]);
69
+ }
68
70
 
69
71
  const debouncedOnChange = useDebouncedCallback((v: string) => {
70
72
  if (v) {
@@ -22,16 +22,24 @@ export function useAsyncData<T>(
22
22
  const [data, setData] = useState<T | null>(null);
23
23
  const [loading, setLoading] = useState(true);
24
24
  const [error, setError] = useState<string | null>(null);
25
+ const [generation, setGeneration] = useState(0);
25
26
 
26
27
  const fetcherRef = useRef(fetcher);
27
28
  useEffect(() => {
28
29
  fetcherRef.current = fetcher;
29
30
  });
30
31
 
32
+ // Detect dep changes during render to reset loading state and bump generation
33
+ const [prevDeps, setPrevDeps] = useState(deps);
34
+ if (deps.length !== prevDeps.length || deps.some((d, i) => d !== prevDeps[i])) {
35
+ setPrevDeps(deps);
36
+ setGeneration((g) => g + 1);
37
+ if (!loading) setLoading(true);
38
+ if (error !== null) setError(null);
39
+ }
40
+
31
41
  useEffect(() => {
32
42
  let cancelled = false;
33
- setLoading(true);
34
- setError(null);
35
43
 
36
44
  fetcherRef
37
45
  .current()
@@ -49,8 +57,7 @@ export function useAsyncData<T>(
49
57
  return () => {
50
58
  cancelled = true;
51
59
  };
52
- // eslint-disable-next-line react-hooks/exhaustive-deps --- deps are explicitly managed by the caller
53
- }, deps);
60
+ }, [generation]);
54
61
 
55
62
  return { data, loading, error };
56
63
  }
@@ -1,28 +1,28 @@
1
1
  /**
2
- * Thin GraphQL client: createDataSDK + data.graphql with centralized error handling.
3
- * Use with gql-tagged queries and generated operation types for type-safe calls.
2
+ * Thin GraphQL client: createDataSDK + sdk.graphql.query with centralized
3
+ * error handling. Use with gql-tagged queries and generated operation types
4
+ * for type-safe calls.
4
5
  */
5
- import { createDataSDK } from '@salesforce/sdk-data';
6
+ import { createDataSDK } from '@salesforce/platform-sdk';
6
7
 
7
8
  export async function executeGraphQL<TData, TVariables>(
8
9
  query: string,
9
10
  variables?: TVariables
10
11
  ): Promise<TData> {
11
12
  const data = await createDataSDK();
12
- // SDK types graphql() first param as string; at runtime it may accept gql DocumentNode too
13
- const response = await data.graphql?.<TData, TVariables>({
14
- query,
15
- variables,
13
+ const result = await data.graphql!.query<TData, TVariables>({
14
+ query: query,
15
+ variables: variables,
16
16
  });
17
17
 
18
- if (!response) {
19
- throw new Error('GraphQL response is undefined');
18
+ if (result.errors?.length) {
19
+ const msg = result.errors.map(e => e.message).join('; ');
20
+ throw new Error(`GraphQL Error: ${msg}`);
20
21
  }
21
22
 
22
- if (response?.errors?.length) {
23
- const msg = response.errors.map(e => e.message).join('; ');
24
- throw new Error(`GraphQL Error: ${msg}`);
23
+ if (result.data == null) {
24
+ throw new Error('GraphQL response data is null');
25
25
  }
26
26
 
27
- return response.data;
27
+ return result.data;
28
28
  }
@@ -13,38 +13,6 @@ import type {
13
13
  } from "../types/conversation";
14
14
 
15
15
  const GLOBAL_HOST_ID = "agentforce-conversation-client-global-host";
16
-
17
- const DEFAULT_STYLE_TOKENS: NonNullable<AgentforceClientConfig["styleTokens"]> = {
18
- containerBackground: "#fafafa",
19
-
20
- headerBlockBackground: "#372949",
21
- headerBlockTextColor: "#ffffff",
22
- headerBlockIconColor: "#ffffff",
23
- headerBlockBorderBottomColor: "#3b0764",
24
- headerBlockFocusBorder: "#c4b5fd",
25
-
26
- messageBlockInboundBackgroundColor: "#ffffff",
27
- messageBlockInboundTextColor: "#1f2937",
28
- messageBlockInboundBorder: "1px solid #e5e7eb",
29
-
30
- messageBlockOutboundBackgroundColor: "#ede9fe",
31
- messageBlockOutboundTextColor: "#1f2937",
32
- messageBlockOutboundBorder: "1px solid #d8b4fe",
33
-
34
- messageInputTextColor: "#1f2937",
35
- messageInputTextBackgroundColor: "#ffffff",
36
- messageInputFooterBorderColor: "#d1d5db",
37
- messageInputFooterBorderFocusColor: "#9ca3af",
38
- messageInputFocusShadow: "0 0 0 3px rgba(156, 163, 175, 0.25)",
39
- messageInputFooterPlaceholderText: "#6b7280",
40
-
41
- messageInputFooterSendButton: "#7e22ce",
42
- messageInputFooterSendButtonHoverColor: "#6b21a8",
43
- messageInputSendButtonIconColor: "#ffffff",
44
- messageInputSendButtonDisabledColor: "#e5e7eb",
45
- messageInputActionButtonFocusBorder: "#a855f7",
46
- errorBlockBackground: "#fafafa",
47
- };
48
16
  const SINGLETON_KEY = "__agentforceConversationClientSingleton";
49
17
 
50
18
  interface AgentforceConversationClientSingleton {
@@ -56,6 +24,12 @@ interface WindowWithAgentforceSingleton extends Window {
56
24
  [SINGLETON_KEY]?: AgentforceConversationClientSingleton;
57
25
  }
58
26
 
27
+ type GlobalWithSfdcEnv = typeof globalThis & {
28
+ SFDC_ENV?: {
29
+ orgUrl?: string;
30
+ };
31
+ };
32
+
59
33
  function getSingleton(): AgentforceConversationClientSingleton {
60
34
  const win = window as WindowWithAgentforceSingleton;
61
35
  if (!win[SINGLETON_KEY]) {
@@ -77,7 +51,8 @@ function getOrCreateGlobalHost(): HTMLDivElement {
77
51
  }
78
52
 
79
53
  function getDefaultEmbedOptions(): ResolvedEmbedOptions {
80
- return { salesforceOrigin: window.location.origin };
54
+ const sfdcEnv = (globalThis as GlobalWithSfdcEnv).SFDC_ENV;
55
+ return { salesforceOrigin: sfdcEnv?.orgUrl };
81
56
  }
82
57
 
83
58
  /**
@@ -96,6 +71,8 @@ export function AgentforceConversationClient({
96
71
  styleTokens,
97
72
  salesforceOrigin,
98
73
  frontdoorUrl,
74
+ onReady,
75
+ onError,
99
76
  }: AgentforceConversationClientProps) {
100
77
  const containerRef = useRef<HTMLDivElement>(null);
101
78
  const normalizedAgentforceClientConfig = useMemo<AgentforceClientConfig>(() => {
@@ -111,8 +88,9 @@ export function AgentforceConversationClient({
111
88
  return {
112
89
  ...(agentId !== undefined && { agentId }),
113
90
  ...(agentLabel !== undefined && { agentLabel }),
114
- styleTokens: { ...DEFAULT_STYLE_TOKENS, ...styleTokens },
91
+ ...(styleTokens !== undefined && { styleTokens }),
115
92
  renderingConfig,
93
+ channel: "Vibes",
116
94
  };
117
95
  }, [agentId, agentLabel, inlineProp, headerEnabled, showHeaderIcon, width, height, styleTokens]);
118
96
 
@@ -146,13 +124,31 @@ export function AgentforceConversationClient({
146
124
  }
147
125
  const host = inline ? containerRef.current! : getOrCreateGlobalHost();
148
126
 
149
- embedAgentforceClient({
150
- container: host,
151
- salesforceOrigin: salesforceOrigin ?? options.salesforceOrigin,
152
- frontdoorUrl: frontdoorUrl ?? options.frontdoorUrl,
153
- agentforceClientConfig: normalizedAgentforceClientConfig,
154
- });
155
- singleton.initialized = true;
127
+ try {
128
+ embedAgentforceClient({
129
+ container: host,
130
+ salesforceOrigin: salesforceOrigin ?? options.salesforceOrigin,
131
+ frontdoorUrl: frontdoorUrl ?? options.frontdoorUrl,
132
+ agentforceClientConfig: normalizedAgentforceClientConfig,
133
+ onReady,
134
+ onError,
135
+ });
136
+ singleton.initialized = true;
137
+ } catch (err) {
138
+ // Strip a partially-created LO element so the next mount can retry.
139
+ const partialEmbed = document.querySelector('lightning-out-application[data-lo="acc"]');
140
+ partialEmbed?.remove();
141
+ console.error("AgentforceConversationClient: initialization failed", err);
142
+ }
143
+ };
144
+
145
+ const safeInitialize = (options: ResolvedEmbedOptions) => {
146
+ try {
147
+ initialize(options);
148
+ } catch (initErr) {
149
+ console.error("AgentforceConversationClient: initialization failed", initErr);
150
+ singleton.initialized = false;
151
+ }
156
152
  };
157
153
 
158
154
  const shouldFetchFrontdoor = window.location.hostname === "localhost";
@@ -165,7 +161,7 @@ export function AgentforceConversationClient({
165
161
  return;
166
162
  }
167
163
  const { frontdoorUrl: resolvedFrontdoorUrl } = await res.json();
168
- initialize({ frontdoorUrl: resolvedFrontdoorUrl });
164
+ safeInitialize({ frontdoorUrl: resolvedFrontdoorUrl });
169
165
  })
170
166
  .catch((err) => {
171
167
  console.error("AgentforceConversationClient: failed to fetch frontdoor URL", err);
@@ -176,7 +172,7 @@ export function AgentforceConversationClient({
176
172
  } else {
177
173
  singleton.initPromise = Promise.resolve()
178
174
  .then(() => {
179
- initialize(getDefaultEmbedOptions());
175
+ safeInitialize(getDefaultEmbedOptions());
180
176
  })
181
177
  .catch((err) => {
182
178
  console.error("AgentforceConversationClient: failed to embed Agentforce client", err);
@@ -190,7 +186,7 @@ export function AgentforceConversationClient({
190
186
  // Intentionally no cleanup:
191
187
  // This component guarantees a single LO initialization per window.
192
188
  };
193
- }, [salesforceOrigin, frontdoorUrl, normalizedAgentforceClientConfig, inline]);
189
+ }, [salesforceOrigin, frontdoorUrl, normalizedAgentforceClientConfig, inline, onReady, onError]);
194
190
 
195
191
  if (!inline) {
196
192
  return null;
@@ -1,5 +1,5 @@
1
1
  import { cva, type VariantProps } from 'class-variance-authority';
2
- import { AlertCircleIcon, CheckCircle2Icon } from 'lucide-react';
2
+ import { AlertCircleIcon, CheckCircle2Icon, InfoIcon } from 'lucide-react';
3
3
  import { Alert, AlertDescription } from '../../components/ui/alert';
4
4
  import { useId } from 'react';
5
5
 
@@ -8,6 +8,7 @@ const statusAlertVariants = cva('', {
8
8
  variant: {
9
9
  error: '',
10
10
  success: '',
11
+ info: 'text-blue-600 *:[svg]:text-current *:data-[slot=alert-description]:text-blue-600/90',
11
12
  },
12
13
  },
13
14
  defaultVariants: {
@@ -18,11 +19,11 @@ const statusAlertVariants = cva('', {
18
19
  interface StatusAlertProps extends VariantProps<typeof statusAlertVariants> {
19
20
  children?: React.ReactNode;
20
21
  /** Alert variant type. @default "error" */
21
- variant?: 'error' | 'success';
22
+ variant?: 'error' | 'success' | 'info';
22
23
  }
23
24
 
24
25
  /**
25
- * Status alert component for displaying error or success messages.
26
+ * Status alert component for displaying error, success, or info messages.
26
27
  * Returns null if no children are provided.
27
28
  */
28
29
  export function StatusAlert({ children, variant = 'error' }: StatusAlertProps) {
@@ -31,6 +32,12 @@ export function StatusAlert({ children, variant = 'error' }: StatusAlertProps) {
31
32
 
32
33
  const isError = variant === 'error';
33
34
 
35
+ const icon = {
36
+ error: <AlertCircleIcon aria-hidden="true" />,
37
+ success: <CheckCircle2Icon aria-hidden="true" />,
38
+ info: <InfoIcon aria-hidden="true" />,
39
+ }[variant];
40
+
34
41
  return (
35
42
  <Alert
36
43
  variant={isError ? 'destructive' : 'default'}
@@ -38,11 +45,7 @@ export function StatusAlert({ children, variant = 'error' }: StatusAlertProps) {
38
45
  aria-describedby={descriptionId}
39
46
  role={isError ? 'alert' : 'status'}
40
47
  >
41
- {isError ? (
42
- <AlertCircleIcon aria-hidden="true" />
43
- ) : (
44
- <CheckCircle2Icon aria-hidden="true" />
45
- )}
48
+ {icon}
46
49
  <AlertDescription id={descriptionId}>{children}</AlertDescription>
47
50
  </Alert>
48
51
  );
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
8
8
  type={type}
9
9
  data-slot="input"
10
10
  className={cn(
11
- 'dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
11
+ 'dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground/70 placeholder:italic w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
12
12
  className
13
13
  )}
14
14
  {...props}
@@ -0,0 +1,67 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+
3
+ interface UseAsyncDataResult<T> {
4
+ data: T | null;
5
+ loading: boolean;
6
+ error: string | null;
7
+ }
8
+
9
+ /**
10
+ * Runs an async fetcher on mount and whenever `deps` change.
11
+ * Returns the loading/error/data state. Does not cache — every call
12
+ * to the fetcher hits the source directly.
13
+ *
14
+ * A cleanup flag prevents state updates if the component unmounts
15
+ * or deps change before the fetch completes (avoids React warnings
16
+ * and stale updates from out-of-order responses).
17
+ */
18
+ export function useAsyncData<T>(
19
+ fetcher: () => Promise<T>,
20
+ deps: React.DependencyList
21
+ ): UseAsyncDataResult<T> {
22
+ const [data, setData] = useState<T | null>(null);
23
+ const [loading, setLoading] = useState(true);
24
+ const [error, setError] = useState<string | null>(null);
25
+ const [generation, setGeneration] = useState(0);
26
+
27
+ const fetcherRef = useRef(fetcher);
28
+ useEffect(() => {
29
+ fetcherRef.current = fetcher;
30
+ });
31
+
32
+ // Detect dep changes during render to reset loading state and bump generation
33
+ const [prevDeps, setPrevDeps] = useState(deps);
34
+ if (
35
+ deps.length !== prevDeps.length ||
36
+ deps.some((d, i) => d !== prevDeps[i])
37
+ ) {
38
+ setPrevDeps(deps);
39
+ setGeneration(g => g + 1);
40
+ if (!loading) setLoading(true);
41
+ if (error !== null) setError(null);
42
+ }
43
+
44
+ useEffect(() => {
45
+ let cancelled = false;
46
+
47
+ fetcherRef
48
+ .current()
49
+ .then(result => {
50
+ if (!cancelled) setData(result);
51
+ })
52
+ .catch(err => {
53
+ console.error(err);
54
+ if (!cancelled)
55
+ setError(err instanceof Error ? err.message : 'An error occurred');
56
+ })
57
+ .finally(() => {
58
+ if (!cancelled) setLoading(false);
59
+ });
60
+
61
+ return () => {
62
+ cancelled = true;
63
+ };
64
+ }, [generation]);
65
+
66
+ return { data, loading, error };
67
+ }