@vtex/faststore-plugin-buyer-portal 1.1.106 → 1.1.108

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 (37) hide show
  1. package/package.json +1 -1
  2. package/src/features/custom-fields/layouts/CustomFieldsLayout/CustomFieldsLayout.tsx +1 -0
  3. package/src/features/shared/clients/Auth.ts +25 -0
  4. package/src/features/shared/clients/Client.ts +11 -1
  5. package/src/features/shared/components/withErrorBoundary/withErrorBoundary.tsx +7 -6
  6. package/src/features/shared/services/index.ts +4 -0
  7. package/src/features/shared/services/validate-access.service.ts +11 -0
  8. package/src/features/shared/types/AuthRouteProps.ts +5 -0
  9. package/src/features/shared/types/index.ts +1 -0
  10. package/src/features/shared/utils/constants.ts +4 -4
  11. package/src/features/shared/utils/index.ts +5 -0
  12. package/src/features/shared/utils/withAuth.tsx +31 -0
  13. package/src/features/shared/utils/withAuthLoader.ts +47 -0
  14. package/src/features/shared/utils/withAuthProvider.tsx +38 -0
  15. package/src/features/shared/utils/withBuyerPortal.tsx +24 -0
  16. package/src/features/shared/utils/withLoaderErrorBoundary.ts +79 -71
  17. package/src/features/shared/utils/withProviders.tsx +35 -0
  18. package/src/pages/address-details.tsx +95 -91
  19. package/src/pages/addresses.tsx +63 -63
  20. package/src/pages/budgets-details.tsx +44 -43
  21. package/src/pages/budgets.tsx +58 -56
  22. package/src/pages/buying-policies.tsx +84 -78
  23. package/src/pages/buying-policy-details.tsx +43 -44
  24. package/src/pages/collections.tsx +59 -60
  25. package/src/pages/cost-centers.tsx +48 -51
  26. package/src/pages/credit-cards.tsx +44 -49
  27. package/src/pages/home.tsx +28 -23
  28. package/src/pages/org-unit-details.tsx +39 -36
  29. package/src/pages/org-units.tsx +86 -90
  30. package/src/pages/payment-methods.tsx +41 -42
  31. package/src/pages/po-numbers.tsx +43 -46
  32. package/src/pages/profile.tsx +54 -52
  33. package/src/pages/releases.tsx +43 -41
  34. package/src/pages/role-details.tsx +57 -60
  35. package/src/pages/roles.tsx +42 -39
  36. package/src/pages/user-details.tsx +58 -58
  37. package/src/pages/users.tsx +83 -80
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.1.106",
3
+ "version": "1.1.108",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -43,6 +43,7 @@ export const CustomFieldsLayout = ({
43
43
  currentContract: contract,
44
44
  clientContext,
45
45
  } = useBuyerPortal();
46
+
46
47
  const searchParams = useSearchParams();
47
48
  const [currentSelectedContent, setCurrentSelectedContent] =
48
49
  useState<CustomFieldData>();
@@ -0,0 +1,25 @@
1
+ import { getApiUrl } from "../../shared/utils";
2
+
3
+ import { Client } from "./Client";
4
+
5
+ type AuthContextProps = {
6
+ cookie: string;
7
+ };
8
+
9
+ export default class AuthClient extends Client {
10
+ constructor() {
11
+ super(getApiUrl());
12
+ }
13
+
14
+ validateAccess(context: AuthContextProps) {
15
+ return this.get<boolean>("granted", {
16
+ headers: {
17
+ Cookie: context.cookie,
18
+ "Content-Type": "text/plain",
19
+ },
20
+ });
21
+ }
22
+ }
23
+
24
+ const authClient = new AuthClient();
25
+ export { authClient };
@@ -1,8 +1,8 @@
1
- import { toQueryParams } from "../utils";
2
1
  import {
3
2
  getAuthFromCookie,
4
3
  getCookieWithoutSessionObjects,
5
4
  } from "../utils/cookie";
5
+ import { toQueryParams } from "../utils/toQueryParams";
6
6
 
7
7
  interface RequestConfig<Input = unknown> {
8
8
  url: string;
@@ -87,6 +87,7 @@ class Client {
87
87
  ? getCookieWithoutSessionObjects(headers.Cookie ?? "")
88
88
  : {}),
89
89
  ...(headers.Cookie ? getAuthFromCookie(headers.Cookie ?? "") : {}),
90
+ ...headers,
90
91
  },
91
92
  ...(cache ? { cache, next: { revalidate: 300 } } : {}),
92
93
  };
@@ -106,6 +107,15 @@ class Client {
106
107
  responseData = await response.json();
107
108
  }
108
109
 
110
+ if (contentType?.includes("text/plain")) {
111
+ const text = await response.text();
112
+ try {
113
+ responseData = JSON.parse(text);
114
+ } catch {
115
+ responseData = text as unknown as Return;
116
+ }
117
+ }
118
+
109
119
  if (!response.ok) {
110
120
  console.error(`Request to ${url} failed:`, responseData);
111
121
  console.error(response.status, response.statusText);
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React from "react";
4
4
 
5
- import { ErrorBoundary } from "../ErrorBoundary/ErrorBoundary";
5
+ // import { ErrorBoundary } from "../ErrorBoundary/ErrorBoundary";
6
6
 
7
7
  type WithErrorBoundaryOptions = {
8
8
  fallback?: React.ReactNode;
@@ -15,15 +15,16 @@ type WithErrorBoundaryOptions = {
15
15
 
16
16
  export function withErrorBoundary<P extends Record<string, unknown>>(
17
17
  WrappedComponent: React.ComponentType<P>,
18
- options: WithErrorBoundaryOptions = {}
18
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
19
+ _options: WithErrorBoundaryOptions = {}
19
20
  ) {
20
- const { onError, tags } = options;
21
+ // const { onError, tags } = options;
21
22
 
22
23
  const ComponentWithErrorBoundary = (props: P) => {
23
24
  return (
24
- <ErrorBoundary onError={onError} tags={tags}>
25
- <WrappedComponent {...props} />
26
- </ErrorBoundary>
25
+ // <ErrorBoundary onError={onError} tags={tags}>
26
+ <WrappedComponent {...props} />
27
+ // </ErrorBoundary>
27
28
  );
28
29
  };
29
30
 
@@ -6,3 +6,7 @@ export {
6
6
  removeFromScopeService,
7
7
  type RemoveFromScopeServiceProps,
8
8
  } from "./remove-from-scope.service";
9
+ export {
10
+ validateAccessService,
11
+ type ValidateAccessServiceProps,
12
+ } from "./validate-access.service";
@@ -0,0 +1,11 @@
1
+ import { authClient } from "../clients/Auth";
2
+
3
+ export type ValidateAccessServiceProps = {
4
+ cookie: string;
5
+ };
6
+
7
+ export const validateAccessService = async ({
8
+ cookie,
9
+ }: ValidateAccessServiceProps) => {
10
+ return authClient.validateAccess({ cookie });
11
+ };
@@ -0,0 +1,5 @@
1
+ import { ClientContext } from "../utils";
2
+
3
+ export type AuthRouteProps<T> =
4
+ | { authorized: true; data: T; clientContext: ClientContext }
5
+ | { authorized: false };
@@ -7,3 +7,4 @@ export type {
7
7
  PaymentMethodResponse,
8
8
  PaymentMethodsReqCommonParams,
9
9
  } from "./PaymentMethodsClientTypes";
10
+ export { type AuthRouteProps } from "./AuthRouteProps";
@@ -1,10 +1,10 @@
1
1
  export const AUT_COOKIE_KEY = "VtexIdclientAutCookie";
2
2
  // PROD URL
3
- export const API_URL = (checkoutUrl: string, operation?: string) =>
4
- `${checkoutUrl}/_v/store-front/${operation}`;
3
+ // export const API_URL = (checkoutUrl: string, operation?: string) =>
4
+ // `${checkoutUrl}/_v/store-front/${operation}`;
5
5
  // DEV URL - CHANGE BEFORE MERGE
6
- // export const API_URL = (checkoutUrl?: string, operation?: string) =>
7
- // `https://everton--b2bfaststoredev.myvtex.com/_v/store-front/${operation}`;
6
+ export const API_URL = (checkoutUrl?: string, operation?: string) =>
7
+ `https://permission--b2bfaststoredev.myvtex.com/_v/store-front/${operation}`;
8
8
 
9
9
  export const DEBOUNCE_TIMEOUT = 500;
10
10
 
@@ -37,3 +37,8 @@ export { isPluginError } from "./isPluginError";
37
37
  export { serializeLoaderData } from "./serializeLoaderData";
38
38
  export { withLoaderErrorBoundary } from "./withLoaderErrorBoundary";
39
39
  export { withClientErrorBoundary } from "./withClientErrorBoundary";
40
+ export { withAuthProvider } from "./withAuthProvider";
41
+ export { withAuth } from "./withAuth";
42
+ export { withBuyerPortal } from "./withBuyerPortal";
43
+ export { withProviders } from "./withProviders";
44
+ export { withAuthLoader } from "./withAuthLoader";
@@ -0,0 +1,31 @@
1
+ import { useEffect, type ComponentType } from "react";
2
+
3
+ import { useRouter } from "next/router";
4
+
5
+ import type { AuthRouteProps } from "../types";
6
+
7
+ /**
8
+ * HOC that checks only for authorization
9
+ */
10
+ export const withAuth = <T extends Record<string, unknown>>(
11
+ Component: ComponentType<T>
12
+ ) => {
13
+ return function AuthenticatedComponent(props: AuthRouteProps<T>) {
14
+ const router = useRouter();
15
+
16
+ useEffect(() => {
17
+ // If not authorized, reload the page
18
+ if (!props?.authorized) {
19
+ router.reload();
20
+ }
21
+ }, [props?.authorized, router]);
22
+
23
+ // If not authorized, render nothing
24
+ if (!props?.authorized) {
25
+ return null;
26
+ }
27
+
28
+ // If authorized, render the component with the data
29
+ return <Component {...(props.data as T)} />;
30
+ };
31
+ };
@@ -0,0 +1,47 @@
1
+ import { validateAccessService } from "../services";
2
+
3
+ import { ClientContext, getClientContext } from "./getClientContext";
4
+
5
+ import type { AuthRouteProps } from "../types";
6
+ import type { LoaderData } from "../types";
7
+
8
+ /**
9
+ * Utility function to encapsulate access validation in loaders.
10
+ * Similar to withAuthProvider but for server-side usage.
11
+ */
12
+ export const withAuthLoader = async <T>(
13
+ data: LoaderData,
14
+ dataLoader: (clientContext: ClientContext) => Promise<T>
15
+ ): Promise<AuthRouteProps<T>> => {
16
+ try {
17
+ const { cookie, userId, ...clientContext } = await getClientContext(data);
18
+
19
+ const hasAccess = await validateAccessService({ cookie });
20
+
21
+ if (!hasAccess) {
22
+ data.res?.writeHead(302, { Location: "/" });
23
+ data.res?.end();
24
+
25
+ return {
26
+ authorized: false,
27
+ };
28
+ }
29
+
30
+ const pageData = await dataLoader({ cookie, userId, ...clientContext });
31
+
32
+ return {
33
+ authorized: true,
34
+ data: pageData,
35
+ clientContext: { cookie, userId, ...clientContext },
36
+ };
37
+ } catch (error) {
38
+ console.error("Auth validation failed:", error);
39
+
40
+ data.res?.writeHead(302, { Location: "/" });
41
+ data.res?.end();
42
+
43
+ return {
44
+ authorized: false,
45
+ };
46
+ }
47
+ };
@@ -0,0 +1,38 @@
1
+ import { useEffect, type ComponentType } from "react";
2
+
3
+ import { useRouter } from "next/router";
4
+
5
+ import { BuyerPortalProvider } from "../components";
6
+
7
+ import type { AuthRouteProps } from "../types";
8
+ import type { ClientContext } from "./getClientContext";
9
+
10
+ /**
11
+ * HOC que encapsula lógica de autenticação e provider
12
+ */
13
+ export const withAuthProvider = <T,>(
14
+ Component: ComponentType<T & { clientContext: ClientContext }>
15
+ ) => {
16
+ return function WrappedPage(props: AuthRouteProps<T>) {
17
+ const router = useRouter();
18
+
19
+ useEffect(() => {
20
+ // Se não está autorizado, recarrega a página
21
+ if (!props?.authorized) {
22
+ router.reload();
23
+ }
24
+ }, [props?.authorized, router]);
25
+
26
+ // Se não está autorizado, não renderiza nada
27
+ if (!props?.authorized) {
28
+ return null;
29
+ }
30
+
31
+ // Se autorizado, renderiza o componente envolvido no provider
32
+ return (
33
+ <BuyerPortalProvider clientContext={props?.clientContext}>
34
+ <Component {...props.data} clientContext={props?.clientContext} />
35
+ </BuyerPortalProvider>
36
+ );
37
+ };
38
+ };
@@ -0,0 +1,24 @@
1
+ import { type ComponentType } from "react";
2
+
3
+ import { BuyerPortalProvider } from "../components";
4
+
5
+ import type { ClientContext } from "./getClientContext";
6
+
7
+ /**
8
+ * HOC que adiciona BuyerPortalProvider e context
9
+ */
10
+ export const withBuyerPortal = <T extends Record<string, unknown>>(
11
+ Component: ComponentType<T>
12
+ ) => {
13
+ return function WrappedWithProvider(
14
+ props: T & { context: { clientContext: ClientContext } }
15
+ ) {
16
+ const { context, ...componentProps } = props;
17
+
18
+ return (
19
+ <BuyerPortalProvider {...context}>
20
+ <Component {...(componentProps as unknown as T)} />
21
+ </BuyerPortalProvider>
22
+ );
23
+ };
24
+ };
@@ -1,4 +1,4 @@
1
- import { getClientContext } from "./getClientContext";
1
+ // import { getClientContext } from "./getClientContext";
2
2
  import { serializeLoaderData } from "./serializeLoaderData";
3
3
 
4
4
  import type { LoaderData } from "../types";
@@ -11,77 +11,85 @@ interface WithLoaderErrorBoundaryOptions {
11
11
 
12
12
  export function withLoaderErrorBoundary<TQuery, TReturn>(
13
13
  loaderFn: (data: LoaderData<TQuery>) => Promise<TReturn>,
14
- options: WithLoaderErrorBoundaryOptions = {}
14
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
15
+ _options: WithLoaderErrorBoundaryOptions = {}
15
16
  ) {
16
- const { componentName, onError, redirectToError = true } = options;
17
-
17
+ // Bypass: Just call the loader function and serialize the result
18
18
  return async (data: LoaderData<TQuery>): Promise<TReturn> => {
19
- try {
20
- const result = await loaderFn(data);
21
- return serializeLoaderData(result);
22
- } catch (error) {
23
- console.error(`[${componentName || "Loader"}] Error:`, {
24
- message: (error as Error).message,
25
- query: data.query,
26
- });
27
-
28
- if (onError) {
29
- onError(error as Error, data.query);
30
- }
31
-
32
- if (redirectToError) {
33
- let clientContext;
34
- try {
35
- clientContext = await getClientContext(data);
36
- } catch (contextError) {
37
- console.error(
38
- `[${componentName || "Loader"}] Failed to get client context:`,
39
- contextError
40
- );
41
- clientContext = {
42
- cookie: "",
43
- customerId: "",
44
- userId: "",
45
- vtexIdclientAutCookie: "",
46
- };
47
- }
48
-
49
- // Serialize query to ensure it's JSON-safe (use null instead of undefined)
50
- const serializedQuery = data.query
51
- ? JSON.parse(JSON.stringify(data.query))
52
- : null;
53
-
54
- const errorResponse = {
55
- error: {
56
- error: {
57
- message: (error as Error).message,
58
- name: (error as Error).name,
59
- stack: (error as Error).stack || null,
60
- query: serializedQuery,
61
- },
62
- tags: {
63
- component: componentName || "Unknown",
64
- errorType: "loader_error",
65
- },
66
- extra: {
67
- query: serializedQuery,
68
- },
69
- },
70
- hasError: true,
71
- context: {
72
- clientContext,
73
- currentOrgUnit: null,
74
- currentContract: null,
75
- currentUser: null,
76
- },
77
- data: null,
78
- };
79
-
80
- // Ensure error response is also JSON-serializable
81
- return serializeLoaderData(errorResponse) as TReturn;
82
- }
83
-
84
- throw error;
85
- }
19
+ const result = await loaderFn(data);
20
+ return serializeLoaderData(result);
86
21
  };
22
+
23
+ // Old error handling code - commented out for bypass
24
+ // const { componentName, onError, redirectToError = true } = options;
25
+ //
26
+ // return async (data: LoaderData<TQuery>): Promise<TReturn> => {
27
+ // try {
28
+ // const result = await loaderFn(data);
29
+ // return serializeLoaderData(result);
30
+ // } catch (error) {
31
+ // console.error(`[${componentName || "Loader"}] Error:`, {
32
+ // message: (error as Error).message,
33
+ // query: data.query,
34
+ // });
35
+ //
36
+ // if (onError) {
37
+ // onError(error as Error, data.query);
38
+ // }
39
+ //
40
+ // if (redirectToError) {
41
+ // let clientContext;
42
+ // try {
43
+ // clientContext = await getClientContext(data);
44
+ // } catch (contextError) {
45
+ // console.error(
46
+ // `[${componentName || "Loader"}] Failed to get client context:`,
47
+ // contextError
48
+ // );
49
+ // clientContext = {
50
+ // cookie: "",
51
+ // customerId: "",
52
+ // userId: "",
53
+ // vtexIdclientAutCookie: "",
54
+ // };
55
+ // }
56
+ //
57
+ // // Serialize query to ensure it's JSON-safe (use null instead of undefined)
58
+ // const serializedQuery = data.query
59
+ // ? JSON.parse(JSON.stringify(data.query))
60
+ // : null;
61
+ //
62
+ // const errorResponse = {
63
+ // error: {
64
+ // error: {
65
+ // message: (error as Error).message,
66
+ // name: (error as Error).name,
67
+ // stack: (error as Error).stack || null,
68
+ // query: serializedQuery,
69
+ // },
70
+ // tags: {
71
+ // component: componentName || "Unknown",
72
+ // errorType: "loader_error",
73
+ // },
74
+ // extra: {
75
+ // query: serializedQuery,
76
+ // },
77
+ // },
78
+ // hasError: true,
79
+ // context: {
80
+ // clientContext,
81
+ // currentOrgUnit: null,
82
+ // currentContract: null,
83
+ // currentUser: null,
84
+ // },
85
+ // data: null,
86
+ // };
87
+ //
88
+ // // Ensure error response is also JSON-serializable
89
+ // return serializeLoaderData(errorResponse) as TReturn;
90
+ // }
91
+ //
92
+ // throw error;
93
+ // }
94
+ // };
87
95
  }
@@ -0,0 +1,35 @@
1
+ import { type ComponentType } from "react";
2
+
3
+ import { withAuth } from "./withAuth";
4
+ import { withBuyerPortal } from "./withBuyerPortal";
5
+
6
+ /**
7
+ * Type for HOCs that can be applied in sequence
8
+ * Using any for flexibility in HOC composition
9
+ */
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ type ProviderHOC = (Component: ComponentType<any>) => ComponentType<any>;
12
+
13
+ /**
14
+ * Default project providers applied to all pages.
15
+ * Order: right to left (like pipe/compose)
16
+ */
17
+ const projectProviders: ProviderHOC[] = [withAuth, withBuyerPortal];
18
+
19
+ /**
20
+ * HOC that applies all default project providers.
21
+ * To add new providers, just include them in the projectProviders array.
22
+ *
23
+ * @example
24
+ * export default withProviders(MyComponent);
25
+ */
26
+ export const withProviders = <T extends Record<string, unknown>>(
27
+ Component: ComponentType<T>
28
+ ): ComponentType<T> => {
29
+ // Apply providers in sequence (right to left)
30
+ return projectProviders.reduceRight(
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ (WrappedComponent, provider) => provider(WrappedComponent) as any,
33
+ Component
34
+ ) as ComponentType<T>;
35
+ };