@salesforce/ui-bundle-template-app-react-template-b2e 3.0.0 → 3.1.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.
Files changed (18) hide show
  1. package/dist/CHANGELOG.md +17 -0
  2. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/alerts/status-alert.tsx +11 -8
  3. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/input.tsx +1 -1
  4. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +2 -4
  5. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/__examples__/pages/AccountSearch.tsx +7 -15
  6. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/NumericRangeFilter.tsx +9 -5
  7. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/SearchFilter.tsx +5 -3
  8. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/TextFilter.tsx +5 -3
  9. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/hooks/useAsyncData.ts +11 -4
  10. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/hooks/useAsyncData.ts +67 -0
  11. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/pages/AccountObjectDetailPage.tsx +2 -4
  12. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/pages/AccountSearch.tsx +7 -15
  13. package/dist/package-lock.json +2 -2
  14. package/dist/package.json +1 -1
  15. package/dist/scripts/org-setup.config.json +0 -1
  16. package/dist/scripts/org-setup.mjs +528 -44
  17. package/package.json +1 -1
  18. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/hooks/useCachedAsyncData.ts +0 -188
package/dist/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [3.1.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v3.1.0...v3.1.1) (2026-05-12)
7
+
8
+ **Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
9
+
10
+
11
+
12
+
13
+
14
+ ## [3.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v3.0.0...v3.1.0) (2026-05-12)
15
+
16
+
17
+ ### Features
18
+
19
+ * post tdx changes @W-22390798 ([#500](https://github.com/salesforce-experience-platform-emu/webapps/issues/500)) ([b611d9e](https://github.com/salesforce-experience-platform-emu/webapps/commit/b611d9ecbadd07d052bd7d9a4f5659dec422bf22))
20
+
21
+
22
+
6
23
  ## [3.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v2.2.1...v3.0.0) (2026-05-11)
7
24
 
8
25
  **Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
@@ -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}
@@ -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<
@@ -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 { 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
  }
@@ -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
+ }
@@ -22,7 +22,7 @@ import {
22
22
  } from "../components/ui/collapsible";
23
23
  import { Separator } from "../components/ui/separator";
24
24
  import { Skeleton } from "../components/ui/skeleton";
25
- import { useCachedAsyncData } from "../features/object-search/hooks/useCachedAsyncData";
25
+ import { useAsyncData } from "../hooks/useAsyncData";
26
26
  import { ObjectBreadcrumb } from "../features/object-search/components/ObjectBreadcrumb";
27
27
 
28
28
  type AccountNode = NonNullable<
@@ -53,9 +53,7 @@ export default function AccountObjectDetail() {
53
53
  data: account,
54
54
  loading,
55
55
  error,
56
- } = useCachedAsyncData(() => fetchAccountDetail(recordId!), [recordId], {
57
- key: `account:${recordId}`,
58
- });
56
+ } = useAsyncData(() => fetchAccountDetail(recordId!), [recordId]);
59
57
 
60
58
  return (
61
59
  <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/account/accountSearchService";
9
- import { useCachedAsyncData } from "../features/object-search/hooks/useCachedAsyncData";
9
+ import { useAsyncData } from "../hooks/useAsyncData";
10
10
  import { fieldValue } from "../features/object-search/utils/fieldUtils";
11
11
  import { useObjectSearchParams } from "../features/object-search/hooks/useObjectSearchParams";
12
12
  import { Alert, AlertTitle, AlertDescription } from "../components/ui/alert";
@@ -40,8 +40,8 @@ import PaginationControls from "../features/object-search/components/PaginationC
40
40
  import type { PaginationConfig } from "../features/object-search/hooks/useObjectSearchParams";
41
41
 
42
42
  const PAGINATION_CONFIG: PaginationConfig = {
43
- defaultPageSize: 6,
44
- validPageSizes: [6, 12, 24, 48],
43
+ defaultPageSize: 7,
44
+ validPageSizes: [7, 14, 28, 42],
45
45
  };
46
46
 
47
47
  type AccountNode = NonNullable<
@@ -75,22 +75,15 @@ const ACCOUNT_SORT_CONFIGS: SortFieldConfig<keyof Account_OrderBy>[] = [
75
75
 
76
76
  export default function AccountSearch() {
77
77
  const [filtersOpen, setFiltersOpen] = useState(true);
78
- const { data: industryOptions } = useCachedAsyncData(fetchDistinctIndustries, [], {
79
- key: "distinctIndustries",
80
- ttl: 300_000,
81
- });
82
- const { data: typeOptions } = useCachedAsyncData(fetchDistinctTypes, [], {
83
- key: "distinctTypes",
84
- ttl: 300_000,
85
- });
78
+ const { data: industryOptions } = useAsyncData(fetchDistinctIndustries, []);
79
+ const { data: typeOptions } = useAsyncData(fetchDistinctTypes, []);
86
80
 
87
81
  const { filters, sort, query, pagination, resetAll } = useObjectSearchParams<
88
82
  Account_Filter,
89
83
  Account_OrderBy
90
84
  >(FILTER_CONFIGS, ACCOUNT_SORT_CONFIGS, PAGINATION_CONFIG);
91
85
 
92
- const searchKey = `accounts:${JSON.stringify({ where: query.where, orderBy: query.orderBy, first: pagination.pageSize, after: pagination.afterCursor })}`;
93
- const { data, loading, error } = useCachedAsyncData(
86
+ const { data, loading, error } = useAsyncData(
94
87
  () =>
95
88
  searchAccounts({
96
89
  where: query.where,
@@ -99,7 +92,6 @@ export default function AccountSearch() {
99
92
  after: pagination.afterCursor,
100
93
  }),
101
94
  [query.where, query.orderBy, pagination.pageSize, pagination.afterCursor],
102
- { key: searchKey },
103
95
  );
104
96
 
105
97
  const pageInfo = data?.pageInfo;
@@ -189,7 +181,7 @@ export default function AccountSearch() {
189
181
  <ActiveFilters filters={filters.active} onRemove={filters.remove} />
190
182
  </div>
191
183
 
192
- <div className="min-h-112">
184
+ <div className="min-h-132">
193
185
  {/* Loading state */}
194
186
  {loading && (
195
187
  <>
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
9
- "version": "3.0.0",
9
+ "version": "3.1.1",
10
10
  "license": "SEE LICENSE IN LICENSE.txt",
11
11
  "devDependencies": {
12
12
  "@lwc/eslint-plugin-lwc": "^3.3.0",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "description": "Base SFDX project template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "publishConfig": {
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "permsetAssignments": {
3
- "defaultAssignee": "currentUser",
4
3
  "assignments": {}
5
4
  }
6
5
  }