@vtex/faststore-plugin-buyer-portal 1.0.40 → 1.0.41

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 (31) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/package.json +1 -1
  3. package/plugin.config.js +10 -0
  4. package/src/features/addresses/components/CreateAddressDrawer/create-address-drawer.scss +1 -1
  5. package/src/features/addresses/layouts/AddressDetailsLayout/address-details-layout.scss +2 -2
  6. package/src/features/buying-policies/components/BuyingPolicyDropdownMenu/BuyingPolicyDropdownMenu.tsx +23 -0
  7. package/src/features/buying-policies/components/BuyingPolicyDropdownMenu/buying-policy-dropdown-menu.scss +1 -0
  8. package/src/features/buying-policies/components/index.ts +1 -0
  9. package/src/features/buying-policies/layouts/BuyingPoliciesLayout/BuyingPoliciesLayout.tsx +62 -0
  10. package/src/features/buying-policies/layouts/BuyingPoliciesLayout/buying-policies-layout.scss +24 -0
  11. package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout.tsx +83 -0
  12. package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/buying-policy-details-layout.scss +74 -0
  13. package/src/features/buying-policies/layouts/index.ts +1 -0
  14. package/src/features/buying-policies/mocks/buying-policy-data.ts +145 -0
  15. package/src/features/buying-policies/mocks/index.ts +1 -0
  16. package/src/features/buying-policies/types/BuyingPolicies.ts +10 -0
  17. package/src/features/buying-policies/types/index.ts +1 -0
  18. package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +9 -7
  19. package/src/features/shared/components/ListLine/ListLine.tsx +52 -0
  20. package/src/features/shared/components/ListLine/list-line.scss +49 -0
  21. package/src/features/shared/components/index.ts +1 -0
  22. package/src/features/shared/layouts/BaseTabsLayout/base-tabs-layout.scss +1 -0
  23. package/src/features/shared/layouts/FinanceTabsLayout/FinanceTabsLayout.tsx +40 -0
  24. package/src/features/shared/layouts/FinanceTabsLayout/finance-tabs-layout.scss +4 -0
  25. package/src/features/shared/layouts/index.ts +4 -0
  26. package/src/features/shared/utils/buyerPortalRoutes.ts +12 -0
  27. package/src/features/shared/utils/getFinanceSettingsLinks.ts +15 -0
  28. package/src/features/shared/utils/index.ts +1 -0
  29. package/src/pages/buying-policies.tsx +81 -0
  30. package/src/pages/buying-policy-details.tsx +72 -0
  31. package/src/themes/layouts.scss +4 -0
package/CHANGELOG.md CHANGED
@@ -9,5 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
+ - Add Buying Policies Page
13
+ - Add Buying Policy Details Page
14
+
15
+ ### Added
16
+
12
17
  - Add CHANGELOG file
13
18
  - Add README file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.0.40",
3
+ "version": "1.0.41",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "dependencies": {
package/plugin.config.js CHANGED
@@ -49,6 +49,16 @@ module.exports = {
49
49
  // appLayout: false,
50
50
  // },
51
51
 
52
+ "buying-policies": {
53
+ path: "/buyer-portal/buying-policies/[orgUnitId]/[contractId]",
54
+ appLayout: false,
55
+ },
56
+
57
+ "buying-policy-details": {
58
+ path: "/buyer-portal/buying-policy/[orgUnitId]/[contractId]/[buyingPolicyId]",
59
+ appLayout: false,
60
+ },
61
+
52
62
  users: {
53
63
  path: "/buyer-portal/users/[orgUnitId]",
54
64
  appLayout: false,
@@ -29,7 +29,7 @@
29
29
  margin-top: 1rem;
30
30
  width: fit-content;
31
31
  border-radius: var(--fs-border-radius-pill);
32
- border: 1px;
32
+ border: var(--fs-border-width);
33
33
  padding: var(--fs-spacing-0);
34
34
  gap: var(--fs-spacing-0);
35
35
  border-style: solid;
@@ -53,7 +53,7 @@
53
53
  [data-fs-bp-tab-bar] {
54
54
  width: fit-content;
55
55
  border-radius: var(--fs-border-radius-pill);
56
- border: 1px;
56
+ border: var(--fs-border-width);
57
57
  padding: var(--fs-spacing-0);
58
58
  gap: var(--fs-spacing-0);
59
59
  border-style: solid;
@@ -82,7 +82,7 @@
82
82
  line-height: var(--fs-spacing-4);
83
83
 
84
84
  border-radius: var(--fs-border-radius-pill);
85
- border: 1px solid #e0e0e0;
85
+ border: var(--fs-border-width) solid #e0e0e0;
86
86
  }
87
87
  }
88
88
 
@@ -0,0 +1,23 @@
1
+ import { BasicDropdownMenu, Icon } from "../../../shared/components";
2
+
3
+ import { DropdownItem } from "@faststore/ui";
4
+
5
+ export const BuyingPolicyDropdownMenu = () => {
6
+ return (
7
+ <BasicDropdownMenu>
8
+ <DropdownItem>
9
+ <Icon name="OpenInNew" />
10
+ Open
11
+ </DropdownItem>
12
+ <DropdownItem>
13
+ <Icon name="Edit" />
14
+ Edit settings
15
+ </DropdownItem>
16
+ <BasicDropdownMenu.Separator />
17
+ <DropdownItem data-fs-bp-dropdown-menu-item-mode="danger">
18
+ <Icon name="Delete" />
19
+ <span>Delete</span>
20
+ </DropdownItem>
21
+ </BasicDropdownMenu>
22
+ );
23
+ };
@@ -0,0 +1 @@
1
+ @import "../../../shared/components/BasicDropdownMenu/basic-dropdown-menu.scss";
@@ -0,0 +1 @@
1
+ export { BuyingPolicyDropdownMenu } from "./BuyingPolicyDropdownMenu/BuyingPolicyDropdownMenu";
@@ -0,0 +1,62 @@
1
+ import { FinanceTabsLayout, GlobalLayout } from "../../../shared/layouts";
2
+ import {
3
+ HeaderInside,
4
+ InternalSearch,
5
+ ListLine,
6
+ } from "../../../shared/components";
7
+ import { useBuyerPortal, useQueryParams } from "../../../shared/hooks";
8
+ import type { BuyingPolicy } from "../../types";
9
+ import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
10
+ import { BuyingPolicyDropdownMenu } from "../../components";
11
+
12
+ export type BuyingPoliciesLayoutProps = {
13
+ data: { buyingPolicies: BuyingPolicy[] } | null;
14
+ search: string;
15
+ };
16
+
17
+ export const BuyingPoliciesLayout = ({
18
+ data,
19
+ search,
20
+ }: BuyingPoliciesLayoutProps) => {
21
+ const { setQueryString, removeQueryString } = useQueryParams();
22
+
23
+ const { currentContract, currentOrgUnit } = useBuyerPortal();
24
+
25
+ return (
26
+ <GlobalLayout>
27
+ <FinanceTabsLayout pageName="Finance and Compliance">
28
+ <section data-fs-buying-policies-section>
29
+ <HeaderInside title="Buying Policies">
30
+ <HeaderInside.Button />
31
+ </HeaderInside>
32
+
33
+ <div data-fs-buying-policies-filter>
34
+ <InternalSearch
35
+ defaultValue={search}
36
+ textSearch={(searchTerm) => {
37
+ searchTerm
38
+ ? setQueryString("search", searchTerm)
39
+ : removeQueryString("search");
40
+ }}
41
+ />
42
+ </div>
43
+
44
+ <span data-fs-buying-policies-heading>Name</span>
45
+ {data?.buyingPolicies.map((buyingPolicy) => (
46
+ <ListLine
47
+ key={buyingPolicy.id}
48
+ title={buyingPolicy.name}
49
+ iconName="Rebase"
50
+ href={buyerPortalRoutes.buyingPolicyDetails({
51
+ contractId: currentContract?.id ?? "",
52
+ orgUnitId: currentOrgUnit?.id ?? "",
53
+ buyingPolicyId: buyingPolicy.id,
54
+ })}
55
+ dropdownMenu={<BuyingPolicyDropdownMenu />}
56
+ />
57
+ ))}
58
+ </section>
59
+ </FinanceTabsLayout>
60
+ </GlobalLayout>
61
+ );
62
+ };
@@ -0,0 +1,24 @@
1
+ @import "@faststore/ui/src/components/molecules/Dropdown/styles.scss";
2
+ @import "../../components/BuyingPolicyDropdownMenu/buying-policy-dropdown-menu.scss";
3
+ @import "../../../shared/layouts/FinanceTabsLayout/finance-tabs-layout.scss";
4
+
5
+ [data-fs-buying-policies-section] {
6
+ @import "../../../shared/components/HeaderInside/header-inside.scss";
7
+ @import "../../../shared/components/InternalSearch/internal-search.scss";
8
+ @import "../../../shared/components/ListLine/list-line.scss";
9
+
10
+ [data-fs-buying-policies-filter] {
11
+ display: flex;
12
+ justify-content: space-between;
13
+ padding: 0 0 var(--fs-spacing-4);
14
+ }
15
+
16
+ [data-fs-buying-policies-heading] {
17
+ display: flex;
18
+ font-weight: 400;
19
+ font-size: var(--fs-text-size-1);
20
+ line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
21
+ color: #5c5c5c;
22
+ margin-bottom: var(--fs-spacing-1);
23
+ }
24
+ }
@@ -0,0 +1,83 @@
1
+ import { FinanceTabsLayout, GlobalLayout } from "../../../shared/layouts";
2
+ import {
3
+ BasicDropdownMenu,
4
+ HeaderInside,
5
+ Icon,
6
+ } from "../../../shared/components";
7
+ import { useBuyerPortal } from "../../../shared/hooks";
8
+ import type { BuyingPolicy } from "../../types";
9
+ import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
10
+ import { Dropdown, DropdownItem } from "@faststore/ui";
11
+ import { BuyingPolicyDropdownMenu } from "../../components";
12
+
13
+ export type BuyingPolicyDetailsLayoutProps = {
14
+ data: { buyingPolicy: BuyingPolicy | null };
15
+ };
16
+
17
+ export const BuyingPolicyDetailsLayout = ({
18
+ data: { buyingPolicy },
19
+ }: BuyingPolicyDetailsLayoutProps) => {
20
+ const { currentContract, currentOrgUnit } = useBuyerPortal();
21
+
22
+ return (
23
+ <GlobalLayout>
24
+ <FinanceTabsLayout pageName="Finance and Compliance">
25
+ <section data-fs-buying-policy-details-section>
26
+ <HeaderInside
27
+ title={buyingPolicy?.name ?? ""}
28
+ backLink={buyerPortalRoutes.buyingPolicies({
29
+ contractId: currentContract?.id || "",
30
+ orgUnitId: currentOrgUnit?.id || "",
31
+ })}
32
+ >
33
+ <Dropdown>
34
+ <BasicDropdownMenu.Trigger />
35
+ <BuyingPolicyDropdownMenu />
36
+ </Dropdown>
37
+ </HeaderInside>
38
+
39
+ <div data-fs-buying-policy-details-line>
40
+ <span data-fs-buying-policy-details-title>Settings</span>
41
+ <button type="button" data-fs-buying-policy-details-edit-button>
42
+ Edit
43
+ </button>
44
+ </div>
45
+ <div data-fs-buying-policy-details-line>
46
+ <span data-fs-buying-policy-details-field-label>Name</span>
47
+ <span data-fs-buying-policy-details-field-content>
48
+ {buyingPolicy?.name}
49
+ </span>
50
+ </div>
51
+ <div data-fs-buying-policy-details-line>
52
+ <span data-fs-buying-policy-details-field-label>Description</span>
53
+ <span data-fs-buying-policy-details-field-content>
54
+ {buyingPolicy?.description}
55
+ </span>
56
+ </div>
57
+ <div data-fs-buying-policy-details-line>
58
+ <span data-fs-buying-policy-details-field-label>Criteria</span>
59
+ <span data-fs-buying-policy-details-field-content>
60
+ {buyingPolicy?.criteria}
61
+ </span>
62
+ </div>
63
+ <div data-fs-buying-policy-details-line>
64
+ <span data-fs-buying-policy-details-field-label>Action</span>
65
+ <span data-fs-buying-policy-details-field-content>
66
+ {buyingPolicy?.action.type}
67
+ {buyingPolicy?.action.levels?.map((level, index) => (
68
+ <>
69
+ <span key={level} data-fs-buying-policy-details-level-title>
70
+ Level {index + 1}
71
+ </span>
72
+ <span key={level} data-fs-buying-policy-details-level-value>
73
+ {level}
74
+ </span>
75
+ </>
76
+ ))}
77
+ </span>
78
+ </div>
79
+ </section>
80
+ </FinanceTabsLayout>
81
+ </GlobalLayout>
82
+ );
83
+ };
@@ -0,0 +1,74 @@
1
+ @import "@faststore/ui/src/components/molecules/Dropdown/styles.scss";
2
+
3
+ @import "../../../shared/layouts/FinanceTabsLayout/finance-tabs-layout.scss";
4
+ @import "../../components/BuyingPolicyDropdownMenu/buying-policy-dropdown-menu.scss";
5
+
6
+ [data-fs-buying-policy-details-section] {
7
+ @import "../../../shared/components/HeaderInside/header-inside.scss";
8
+ @import "../../../shared/components/InternalSearch/internal-search.scss";
9
+
10
+ [data-fs-buying-policy-details-line] {
11
+ padding: calc(var(--fs-spacing-4) - var(--fs-spacing-0)) 0;
12
+ border-top: var(--fs-border-width) solid #e0e0e0;
13
+ display: flex;
14
+
15
+ &:first-of-type {
16
+ border-top: none;
17
+ }
18
+
19
+ [data-fs-buying-policy-details-title] {
20
+ font-weight: 600;
21
+ font-size: var(--fs-text-size-2);
22
+ line-height: var(--fs-spacing-4);
23
+ margin-right: auto;
24
+ }
25
+
26
+ [data-fs-buying-policy-details-edit-button] {
27
+ border: var(--fs-border-width) solid #e0e0e0;
28
+ border-radius: var(--fs-border-radius-pill);
29
+ padding: var(--fs-spacing-1)
30
+ calc(var(--fs-spacing-4) - var(--fs-spacing-0));
31
+ font-weight: 600;
32
+ font-size: var(--fs-text-size-1);
33
+ line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
34
+ text-align: center;
35
+ color: #0366dd;
36
+ cursor: pointer;
37
+ }
38
+
39
+ [data-fs-buying-policy-details-field-label] {
40
+ width: 14rem;
41
+ display: inline-block;
42
+
43
+ font-weight: 400;
44
+ font-size: var(--fs-text-size-1);
45
+ line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
46
+
47
+ color: #5c5c5c;
48
+ }
49
+
50
+ [data-fs-buying-policy-details-field-content] {
51
+ font-weight: 400;
52
+ font-size: var(--fs-text-size-1);
53
+ line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
54
+ color: #000000;
55
+
56
+ [data-fs-buying-policy-details-level-title] {
57
+ display: block;
58
+ margin-top: var(--fs-spacing-3);
59
+ font-weight: 400;
60
+ font-size: var(--fs-text-size-1);
61
+ line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
62
+ color: #5c5c5c;
63
+ }
64
+
65
+ [data-fs-buying-policy-details-level-value] {
66
+ display: block;
67
+ font-weight: 500;
68
+ font-size: var(--fs-text-size-1);
69
+ line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
70
+ color: #1f1f1f;
71
+ }
72
+ }
73
+ }
74
+ }
@@ -0,0 +1 @@
1
+ export { BuyingPoliciesLayout } from "./BuyingPoliciesLayout/BuyingPoliciesLayout";
@@ -0,0 +1,145 @@
1
+ import type { BuyingPolicy } from "../types";
2
+
3
+ export const buyingPoliciesData: BuyingPolicy[] = [
4
+ {
5
+ id: "1",
6
+ name: "Standard Purchase Policy",
7
+ description: "Defines the standard rules for purchasing items.",
8
+ criteria: "budget >= 100",
9
+ action: {
10
+ type: "Bypass all policies",
11
+ },
12
+ },
13
+ {
14
+ id: "2",
15
+ name: "Bulk Order Policy",
16
+ description: "Policy for handling bulk orders with discounts.",
17
+ criteria: "quantity > 100",
18
+ action: {
19
+ type: "Sequential approval workflow",
20
+ levels: ["Manager", "Director", "CEO"],
21
+ },
22
+ },
23
+ {
24
+ id: "3",
25
+ name: "Restricted Items Policy",
26
+ description: "Restricts the purchase of certain items.",
27
+ criteria: "category == 'restricted'",
28
+ action: {
29
+ type: "Deny all",
30
+ },
31
+ },
32
+ {
33
+ id: "4",
34
+ name: "Employee Discount Policy",
35
+ description: "Provides discounts for employees.",
36
+ criteria: "userRole == 'employee'",
37
+ action: {
38
+ type: "Bypass all policies",
39
+ },
40
+ },
41
+ {
42
+ id: "5",
43
+ name: "Seasonal Promotion Policy",
44
+ description: "Handles seasonal promotions and discounts.",
45
+ criteria: "season == 'holiday'",
46
+ action: {
47
+ type: "Sequential approval workflow",
48
+ levels: ["Marketing Manager", "Finance Director"],
49
+ },
50
+ },
51
+ {
52
+ id: "6",
53
+ name: "High-Value Purchase Policy",
54
+ description: "Requires approval for high-value purchases.",
55
+ criteria: "purchaseAmount > 10000",
56
+ action: {
57
+ type: "Sequential approval workflow",
58
+ levels: ["Manager", "Finance Director", "CEO"],
59
+ },
60
+ },
61
+ {
62
+ id: "7",
63
+ name: "Loyalty Program Policy",
64
+ description: "Rewards loyal customers with points.",
65
+ criteria: "loyaltyPoints >= 500",
66
+ action: {
67
+ type: "Bypass all policies",
68
+ },
69
+ },
70
+ {
71
+ id: "8",
72
+ name: "New Customer Policy",
73
+ description: "Special offers for new customers.",
74
+ criteria: "isFirstPurchase == true",
75
+ action: {
76
+ type: "Bypass all policies",
77
+ },
78
+ },
79
+ {
80
+ id: "9",
81
+ name: "Corporate Account Policy",
82
+ description: "Custom rules for corporate accounts.",
83
+ criteria: "accountType == 'corporate'",
84
+ action: {
85
+ type: "Sequential approval workflow",
86
+ levels: ["Account Manager", "Finance Director"],
87
+ },
88
+ },
89
+ {
90
+ id: "10",
91
+ name: "Subscription Renewal Policy",
92
+ description: "Handles subscription renewals.",
93
+ criteria: "subscriptionStatus == 'active'",
94
+ action: {
95
+ type: "Bypass all policies",
96
+ },
97
+ },
98
+ {
99
+ id: "11",
100
+ name: "International Shipping Policy",
101
+ description: "Rules for international orders.",
102
+ criteria: "shippingCountry != 'domestic'",
103
+ action: {
104
+ type: "Sequential approval workflow",
105
+ levels: ["Logistics Manager", "Finance Director"],
106
+ },
107
+ },
108
+ {
109
+ id: "12",
110
+ name: "Return and Refund Policy",
111
+ description: "Defines rules for returns and refunds.",
112
+ criteria: "daysSincePurchase <= 30",
113
+ action: {
114
+ type: "Deny all",
115
+ },
116
+ },
117
+ {
118
+ id: "13",
119
+ name: "Age-Restricted Items Policy",
120
+ description: "Restricts purchase of age-sensitive items.",
121
+ criteria: "userAge < 18",
122
+ action: {
123
+ type: "Deny all",
124
+ },
125
+ },
126
+ {
127
+ id: "14",
128
+ name: "VIP Customer Policy",
129
+ description: "Exclusive benefits for VIP customers.",
130
+ criteria: "userStatus == 'VIP'",
131
+ action: {
132
+ type: "Bypass all policies",
133
+ },
134
+ },
135
+ {
136
+ id: "15",
137
+ name: "Environmental Sustainability Policy",
138
+ description: "Encourages eco-friendly purchases.",
139
+ criteria: "productType == 'eco-friendly'",
140
+ action: {
141
+ type: "Sequential approval workflow",
142
+ levels: ["Sustainability Manager", "CEO"],
143
+ },
144
+ },
145
+ ];
@@ -0,0 +1 @@
1
+ export { buyingPoliciesData } from "./buying-policy-data";
@@ -0,0 +1,10 @@
1
+ export type BuyingPolicy = {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ criteria: string;
6
+ action: {
7
+ type: string;
8
+ levels?: string[];
9
+ };
10
+ };
@@ -0,0 +1 @@
1
+ export type { BuyingPolicy } from "./BuyingPolicies";
@@ -16,6 +16,7 @@ import {
16
16
  } from "../../../shared/components";
17
17
  import {
18
18
  getContractSettingsLinks,
19
+ getFinanceSettingsLinks,
19
20
  getOrganizationSettingsLinks,
20
21
  } from "../../../shared/utils";
21
22
  import type { UserData } from "../../../users/types";
@@ -30,11 +31,6 @@ export type OrgUnitsDetailsLayoutProps = {
30
31
  };
31
32
  };
32
33
 
33
- const financeSettings = [
34
- { name: "Budgets", link: "/budgets" },
35
- { name: "Buying Policies", link: "/buying-policies" },
36
- ];
37
-
38
34
  export const OrgUnitsDetailsLayout = ({
39
35
  data: { orgUnit, contracts, user },
40
36
  }: OrgUnitsDetailsLayoutProps) => {
@@ -141,11 +137,17 @@ export const OrgUnitsDetailsLayout = ({
141
137
  <BasicCard
142
138
  data-fs-bp-compliance-settings-card
143
139
  footerMessage="Manage finance and compliance settings"
144
- footerLink="/"
140
+ footerLink={buyerPortalRoutes.buyingPolicies({
141
+ orgUnitId: orgUnit.id,
142
+ contractId: contracts[0].id,
143
+ })}
145
144
  enableFooter
146
145
  >
147
146
  <VerticalNav.Menu title="Finance and Compliance">
148
- {financeSettings.map((option) => (
147
+ {getFinanceSettingsLinks({
148
+ orgUnitId: orgUnit.id,
149
+ contractId: contracts[0].id,
150
+ }).map((option) => (
149
151
  <VerticalNav.Link key={option.name} link={option.link}>
150
152
  {option.name}
151
153
  </VerticalNav.Link>
@@ -0,0 +1,52 @@
1
+ import type { ReactNode } from "react";
2
+ import { Icon } from "../Icon";
3
+ import { BasicDropdownMenu } from "../BasicDropdownMenu/BasicDropdownMenu";
4
+ import { Dropdown } from "@faststore/components";
5
+ import Link from "next/link";
6
+
7
+ export type ListLineProps = {
8
+ children?: ReactNode;
9
+ title: string;
10
+ iconName?: string;
11
+ iconSize?: number;
12
+ dropdownMenu?: ReactNode;
13
+ href?: string;
14
+ onClick?: () => void;
15
+ };
16
+
17
+ export const ListLine = ({
18
+ iconName,
19
+ iconSize = 24,
20
+ title,
21
+ dropdownMenu,
22
+ children,
23
+ href,
24
+ onClick,
25
+ ...otherProps
26
+ }: ListLineProps) => {
27
+ const ClickComponent = href ? Link : "button";
28
+
29
+ return (
30
+ <li data-fs-bp-line {...otherProps}>
31
+ {href && (
32
+ <ClickComponent data-fs-bp-line-link href={href} onClick={onClick} />
33
+ )}
34
+ {iconName && (
35
+ <Icon
36
+ data-fs-bp-line-icon
37
+ name={iconName}
38
+ width={iconSize}
39
+ height={iconSize}
40
+ />
41
+ )}
42
+ <h3 data-fs-bp-line-title>{title}</h3>
43
+ {children}
44
+ {dropdownMenu && (
45
+ <Dropdown>
46
+ <BasicDropdownMenu.Trigger />
47
+ {dropdownMenu}
48
+ </Dropdown>
49
+ )}
50
+ </li>
51
+ );
52
+ };
@@ -0,0 +1,49 @@
1
+ @import "../BasicDropdownMenu/basic-dropdown-menu.scss";
2
+
3
+ [data-fs-bp-line] {
4
+ position: relative;
5
+ list-style: none;
6
+ border-top: var(--fs-border-width) solid #e0e0e0;
7
+ padding: var(--fs-spacing-1) var(--fs-spacing-1);
8
+ align-items: center;
9
+
10
+ display: flex;
11
+ width: 100%;
12
+
13
+ &:last-of-type {
14
+ border-bottom: var(--fs-border-width) solid #e0e0e0;
15
+ }
16
+
17
+ [data-fs-bp-line-link] {
18
+ position: absolute;
19
+ left: 0;
20
+ right: 0;
21
+ top: 0;
22
+ bottom: 0;
23
+
24
+ &:hover {
25
+ background-color: #f5f5f5;
26
+ }
27
+ }
28
+
29
+ [data-fs-bp-line-icon] {
30
+ color: #0366dd;
31
+ margin-right: var(--fs-spacing-2);
32
+ pointer-events: none;
33
+ z-index: 1;
34
+ }
35
+
36
+ [data-fs-bp-line-title] {
37
+ font-weight: 500;
38
+ font-size: var(--fs-text-size-1);
39
+ line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
40
+ color: #1f1f1f;
41
+ z-index: 1;
42
+ pointer-events: none;
43
+ }
44
+
45
+ [data-fs-bp-basic-dropdown-menu-trigger] {
46
+ z-index: 1;
47
+ margin-left: auto;
48
+ }
49
+ }
@@ -66,3 +66,4 @@ export {
66
66
  HeaderInside,
67
67
  type HeaderInsideProps,
68
68
  } from "./HeaderInside/HeaderInside";
69
+ export { ListLine, type ListLineProps } from "./ListLine/ListLine";
@@ -76,5 +76,6 @@
76
76
 
77
77
  max-height: calc(100vh - var(--fs-bp-nav-height));
78
78
  overflow-y: auto;
79
+ padding: 0 var(--fs-text-size-7);
79
80
  }
80
81
  }
@@ -0,0 +1,40 @@
1
+ import type { ReactNode } from "react";
2
+ import { BaseTabsLayout } from "../BaseTabsLayout/BaseTabsLayout";
3
+ import { getFinanceSettingsLinks } from "../../utils";
4
+ import { useBuyerPortal } from "../../hooks";
5
+
6
+ export type FinanceTabsLayoutProps = {
7
+ pageName: string;
8
+ children?: ReactNode;
9
+ };
10
+
11
+ export const FinanceTabsLayout = ({
12
+ pageName,
13
+ children,
14
+ }: FinanceTabsLayoutProps) => {
15
+ const { currentOrgUnit, currentUser, currentContract } = useBuyerPortal();
16
+
17
+ const verticalLinks = getFinanceSettingsLinks({
18
+ contractId: currentContract?.id ?? "",
19
+ orgUnitId: currentOrgUnit?.id ?? "",
20
+ });
21
+
22
+ return (
23
+ <BaseTabsLayout data-fs-bp-finance-tabs-layout>
24
+ <BaseTabsLayout.Navbar
25
+ orgUnitName={currentOrgUnit?.name ?? ""}
26
+ orgUnitId={currentOrgUnit?.id ?? ""}
27
+ pageName={pageName}
28
+ person={{
29
+ name: currentUser?.name ?? "",
30
+ role: currentUser?.role ?? "",
31
+ }}
32
+ />
33
+ <BaseTabsLayout.SidebarMenu
34
+ verticalLinks={verticalLinks}
35
+ showLetterHighlight={false}
36
+ />
37
+ <BaseTabsLayout.Content>{children}</BaseTabsLayout.Content>
38
+ </BaseTabsLayout>
39
+ );
40
+ };
@@ -0,0 +1,4 @@
1
+ @import "../BaseTabsLayout/base-tabs-layout.scss";
2
+
3
+ [data-fs-bp-finance-tabs-layout] {
4
+ }
@@ -10,3 +10,7 @@ export {
10
10
  ContractTabsLayout,
11
11
  type ContractTabsLayoutProps,
12
12
  } from "./ContractTabsLayout/ContractTabsLayout";
13
+ export {
14
+ FinanceTabsLayout,
15
+ type FinanceTabsLayoutProps,
16
+ } from "./FinanceTabsLayout/FinanceTabsLayout";
@@ -42,6 +42,18 @@ export const buyerPortalRoutes = {
42
42
  releases: (params: { orgUnitId: string; contractId: string }) =>
43
43
  replaceParams(`${base}/releases/[orgUnitId]/[contractId]`, params),
44
44
 
45
+ buyingPolicies: (params: { orgUnitId: string; contractId: string }) =>
46
+ replaceParams(`${base}/buying-policies/[orgUnitId]/[contractId]`, params),
47
+
48
+ buyingPolicyDetails: (params: {
49
+ orgUnitId: string;
50
+ contractId: string;
51
+ buyingPolicyId: string;
52
+ }) =>
53
+ replaceParams(
54
+ `${base}/buying-policy/[orgUnitId]/[contractId]/[buyingPolicyId]`,
55
+ params
56
+ ),
45
57
  // orgUnitId only
46
58
  users: (params: { orgUnitId: string }) =>
47
59
  replaceParams(`${base}/users/[orgUnitId]`, params),
@@ -0,0 +1,15 @@
1
+ import { buyerPortalRoutes } from "./buyerPortalRoutes";
2
+
3
+ export const getFinanceSettingsLinks = ({
4
+ orgUnitId,
5
+ contractId,
6
+ }: {
7
+ orgUnitId: string;
8
+ contractId: string;
9
+ }) => [
10
+ { name: "Budgets", link: "/" },
11
+ {
12
+ name: "Buying Policies",
13
+ link: buyerPortalRoutes.buyingPolicies({ orgUnitId, contractId }),
14
+ },
15
+ ];
@@ -25,3 +25,4 @@ export { roles, type Role } from "./roles";
25
25
  export { getContractSettingsLinks } from "./getContractSettingsLinks";
26
26
  export { getOrganizationSettingsLinks } from "./getOrganizationSettingsLinks";
27
27
  export { maskCardNumber, maskCVV, maskExpirationDate } from "./creditCard";
28
+ export { getFinanceSettingsLinks } from "./getFinanceSettingsLinks";
@@ -0,0 +1,81 @@
1
+ import {
2
+ getAddressesService,
3
+ type GetAddressesServiceProps,
4
+ } from "../features/addresses/services";
5
+
6
+ import {
7
+ type ClientContext,
8
+ getClientContext,
9
+ getCustomerIdFromCookieServerSide,
10
+ } from "../features/shared/utils";
11
+ import type { AddressData } from "../features/addresses/types";
12
+ import { AddressLayout } from "../features/addresses/layouts";
13
+ import { BuyerPortalProvider } from "../features/shared/components";
14
+ import type { LoaderData } from "../features/shared/types";
15
+ import type { OrgUnitBasicData } from "../features/org-units/types";
16
+ import { getOrgUnitBasicDataService } from "../features/org-units/services";
17
+ import { getUserByIdService } from "../features/users/services";
18
+ import type { UserData } from "../features/users/types";
19
+ import type { ContractData } from "../features/contracts/types";
20
+ import { getContractDetailsService } from "../features/contracts/services";
21
+ import type { BuyingPolicy } from "../features/buying-policies/types";
22
+ import { buyingPoliciesData } from "../features/buying-policies/mocks";
23
+ import { BuyingPoliciesLayout } from "../features/buying-policies/layouts";
24
+
25
+ export type BuyingPoliciesPageData = {
26
+ data: { buyingPolicies: BuyingPolicy[] };
27
+ search: string;
28
+ context: {
29
+ currentContract: ContractData | null;
30
+ clientContext: ClientContext;
31
+ currentOrgUnit: OrgUnitBasicData | null;
32
+ currentUser: UserData | null;
33
+ };
34
+ };
35
+
36
+ export type BuyingPoliciesPageQuery = GetAddressesServiceProps & {
37
+ orgUnitId: string;
38
+ contractId: string;
39
+ search?: string;
40
+ };
41
+
42
+ export async function loader(
43
+ data: LoaderData<BuyingPoliciesPageQuery>
44
+ ): Promise<BuyingPoliciesPageData> {
45
+ const { contractId, orgUnitId, search = "" } = data.query;
46
+
47
+ const { cookie, userId, ...clientContext } = await getClientContext(data);
48
+
49
+ const currentOrgUnit = await getOrgUnitBasicDataService({
50
+ id: orgUnitId,
51
+ cookie,
52
+ });
53
+
54
+ const user = await getUserByIdService({ userId, cookie });
55
+
56
+ const contract = await getContractDetailsService({
57
+ contractId,
58
+ cookie,
59
+ });
60
+
61
+ return {
62
+ data: {
63
+ buyingPolicies: buyingPoliciesData,
64
+ },
65
+ search,
66
+ context: {
67
+ clientContext: { cookie, userId, ...clientContext },
68
+ currentOrgUnit,
69
+ currentUser: user,
70
+ currentContract: contract,
71
+ },
72
+ };
73
+ }
74
+
75
+ const AddressPage = ({ data, search, context }: BuyingPoliciesPageData) => (
76
+ <BuyerPortalProvider {...context}>
77
+ <BuyingPoliciesLayout data={data} search={search} />
78
+ </BuyerPortalProvider>
79
+ );
80
+
81
+ export default AddressPage;
@@ -0,0 +1,72 @@
1
+ import type { GetAddressesServiceProps } from "../features/addresses/services";
2
+
3
+ import { type ClientContext, getClientContext } from "../features/shared/utils";
4
+ import { BuyerPortalProvider } from "../features/shared/components";
5
+ import type { LoaderData } from "../features/shared/types";
6
+ import type { OrgUnitBasicData } from "../features/org-units/types";
7
+ import { getOrgUnitBasicDataService } from "../features/org-units/services";
8
+ import { getUserByIdService } from "../features/users/services";
9
+ import type { UserData } from "../features/users/types";
10
+ import type { ContractData } from "../features/contracts/types";
11
+ import { getContractDetailsService } from "../features/contracts/services";
12
+ import type { BuyingPolicy } from "../features/buying-policies/types";
13
+ import { buyingPoliciesData } from "../features/buying-policies/mocks";
14
+ import { BuyingPolicyDetailsLayout } from "../features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout";
15
+
16
+ export type BuyingPolicyDetailsPageData = {
17
+ data: { buyingPolicy: BuyingPolicy | null };
18
+ context: {
19
+ currentContract: ContractData | null;
20
+ clientContext: ClientContext;
21
+ currentOrgUnit: OrgUnitBasicData | null;
22
+ currentUser: UserData | null;
23
+ };
24
+ };
25
+
26
+ export type BuyingPolicyDetailsPageQuery = GetAddressesServiceProps & {
27
+ orgUnitId: string;
28
+ contractId: string;
29
+ buyingPolicyId: string;
30
+ };
31
+
32
+ export async function loader(
33
+ data: LoaderData<BuyingPolicyDetailsPageQuery>
34
+ ): Promise<BuyingPolicyDetailsPageData> {
35
+ const { contractId, orgUnitId, buyingPolicyId } = data.query;
36
+
37
+ const { cookie, userId, ...clientContext } = await getClientContext(data);
38
+
39
+ const currentOrgUnit = await getOrgUnitBasicDataService({
40
+ id: orgUnitId,
41
+ cookie,
42
+ });
43
+
44
+ const user = await getUserByIdService({ userId, cookie });
45
+
46
+ const contract = await getContractDetailsService({
47
+ contractId,
48
+ cookie,
49
+ });
50
+
51
+ return {
52
+ data: {
53
+ buyingPolicy:
54
+ buyingPoliciesData.find((policy) => policy.id === buyingPolicyId) ??
55
+ null,
56
+ },
57
+ context: {
58
+ clientContext: { cookie, userId, ...clientContext },
59
+ currentOrgUnit,
60
+ currentUser: user,
61
+ currentContract: contract,
62
+ },
63
+ };
64
+ }
65
+
66
+ const AddressPage = ({ data, context }: BuyingPolicyDetailsPageData) => (
67
+ <BuyerPortalProvider {...context}>
68
+ <BuyingPolicyDetailsLayout data={data} />
69
+ </BuyerPortalProvider>
70
+ );
71
+
72
+ export default AddressPage;
@@ -21,3 +21,7 @@
21
21
 
22
22
  // Credit Cards
23
23
  @import "../features/credit-cards/layouts/CreditCardsLayout/credit-card-layout.scss";
24
+
25
+ // Buying Policies
26
+ @import "../features/buying-policies/layouts/BuyingPoliciesLayout/buying-policies-layout.scss";
27
+ @import "../features/buying-policies/layouts/BuyingPolicyDetailsLayout/buying-policy-details-layout.scss";