@salesforce/ui-bundle-template-app-react-template-b2x 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.
- package/dist/CHANGELOG.md +17 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/alerts/status-alert.tsx +11 -8
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/input.tsx +1 -1
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/api/userProfileApi.ts +2 -1
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/authenticationConfig.ts +9 -9
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/context/AuthContext.tsx +21 -4
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/forms/auth-form.tsx +15 -1
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/hooks/form.tsx +1 -1
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layouts/privateRouteLayout.tsx +2 -11
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/ChangePassword.tsx +20 -4
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/ForgotPassword.tsx +19 -4
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/Login.tsx +19 -4
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/Profile.tsx +80 -43
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/Register.tsx +15 -4
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/ResetPassword.tsx +19 -4
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/utils/helpers.ts +15 -52
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +2 -4
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/AccountSearch.tsx +7 -15
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/NumericRangeFilter.tsx +9 -5
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/SearchFilter.tsx +5 -3
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/TextFilter.tsx +5 -3
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useAsyncData.ts +11 -4
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/hooks/useAsyncData.ts +67 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/AccountObjectDetailPage.tsx +2 -4
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/AccountSearch.tsx +7 -15
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/routes.tsx +19 -25
- package/dist/package-lock.json +2 -2
- package/dist/package.json +1 -1
- package/dist/scripts/org-setup.config.json +0 -1
- package/dist/scripts/org-setup.mjs +528 -44
- package/package.json +1 -1
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layout/card-skeleton.tsx +0 -38
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layouts/authenticationRouteLayout.tsx +0 -21
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useCachedAsyncData.ts +0 -188
|
@@ -8,13 +8,13 @@ import { useAppForm } from "../hooks/form";
|
|
|
8
8
|
import { createDataSDK } from "@salesforce/platform-sdk-data";
|
|
9
9
|
import { ROUTES, AUTH_PLACEHOLDERS } from "../authenticationConfig";
|
|
10
10
|
import { newPasswordSchema } from "../authHelpers";
|
|
11
|
-
import {
|
|
11
|
+
import { ApiError, handleApiResponse } from "../utils/helpers";
|
|
12
12
|
|
|
13
13
|
export default function ResetPassword() {
|
|
14
14
|
const [searchParams] = useSearchParams();
|
|
15
15
|
const token = searchParams.get("token");
|
|
16
16
|
const [success, setSuccess] = useState(false);
|
|
17
|
-
const [submitError, setSubmitError] = useState<
|
|
17
|
+
const [submitError, setSubmitError] = useState<React.ReactNode>(null);
|
|
18
18
|
|
|
19
19
|
const form = useAppForm({
|
|
20
20
|
defaultValues: { newPassword: "", confirmPassword: "" },
|
|
@@ -34,12 +34,27 @@ export default function ResetPassword() {
|
|
|
34
34
|
Accept: "application/json",
|
|
35
35
|
},
|
|
36
36
|
});
|
|
37
|
-
await handleApiResponse(response
|
|
37
|
+
await handleApiResponse(response);
|
|
38
38
|
setSuccess(true);
|
|
39
39
|
// Scroll to top of page after successful submission so user sees it
|
|
40
40
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
41
41
|
} catch (err) {
|
|
42
|
-
|
|
42
|
+
console.error("Password reset failed", err);
|
|
43
|
+
if (err instanceof ApiError) {
|
|
44
|
+
setSubmitError(
|
|
45
|
+
err.errors.length === 1 ? (
|
|
46
|
+
err.errors[0]
|
|
47
|
+
) : (
|
|
48
|
+
<ul>
|
|
49
|
+
{err.errors.map((e, i) => (
|
|
50
|
+
<li key={i}>{e}</li>
|
|
51
|
+
))}
|
|
52
|
+
</ul>
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
setSubmitError("Password reset failed");
|
|
57
|
+
}
|
|
43
58
|
}
|
|
44
59
|
},
|
|
45
60
|
onSubmitInvalid: () => {},
|
|
@@ -1,29 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* @param err - The error object (unknown type)
|
|
6
|
-
* @param fallback - Fallback message if error doesn't have a message property
|
|
7
|
-
* @returns The error message string
|
|
2
|
+
* Error thrown when the API returns a non-OK response with structured error messages.
|
|
3
|
+
* The `errors` array contains user-facing messages that are safe to display —
|
|
4
|
+
* backend Apex classes guarantee that system exceptions are never exposed.
|
|
8
5
|
*/
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
export class ApiError extends Error {
|
|
7
|
+
errors: string[];
|
|
8
|
+
constructor(errors: string[]) {
|
|
9
|
+
super(errors[0]);
|
|
10
|
+
this.name = "ApiError";
|
|
11
|
+
this.errors = errors;
|
|
15
12
|
}
|
|
16
|
-
return fallback;
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
/**
|
|
20
16
|
* [Dev Note] Helper to parse the fetch Response.
|
|
21
17
|
* It handles the distinction between success (JSON) and failure (throwing Error).
|
|
22
18
|
*/
|
|
23
|
-
export async function handleApiResponse<T = unknown>(
|
|
24
|
-
response: Response,
|
|
25
|
-
fallbackError: string,
|
|
26
|
-
): Promise<T> {
|
|
19
|
+
export async function handleApiResponse<T = unknown>(response: Response): Promise<T> {
|
|
27
20
|
// 1. Robustness: Handle 204 No Content gracefully
|
|
28
21
|
if (response.status === 204) {
|
|
29
22
|
return {} as T;
|
|
@@ -41,9 +34,11 @@ export async function handleApiResponse<T = unknown>(
|
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
if (!response.ok) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
console.error("API request failed", data);
|
|
38
|
+
if (data?.errors?.length) {
|
|
39
|
+
throw new ApiError(data.errors);
|
|
40
|
+
}
|
|
41
|
+
throw new Error("An unexpected error occurred");
|
|
47
42
|
}
|
|
48
43
|
|
|
49
44
|
return data as T;
|
|
@@ -87,35 +82,3 @@ export function flattenGraphQLRecord<T>(
|
|
|
87
82
|
]),
|
|
88
83
|
) as T;
|
|
89
84
|
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* [Dev Note] Salesforce APIs may return errors as an array or a single object.
|
|
93
|
-
* This helper standardizes the extraction of the error message string.
|
|
94
|
-
*
|
|
95
|
-
* @param data - The response data.
|
|
96
|
-
* @param fallbackError - Fallback error message if response doesn't have a message property
|
|
97
|
-
* @returns The error message string
|
|
98
|
-
*/
|
|
99
|
-
function parseApiResponseError(
|
|
100
|
-
data: any,
|
|
101
|
-
fallbackError: string = "An unknown error occurred",
|
|
102
|
-
): string {
|
|
103
|
-
if (data?.message) {
|
|
104
|
-
return data.message;
|
|
105
|
-
}
|
|
106
|
-
if (data?.error) {
|
|
107
|
-
return data.error;
|
|
108
|
-
}
|
|
109
|
-
if (data?.errors && Array.isArray(data.errors) && data.errors.length > 0) {
|
|
110
|
-
return data.errors.join(" ") || fallbackError;
|
|
111
|
-
}
|
|
112
|
-
if (Array.isArray(data) && data.length > 0) {
|
|
113
|
-
return (
|
|
114
|
-
data
|
|
115
|
-
.map((e) => e?.message)
|
|
116
|
-
.filter(Boolean)
|
|
117
|
-
.join(" ") || fallbackError
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
return fallbackError;
|
|
121
|
-
}
|
|
@@ -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 {
|
|
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
|
-
} =
|
|
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 {
|
|
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:
|
|
46
|
-
validPageSizes: [
|
|
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 } =
|
|
81
|
-
|
|
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
|
|
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-
|
|
191
|
+
<div className="min-h-132">
|
|
200
192
|
{/* Loading state */}
|
|
201
193
|
{loading && (
|
|
202
194
|
<>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
70
|
+
const [prevExternalMin, setPrevExternalMin] = useState(externalMin);
|
|
71
|
+
const [prevExternalMax, setPrevExternalMax] = useState(externalMax);
|
|
72
|
+
if (prevExternalMin !== externalMin) {
|
|
73
|
+
setPrevExternalMin(externalMin);
|
|
71
74
|
setLocalMin(externalMin);
|
|
72
|
-
}
|
|
73
|
-
|
|
75
|
+
}
|
|
76
|
+
if (prevExternalMax !== externalMax) {
|
|
77
|
+
setPrevExternalMax(externalMax);
|
|
74
78
|
setLocalMax(externalMax);
|
|
75
|
-
}
|
|
79
|
+
}
|
|
76
80
|
|
|
77
81
|
const isOutOfBounds = (v: string) => {
|
|
78
82
|
if (v === "") return false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
25
|
+
const [prevExternalValue, setPrevExternalValue] = useState(externalValue);
|
|
26
|
+
if (prevExternalValue !== externalValue) {
|
|
27
|
+
setPrevExternalValue(externalValue);
|
|
26
28
|
setLocalValue(externalValue);
|
|
27
|
-
}
|
|
29
|
+
}
|
|
28
30
|
|
|
29
31
|
const debouncedOnChange = useDebouncedCallback((v: string) => {
|
|
30
32
|
if (v) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
65
|
+
const [prevExternalValue, setPrevExternalValue] = useState(externalValue);
|
|
66
|
+
if (prevExternalValue !== externalValue) {
|
|
67
|
+
setPrevExternalValue(externalValue);
|
|
66
68
|
setLocalValue(externalValue);
|
|
67
|
-
}
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/AccountObjectDetailPage.tsx
CHANGED
|
@@ -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 {
|
|
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
|
-
} =
|
|
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 {
|
|
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:
|
|
44
|
-
validPageSizes: [
|
|
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 } =
|
|
79
|
-
|
|
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
|
|
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-
|
|
184
|
+
<div className="min-h-132">
|
|
193
185
|
{/* Loading state */}
|
|
194
186
|
{loading && (
|
|
195
187
|
<>
|
|
@@ -7,7 +7,6 @@ import ForgotPassword from "./features/authentication/pages/ForgotPassword";
|
|
|
7
7
|
import ResetPassword from "./features/authentication/pages/ResetPassword";
|
|
8
8
|
import Profile from "./features/authentication/pages/Profile";
|
|
9
9
|
import ChangePassword from "./features/authentication/pages/ChangePassword";
|
|
10
|
-
import AuthenticationRoute from "./features/authentication/layouts/authenticationRouteLayout";
|
|
11
10
|
import PrivateRoute from "./features/authentication/layouts/privateRouteLayout";
|
|
12
11
|
import { ROUTES } from "./features/authentication/authenticationConfig";
|
|
13
12
|
import AccountSearch from "./pages/AccountSearch";
|
|
@@ -29,32 +28,27 @@ export const routes: RouteObject[] = [
|
|
|
29
28
|
element: <NotFound />
|
|
30
29
|
},
|
|
31
30
|
{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
path: ROUTES.RESET_PASSWORD.PATH,
|
|
51
|
-
element: <ResetPassword />,
|
|
52
|
-
handle: { showInNavigation: false, title: ROUTES.RESET_PASSWORD.TITLE }
|
|
53
|
-
}
|
|
54
|
-
]
|
|
31
|
+
path: ROUTES.LOGIN.PATH,
|
|
32
|
+
element: <Login />,
|
|
33
|
+
handle: { showInNavigation: false, label: "Login", title: ROUTES.LOGIN.TITLE }
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
path: ROUTES.REGISTER.PATH,
|
|
37
|
+
element: <Register />,
|
|
38
|
+
handle: { showInNavigation: false, title: ROUTES.REGISTER.TITLE }
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: ROUTES.FORGOT_PASSWORD.PATH,
|
|
42
|
+
element: <ForgotPassword />,
|
|
43
|
+
handle: { showInNavigation: false, title: ROUTES.FORGOT_PASSWORD.TITLE }
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
path: ROUTES.RESET_PASSWORD.PATH,
|
|
47
|
+
element: <ResetPassword />,
|
|
48
|
+
handle: { showInNavigation: false, title: ROUTES.RESET_PASSWORD.TITLE }
|
|
55
49
|
},
|
|
56
50
|
{
|
|
57
|
-
element: <PrivateRoute
|
|
51
|
+
element: <PrivateRoute />,
|
|
58
52
|
children: [
|
|
59
53
|
{
|
|
60
54
|
path: ROUTES.PROFILE.PATH,
|
package/dist/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
|
|
3
|
-
"version": "3.
|
|
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.
|
|
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