@faststore/core 3.72.1 → 3.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +24 -24
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/config.json +3 -3
  5. package/.next/cache/webpack/client-production/0.pack +0 -0
  6. package/.next/cache/webpack/client-production/index.pack +0 -0
  7. package/.next/cache/webpack/server-production/0.pack +0 -0
  8. package/.next/cache/webpack/server-production/index.pack +0 -0
  9. package/.next/prerender-manifest.js +1 -1
  10. package/.next/prerender-manifest.json +1 -1
  11. package/.next/routes-manifest.json +1 -1
  12. package/.next/server/chunks/2778.js +1 -1
  13. package/.next/server/chunks/3918.js +1 -1
  14. package/.next/server/chunks/9563.js +2 -2
  15. package/.next/server/chunks/9630.js +3 -3
  16. package/.next/server/functions-config-manifest.json +1 -1
  17. package/.next/server/middleware-build-manifest.js +1 -1
  18. package/.next/server/pages/[...slug].js.nft.json +1 -1
  19. package/.next/server/pages/[slug]/p.js.nft.json +1 -1
  20. package/.next/server/pages/account/403.js.nft.json +1 -1
  21. package/.next/server/pages/account/404.js.nft.json +1 -1
  22. package/.next/server/pages/account/orders/[id].js.nft.json +1 -1
  23. package/.next/server/pages/account/orders.js.nft.json +1 -1
  24. package/.next/server/pages/account/profile.js.nft.json +1 -1
  25. package/.next/server/pages/account/security.js +1 -1
  26. package/.next/server/pages/account/security.js.nft.json +1 -1
  27. package/.next/server/pages/account/user-details.js.nft.json +1 -1
  28. package/.next/server/pages/account.js.nft.json +1 -1
  29. package/.next/server/pages/api/graphql.js +2 -2
  30. package/.next/server/pages/api/graphql.js.nft.json +1 -1
  31. package/.next/server/pages/en-US/404.html +1 -1
  32. package/.next/server/pages/en-US/500.html +1 -1
  33. package/.next/server/pages/en-US/checkout.html +1 -1
  34. package/.next/server/pages/en-US/login.html +1 -1
  35. package/.next/server/pages/en-US/s.html +1 -1
  36. package/.next/server/pages/en-US.html +1 -1
  37. package/.next/server/pages-manifest.json +1 -1
  38. package/.next/static/{rI6VOpaLnNnxZNUT_-Q3V → ESME7vyDcj2p1BfmznpZV}/_buildManifest.js +1 -1
  39. package/.next/static/chunks/pages/{_app-4068e2b477988545.js → _app-36fb57bc394108e6.js} +1 -1
  40. package/.next/static/chunks/pages/account/security-8ea4d1e2aba1bfb7.js +1 -0
  41. package/.next/static/chunks/{webpack-2ddf567180dd5aaa.js → webpack-3154bd2292a6ff53.js} +1 -1
  42. package/.next/static/css/{32b1696118552960.css → ec7fdad03808422d.css} +1 -1
  43. package/.next/trace +134 -134
  44. package/.turbo/turbo-build.log +11 -11
  45. package/.turbo/turbo-test.log +5 -5
  46. package/@generated/gql.ts +4 -4
  47. package/@generated/graphql.ts +10 -16
  48. package/@generated/persisted-documents.json +1 -1
  49. package/@generated/schema.graphql +0 -5
  50. package/CHANGELOG.md +6 -0
  51. package/package.json +3 -3
  52. package/src/components/account/security/SecurityDrawer.tsx +99 -10
  53. package/src/components/account/security/SecuritySection.tsx +8 -1
  54. package/src/components/account/security/styles.module.scss +15 -0
  55. package/src/pages/account/security.tsx +12 -6
  56. package/src/sdk/account/useSetPassword.ts +146 -0
  57. package/src/utils/utilities.ts +13 -0
  58. package/test/server/index.test.ts +0 -1
  59. package/.next/static/chunks/pages/account/security-94874fc477520f74.js +0 -1
  60. /package/.next/static/{rI6VOpaLnNnxZNUT_-Q3V → ESME7vyDcj2p1BfmznpZV}/_ssgManifest.js +0 -0
@@ -1,23 +1,23 @@
1
1
 
2
- > @faststore/core@3.72.0 prebuild /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@3.72.1 prebuild /home/runner/work/faststore/faststore/packages/core
3
3
  > na run partytown && na run generate
4
4
 
5
5
 
6
- > @faststore/core@3.72.0 partytown /home/runner/work/faststore/faststore/packages/core
6
+ > @faststore/core@3.72.1 partytown /home/runner/work/faststore/faststore/packages/core
7
7
  > partytown copylib ./public/~partytown
8
8
 
9
9
  Partytown lib copied to: /home/runner/work/faststore/faststore/packages/core/public/~partytown
10
10
 
11
- > @faststore/core@3.72.0 generate /home/runner/work/faststore/faststore/packages/core
11
+ > @faststore/core@3.72.1 generate /home/runner/work/faststore/faststore/packages/core
12
12
  > na run generate:schema && na run generate:codegen && na run format:generated
13
13
 
14
14
 
15
- > @faststore/core@3.72.0 generate:schema /home/runner/work/faststore/faststore/packages/core
15
+ > @faststore/core@3.72.1 generate:schema /home/runner/work/faststore/faststore/packages/core
16
16
  > tsx src/server/generator/generateGraphQLSchemaFile.ts
17
17
 
18
18
  Schema GraphQL file generated successfully
19
19
 
20
- > @faststore/core@3.72.0 generate:codegen /home/runner/work/faststore/faststore/packages/core
20
+ > @faststore/core@3.72.1 generate:codegen /home/runner/work/faststore/faststore/packages/core
21
21
  > graphql-codegen
22
22
 
23
23
  [STARTED] Parse Configuration
@@ -37,11 +37,11 @@ Running lifecycle hook "afterStart" scripts...
37
37
  [CLI] Loading Documents
38
38
  [CLI] Generating output
39
39
 
40
- > @faststore/core@3.72.0 format:generated /home/runner/work/faststore/faststore/packages/core
40
+ > @faststore/core@3.72.1 format:generated /home/runner/work/faststore/faststore/packages/core
41
41
  > prettier --write "@generated/**/*.{ts,js,tsx,jsx,json}" --loglevel error
42
42
 
43
43
 
44
- > @faststore/core@3.72.0 build /home/runner/work/faststore/faststore/packages/core
44
+ > @faststore/core@3.72.1 build /home/runner/work/faststore/faststore/packages/core
45
45
  > next build
46
46
 
47
47
  ⚠ No build cache found. Please configure build caching for faster rebuilds. Read more: https://nextjs.org/docs/messages/no-cache
@@ -95,8 +95,8 @@ Route (pages) Size First Load JS
95
95
  ├ └ css/70353bf19c496790.css 12.6 kB
96
96
  ├ λ /account/profile 1.79 kB 138 kB
97
97
  ├ └ css/831a1f72fe4b2d80.css 3.97 kB
98
- ├ λ /account/security 2.48 kB 139 kB
99
- ├ └ css/32b1696118552960.css 5.19 kB
98
+ ├ λ /account/security 3.62 kB 140 kB
99
+ ├ └ css/ec7fdad03808422d.css 5.22 kB
100
100
  ├ λ /account/user-details 1.74 kB 138 kB
101
101
  ├ └ css/e46393a76c5d93a9.css 4.17 kB
102
102
  ├ λ /api/graphql 0 B 106 kB
@@ -109,8 +109,8 @@ Route (pages) Size First Load JS
109
109
  + First Load JS shared by all 109 kB
110
110
  ├ chunks/framework-807b0f81cbc129f0.js 45.4 kB
111
111
  ├ chunks/main-f658704b53a96ab1.js 33.1 kB
112
- ├ chunks/pages/_app-4068e2b477988545.js 23.5 kB
113
- ├ chunks/webpack-2ddf567180dd5aaa.js 3.81 kB
112
+ ├ chunks/pages/_app-36fb57bc394108e6.js 23.6 kB
113
+ ├ chunks/webpack-3154bd2292a6ff53.js 3.81 kB
114
114
  └ css/0a57ee6c7a57788c.css 3.49 kB
115
115
 
116
116
  λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
@@ -1,14 +1,14 @@
1
1
 
2
- > @faststore/core@3.72.0 test /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@3.72.1 test /home/runner/work/faststore/faststore/packages/core
3
3
  > jest
4
4
 
5
- PASS test/utils/multipleTemplates.test.ts (26.191 s)
6
- PASS test/server/cms/global.test.ts (26.533 s)
5
+ PASS test/utils/multipleTemplates.test.ts (26.287 s)
6
+ PASS test/server/cms/global.test.ts (26.441 s)
7
7
  PASS test/server/cms/index.test.ts
8
- PASS test/server/index.test.ts (30.044 s)
8
+ PASS test/server/index.test.ts (29.359 s)
9
9
 
10
10
  Test Suites: 4 passed, 4 total
11
11
  Tests: 22 passed, 22 total
12
12
  Snapshots: 0 total
13
- Time: 31.403 s
13
+ Time: 30.652 s
14
14
  Ran all test suites.
package/@generated/gql.ts CHANGED
@@ -50,8 +50,8 @@ const documents = {
50
50
  types.ServerListOrdersQueryDocument,
51
51
  '\n query ServerProfileQuery {\n accountName\n accountProfile {\n name\n email\n id\n }\n }\n':
52
52
  types.ServerProfileQueryDocument,
53
- '\n query ServerSecurityQuery {\n accountName\n }\n':
54
- types.ServerSecurityQueryDocument,
53
+ '\n query ServerSecurity {\n accountName\n userDetails {\n email\n }\n }\n':
54
+ types.ServerSecurityDocument,
55
55
  '\n query ServerUserDetailsQuery {\n accountName\n userDetails {\n name\n email\n role\n orgUnit\n }\n }\n':
56
56
  types.ServerUserDetailsQueryDocument,
57
57
  '\n mutation CancelOrderMutation($data: IUserOrderCancel!) {\n cancelOrder(data: $data) {\n data\n }\n }\n':
@@ -208,8 +208,8 @@ export function gql(
208
208
  * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
209
209
  */
210
210
  export function gql(
211
- source: '\n query ServerSecurityQuery {\n accountName\n }\n'
212
- ): typeof import('./graphql').ServerSecurityQueryDocument
211
+ source: '\n query ServerSecurity {\n accountName\n userDetails {\n email\n }\n }\n'
212
+ ): typeof import('./graphql').ServerSecurityDocument
213
213
  /**
214
214
  * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
215
215
  */
@@ -650,11 +650,6 @@ export type Mutation = {
650
650
  cancelOrder: Maybe<UserOrderCancel>
651
651
  /** Process Order Authorization */
652
652
  processOrderAuthorization: Maybe<ProcessOrderAuthorizationResponse>
653
- /**
654
- * Sets a new password for the user.
655
- * This mutation is used to change the user's password, typically after a password reset or when the user wants to update their password.
656
- */
657
- setPassword: Maybe<SetPasswordResponse>
658
653
  /** Subscribes a new person to the newsletter list. */
659
654
  subscribeToNewsletter: Maybe<PersonNewsletter>
660
655
  /** Checks for changes between the cart presented in the UI and the cart stored in the ecommerce platform. If changes are detected, it returns the cart stored on the platform. Otherwise, it returns `null`. */
@@ -671,10 +666,6 @@ export type MutationProcessOrderAuthorizationArgs = {
671
666
  data: IProcessOrderAuthorization
672
667
  }
673
668
 
674
- export type MutationSetPasswordArgs = {
675
- data: ISetPassword
676
- }
677
-
678
669
  export type MutationSubscribeToNewsletterArgs = {
679
670
  data: IPersonNewsletter
680
671
  }
@@ -2860,9 +2851,12 @@ export type ServerProfileQueryQuery = {
2860
2851
  }
2861
2852
  }
2862
2853
 
2863
- export type ServerSecurityQueryQueryVariables = Exact<{ [key: string]: never }>
2854
+ export type ServerSecurityQueryVariables = Exact<{ [key: string]: never }>
2864
2855
 
2865
- export type ServerSecurityQueryQuery = { accountName: string | null }
2856
+ export type ServerSecurityQuery = {
2857
+ accountName: string | null
2858
+ userDetails: { email: string | null }
2859
+ }
2866
2860
 
2867
2861
  export type ServerUserDetailsQueryQueryVariables = Exact<{
2868
2862
  [key: string]: never
@@ -4024,14 +4018,14 @@ export const ServerProfileQueryDocument = {
4024
4018
  ServerProfileQueryQuery,
4025
4019
  ServerProfileQueryQueryVariables
4026
4020
  >
4027
- export const ServerSecurityQueryDocument = {
4021
+ export const ServerSecurityDocument = {
4028
4022
  __meta__: {
4029
- operationName: 'ServerSecurityQuery',
4030
- operationHash: '9f24767f16e6e05c168336701a6c6c7b6b5dc1c6',
4023
+ operationName: 'ServerSecurity',
4024
+ operationHash: '63c6eadbe8b77c0c3c91406589755accba5cf155',
4031
4025
  },
4032
4026
  } as unknown as TypedDocumentString<
4033
- ServerSecurityQueryQuery,
4034
- ServerSecurityQueryQueryVariables
4027
+ ServerSecurityQuery,
4028
+ ServerSecurityQueryVariables
4035
4029
  >
4036
4030
  export const ServerUserDetailsQueryDocument = {
4037
4031
  __meta__: {
@@ -5,7 +5,7 @@
5
5
  "ba4e1865d9840cb386fa6d646a51f275cd991bfa": "query ServerOrderDetailsQuery($orderId: String!) { accountName userOrder(orderId: $orderId) { allowCancellation canProcessOrderAuthorization clientProfileData { corporateName email firstName isCorporate lastName phone } creationDate customFields { fields { name refId value } id type } deliveryOptionsData { contact { email name phone } deliveryOptions { address { addressId addressType city complement country entityId geoCoordinates neighborhood number postalCode receiverName reference state street versionId } deliveryChannel deliveryCompany deliveryWindow { endDateUtc price startDateUtc } friendlyDeliveryOptionName friendlyShippingEstimate items { id imageUrl name price quantity tax total uniqueId } pickupStoreInfo { additionalInfo address { addressId addressType city complement country entityId geoCoordinates neighborhood number postalCode receiverName reference state street versionId } dockId friendlyName isPickupStore } quantityOfDifferentItems selectedSla seller shippingEstimate shippingEstimateDate total } } orderId paymentData { transactions { isActive payments { bankIssuedInvoiceIdentificationNumber connectorResponses { authId } group id installments lastDigits paymentOrigin paymentSystemName redemptionCode referenceValue tid url value } } } ruleForAuthorization { dimensionId orderAuthorizationId rule { authorizationData { authorizers { authorizationDate email id type } requireAllApprovals } authorizedEmails doId id isUserAuthorized isUserNextAuthorizer name notification priority scoreInterval { accept deny } status timeout trigger { condition { conditionType description expression greatherThan lessThan } effect { description effectType funcPath } } } } shopperName { firstName lastName } status statusDescription storePreferencesData { currencyCode } totals { id name value } } }",
6
6
  "ee84ac3f5b58c5e1950a927a42c5c1dd6012fcc4": "query ServerListOrdersQuery($clientEmail: String, $dateFinal: String, $dateInitial: String, $page: Int, $perPage: Int, $status: [String], $text: String) { accountName listUserOrders( page: $page perPage: $perPage status: $status dateInitial: $dateInitial dateFinal: $dateFinal text: $text clientEmail: $clientEmail ) { list { ShippingEstimatedDate clientName creationDate currencyCode customFields { type value } items { description ean id price productId quantity refId seller sellingPrice } orderId status statusDescription totalValue } paging { currentPage pages perPage total } } }",
7
7
  "0ed4b5db8fed122d8418195d01fb91b30261d587": "query ServerProfileQuery { accountName accountProfile { email id name } }",
8
- "9f24767f16e6e05c168336701a6c6c7b6b5dc1c6": "query ServerSecurityQuery { accountName }",
8
+ "63c6eadbe8b77c0c3c91406589755accba5cf155": "query ServerSecurity { accountName userDetails { email } }",
9
9
  "522e5feeb80e67cee931bc98eac9d08ea75c75d2": "query ServerUserDetailsQuery { accountName userDetails { email name orgUnit role } }",
10
10
  "e2b06da6840614d3c72768e56579b9d3b8e80802": "mutation CancelOrderMutation($data: IUserOrderCancel!) { cancelOrder(data: $data) { data } }",
11
11
  "8c25d37c8d6e7c20ab21bb8a4f4e6a2fe320ea8d": "mutation ProcessOrderAuthorizationMutation($data: IProcessOrderAuthorization!) { processOrderAuthorization(data: $data) { isPendingForOtherAuthorizer ruleForAuthorization { dimensionId orderAuthorizationId rule { authorizationData { authorizers { authorizationDate email id type } requireAllApprovals } authorizedEmails doId id isUserAuthorized isUserNextAuthorizer name notification priority scoreInterval { accept deny } status timeout trigger { condition { conditionType description expression greatherThan lessThan } effect { description effectType funcPath } } } } } }",
@@ -236,11 +236,6 @@ type Mutation {
236
236
  cancelOrder(data: IUserOrderCancel!): UserOrderCancel
237
237
  """Process Order Authorization"""
238
238
  processOrderAuthorization(data: IProcessOrderAuthorization!): ProcessOrderAuthorizationResponse
239
- """
240
- Sets a new password for the user.
241
- This mutation is used to change the user's password, typically after a password reset or when the user wants to update their password.
242
- """
243
- setPassword(data: ISetPassword!): SetPasswordResponse
244
239
  }
245
240
 
246
241
  """Newsletter information."""
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
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.73.0](https://github.com/vtex/faststore/compare/v3.72.1...v3.73.0) (2025-08-07)
7
+
8
+ ### Features
9
+
10
+ - implement password setting functionality and user email retrieval ([#2957](https://github.com/vtex/faststore/issues/2957)) ([a49aa1b](https://github.com/vtex/faststore/commit/a49aa1b6d55310a64639459b9082530f4a955731))
11
+
6
12
  ## [3.72.1](https://github.com/vtex/faststore/compare/v3.72.0...v3.72.1) (2025-08-07)
7
13
 
8
14
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "3.72.1",
3
+ "version": "3.73.0",
4
4
  "license": "MIT",
5
5
  "repository": "vtex/faststore",
6
6
  "browserslist": "supports es6-module and not dead",
@@ -44,7 +44,7 @@
44
44
  "@envelop/graphql-jit": "^8.0.3",
45
45
  "@envelop/parser-cache": "^6.0.2",
46
46
  "@envelop/validation-cache": "^6.0.2",
47
- "@faststore/api": "^3.72.0",
47
+ "@faststore/api": "^3.73.0",
48
48
  "@faststore/graphql-utils": "^3.56.1",
49
49
  "@faststore/lighthouse": "^3.56.1",
50
50
  "@faststore/sdk": "^3.72.0",
@@ -108,5 +108,5 @@
108
108
  "ts-jest": "29.1.1",
109
109
  "typescript": "5.3.2"
110
110
  },
111
- "gitHead": "0fea186218657d0b90320ac376072e7f65192782"
111
+ "gitHead": "f24d4ea67a2b356ec12a4fe51e30119c5d84d4f0"
112
112
  }
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from 'react'
1
+ import { useState } from 'react'
2
2
  import {
3
3
  Button,
4
4
  Icon,
@@ -7,13 +7,17 @@ import {
7
7
  SlideOver,
8
8
  SlideOverHeader,
9
9
  useFadeEffect,
10
+ useUI,
10
11
  } from '@faststore/ui'
11
12
 
13
+ import { useSetPassword } from 'src/sdk/account/useSetPassword'
12
14
  import styles from './styles.module.scss'
13
15
 
14
16
  type SecurityDrawerProps = {
17
+ userEmail: string
15
18
  isOpen: boolean
16
19
  onClose: () => void
20
+ accountName?: string
17
21
  }
18
22
 
19
23
  const validations = [
@@ -23,8 +27,14 @@ const validations = [
23
27
  { label: '1 number', test: (v: string) => /\d/.test(v) },
24
28
  ]
25
29
 
26
- export const SecurityDrawer = ({ isOpen, onClose }: SecurityDrawerProps) => {
30
+ export const SecurityDrawer = ({
31
+ userEmail,
32
+ accountName,
33
+ isOpen,
34
+ onClose,
35
+ }: SecurityDrawerProps) => {
27
36
  const { fade, fadeOut } = useFadeEffect()
37
+ const { pushToast } = useUI()
28
38
 
29
39
  const [currentPassword, setCurrentPassword] = useState('')
30
40
  const [showCurrentPassword, setShowCurrentPassword] = useState(false)
@@ -32,6 +42,10 @@ export const SecurityDrawer = ({ isOpen, onClose }: SecurityDrawerProps) => {
32
42
  const [newPassword, setNewPassword] = useState('')
33
43
  const [showNewPassword, setShowNewPassword] = useState(false)
34
44
 
45
+ const [formError, setFormError] = useState<string | null>(null)
46
+
47
+ const { setPassword, loading, error } = useSetPassword(accountName)
48
+
35
49
  const newPasswordValidations = validations.map((rule) => ({
36
50
  label: rule.label,
37
51
  isValid: rule.test(newPassword),
@@ -40,6 +54,7 @@ export const SecurityDrawer = ({ isOpen, onClose }: SecurityDrawerProps) => {
40
54
  const allValid = newPasswordValidations.every((r) => r.isValid)
41
55
 
42
56
  const handleClose = () => {
57
+ setFormError(null)
43
58
  setCurrentPassword('')
44
59
  setShowCurrentPassword(false)
45
60
  setNewPassword('')
@@ -47,6 +62,65 @@ export const SecurityDrawer = ({ isOpen, onClose }: SecurityDrawerProps) => {
47
62
  onClose()
48
63
  }
49
64
 
65
+ const handleSetPassword = async () => {
66
+ if (!userEmail) {
67
+ setFormError('Email is required to set a new password.')
68
+ return
69
+ }
70
+
71
+ if (!newPassword || !currentPassword) {
72
+ setFormError('All fields are required to set a new password.')
73
+ return
74
+ }
75
+
76
+ if (newPassword === currentPassword) {
77
+ setFormError('New password cannot be the same as the current password.')
78
+ return
79
+ }
80
+
81
+ try {
82
+ const data = await setPassword({
83
+ userEmail,
84
+ currentPassword,
85
+ newPassword,
86
+ })
87
+
88
+ if (error) {
89
+ throw error
90
+ }
91
+
92
+ if (!data.success) {
93
+ pushToast({
94
+ title: 'Error setting password',
95
+ status: 'ERROR',
96
+ message: `Failed to set password: ${data.message}`,
97
+ icon: <Icon width={30} height={30} name="CircleWavyWarning" />,
98
+ })
99
+
100
+ return
101
+ }
102
+
103
+ if (data.success) {
104
+ pushToast({
105
+ title: 'Success setting password',
106
+ status: 'INFO',
107
+ message: 'Password updated successfully',
108
+ icon: <Icon width={30} height={30} name="CircleWavyCheck" />,
109
+ })
110
+
111
+ handleClose()
112
+ }
113
+ } catch (error) {
114
+ console.error('Error setting password:', error)
115
+ pushToast({
116
+ title: 'Error setting password',
117
+ status: 'ERROR',
118
+ message: 'Failed to set password.',
119
+ icon: <Icon width={30} height={30} name="CircleWavyWarning" />,
120
+ })
121
+ }
122
+ }
123
+
50
124
  return (
51
125
  <SlideOver
52
126
  data-fs-security-drawer
@@ -72,7 +146,10 @@ export const SecurityDrawer = ({ isOpen, onClose }: SecurityDrawerProps) => {
72
146
  placeholder="Current Password"
73
147
  inputMode="text"
74
148
  value={currentPassword}
75
- onChange={(e) => setCurrentPassword(e.target.value)}
149
+ onChange={(e) => {
150
+ setFormError(null)
151
+ setCurrentPassword(e.target.value)
152
+ }}
76
153
  />
77
154
  <IconButton
78
155
  data-fs-security-drawer-input-password-toggle
@@ -97,7 +174,10 @@ export const SecurityDrawer = ({ isOpen, onClose }: SecurityDrawerProps) => {
97
174
  placeholder="New Password"
98
175
  inputMode="text"
99
176
  value={newPassword}
100
- onChange={(e) => setNewPassword(e.target.value)}
177
+ onChange={(e) => {
178
+ setFormError(null)
179
+ setNewPassword(e.target.value)
180
+ }}
101
181
  />
102
182
  <IconButton
103
183
  data-fs-security-drawer-input-password-toggle
@@ -110,6 +190,18 @@ export const SecurityDrawer = ({ isOpen, onClose }: SecurityDrawerProps) => {
110
190
  />
111
191
  </div>
112
192
 
193
+ {formError && (
194
+ <div data-fs-security-drawer-error>
195
+ <Icon
196
+ width={20}
197
+ height={20}
198
+ name="CircleWavyWarning"
199
+ data-fs-security-drawer-error-icon
200
+ />
201
+ <span>{formError}</span>
202
+ </div>
203
+ )}
204
+
113
205
  {newPassword.length > 0 && (
114
206
  <div data-fs-security-drawer-input-password-rules-container>
115
207
  <p data-fs-security-drawer-input-password-rules-title>
@@ -145,12 +237,9 @@ export const SecurityDrawer = ({ isOpen, onClose }: SecurityDrawerProps) => {
145
237
  <Button
146
238
  data-fs-security-drawer-footer-button
147
239
  variant="primary"
148
- disabled={!currentPassword || !newPassword || !allValid}
149
- onClick={() => {
150
- // TODO: Handle password save logic here
151
- console.log('Saving new password')
152
- handleClose()
153
- }}
240
+ loading={loading}
241
+ disabled={loading || !currentPassword || !newPassword || !allValid}
242
+ onClick={handleSetPassword}
154
243
  >
155
244
  Save Password
156
245
  </Button>
@@ -4,12 +4,19 @@ import { Button } from '@faststore/ui'
4
4
  import { SecurityDrawer } from './SecurityDrawer'
5
5
  import styles from './styles.module.scss'
6
6
 
7
- export const SecuritySection = () => {
7
+ type SecuritySectionProps = { userEmail: string; accountName?: string }
8
+
9
+ export const SecuritySection = ({
10
+ userEmail,
11
+ accountName,
12
+ }: SecuritySectionProps) => {
8
13
  const [isDrawerOpen, setIsDrawerOpen] = useState(false)
9
14
  return (
10
15
  <>
11
16
  {isDrawerOpen && (
12
17
  <SecurityDrawer
18
+ userEmail={userEmail}
19
+ accountName={accountName}
13
20
  isOpen={isDrawerOpen}
14
21
  onClose={() => setIsDrawerOpen(false)}
15
22
  />
@@ -231,6 +231,21 @@
231
231
  padding-inline: var(--fs-spacing-1);
232
232
  }
233
233
 
234
+ [data-fs-security-drawer-error] {
235
+ display: flex;
236
+ gap: var(--fs-spacing-1);
237
+ align-items: center;
238
+ font-size: var(--fs-text-size-1);
239
+ font-weight: var(--fs-text-weight-regular);
240
+ color: var(--fs-color-danger-text);
241
+ background-color: var(--fs-color-danger-background);
242
+ border-radius: var(--fs-border-radius);
243
+ }
244
+
245
+ [data-fs-security-drawer-error-icon] {
246
+ flex-shrink: 0;
247
+ }
248
+
234
249
  @include media("<notebook") {
235
250
  [data-fs-security-header] {
236
251
  padding-inline: var(--fs-spacing-4);
@@ -15,8 +15,8 @@ import { getGlobalSectionsData } from 'src/components/cms/GlobalSections'
15
15
 
16
16
  import { gql } from '@generated/gql'
17
17
  import type {
18
- ServerSecurityQueryQuery,
19
- ServerSecurityQueryQueryVariables,
18
+ ServerSecurityQuery,
19
+ ServerSecurityQueryVariables,
20
20
  } from '@generated/graphql'
21
21
  import { default as AfterSection } from 'src/customizations/src/myAccount/extensions/security/after'
22
22
  import { default as BeforeSection } from 'src/customizations/src/myAccount/extensions/security/before'
@@ -40,12 +40,14 @@ const COMPONENTS: Record<string, ComponentType<any>> = {
40
40
 
41
41
  type SecurityPageProps = {
42
42
  accountName: string
43
+ userEmail: string
43
44
  } & MyAccountProps
44
45
 
45
46
  export default function Page({
46
47
  globalSections: globalSectionsProp,
47
48
  accountName,
48
49
  isRepresentative,
50
+ userEmail,
49
51
  }: SecurityPageProps) {
50
52
  const { sections: globalSections, settings: globalSettings } =
51
53
  globalSectionsProp ?? {}
@@ -60,7 +62,7 @@ export default function Page({
60
62
  accountName={accountName}
61
63
  >
62
64
  <BeforeSection />
63
- <SecuritySection />
65
+ <SecuritySection userEmail={userEmail} />
64
66
  <AfterSection />
65
67
  </MyAccountLayout>
66
68
  </RenderSections>
@@ -69,8 +71,11 @@ export default function Page({
69
71
  }
70
72
 
71
73
  const query = gql(`
72
- query ServerSecurityQuery {
74
+ query ServerSecurity {
73
75
  accountName
76
+ userDetails {
77
+ email
78
+ }
74
79
  }
75
80
  `)
76
81
 
@@ -111,7 +116,7 @@ export const getServerSideProps: GetServerSideProps<
111
116
 
112
117
  const [security, globalSections, globalSectionsHeader, globalSectionsFooter] =
113
118
  await Promise.all([
114
- execute<ServerSecurityQueryQueryVariables, ServerSecurityQueryQuery>(
119
+ execute<ServerSecurityQueryVariables, ServerSecurityQuery>(
115
120
  {
116
121
  variables: {},
117
122
  operation: query,
@@ -144,8 +149,9 @@ export const getServerSideProps: GetServerSideProps<
144
149
 
145
150
  return {
146
151
  props: {
147
- globalSections: globalSectionsResult,
148
152
  accountName: security.data.accountName,
153
+ userEmail: security.data?.userDetails.email || '',
154
+ globalSections: globalSectionsResult,
149
155
  isRepresentative,
150
156
  },
151
157
  }
@@ -0,0 +1,146 @@
1
+ import { useState, useCallback } from 'react'
2
+ import fetch from 'isomorphic-unfetch'
3
+ import { buildFormData } from 'src/utils/utilities'
4
+ import config from '../../../discovery.config'
5
+
6
+ type SetPasswordInput = {
7
+ userEmail: string
8
+ newPassword: string
9
+ currentPassword: string
10
+ accesskey?: string
11
+ recaptcha?: string
12
+ }
13
+
14
+ type SetPasswordState = {
15
+ error: Error | null
16
+ loading: boolean
17
+ }
18
+
19
+ type SetPasswordResultType = {
20
+ authStatus?: string
21
+ message?: string
22
+ }
23
+
24
+ export const useSetPassword = (accountName?: string) => {
25
+ const [state, setState] = useState<SetPasswordState>({
26
+ error: null,
27
+ loading: false,
28
+ })
29
+
30
+ const setPassword = useCallback(async (input: SetPasswordInput) => {
31
+ setState((prev) => ({ ...prev, loading: true, error: null }))
32
+
33
+ try {
34
+ await startLogin({ email: input.userEmail, accountName })
35
+
36
+ const body = {
37
+ login: input.userEmail,
38
+ currentPassword: input.currentPassword,
39
+ newPassword: input.newPassword,
40
+ accesskey: !input.accesskey ? null : input.accesskey,
41
+ recaptcha: !input.recaptcha ? null : input.recaptcha,
42
+ }
43
+
44
+ const response = await fetch(
45
+ `/api/vtexid/pub/authentication/classic/setpassword?expireSessions=true`,
46
+ {
47
+ method: 'POST',
48
+ body: buildFormData(body),
49
+ credentials: 'include',
50
+ }
51
+ )
52
+
53
+ if (!response.ok) {
54
+ throw new Error(
55
+ `Failed to set password: ${response.status} ${response.statusText}`
56
+ )
57
+ }
58
+
59
+ const result: SetPasswordResultType = (await response.json()) ?? {
60
+ authStatus: 'Unexpected error',
61
+ message: 'Unexpected error while setting password',
62
+ }
63
+
64
+ if (!result) {
65
+ const fallback = {
66
+ success: false,
67
+ message: 'No response from set password API',
68
+ }
69
+
70
+ setState({ loading: false, error: new Error(fallback.message) })
71
+
72
+ return fallback
73
+ }
74
+
75
+ setState({ error: null, loading: false })
76
+
77
+ return {
78
+ success: result?.authStatus
79
+ ? result?.authStatus.toLowerCase() === 'success'
80
+ : false,
81
+ message: 'Password set successfully',
82
+ }
83
+ } catch (err) {
84
+ console.error('Error setting password:', err)
85
+
86
+ const authStatus =
87
+ typeof err === 'object' && err !== null && 'authStatus' in err
88
+ ? String(err.authStatus)
89
+ : 'Unexpected error'
90
+
91
+ const isInvalidCredentials =
92
+ authStatus.toLowerCase().includes('invalidemail') ||
93
+ authStatus.toLowerCase().includes('invalidpassword')
94
+
95
+ const errorResult = {
96
+ success: false,
97
+ message: isInvalidCredentials
98
+ ? 'Invalid email or password'
99
+ : 'Unexpected error while setting password',
100
+ }
101
+
102
+ setState({ error: new Error('Failed to set password'), loading: false })
103
+ return errorResult
104
+ } finally {
105
+ setState((prev) => ({ ...prev, loading: false }))
106
+ }
107
+ }, [])
108
+
109
+ return {
110
+ setPassword,
111
+ error: state.error,
112
+ loading: state.loading,
113
+ }
114
+ }
115
+
116
+ const startLogin = async ({
117
+ email,
118
+ accountName,
119
+ }: {
120
+ email: string
121
+ accountName?: string
122
+ }) => {
123
+ try {
124
+ const response = await fetch(`/api/vtexid/pub/authentication/startlogin`, {
125
+ method: 'POST',
126
+ credentials: 'include',
127
+ body: buildFormData({
128
+ user: email,
129
+ scope: accountName ?? config.api.storeId,
130
+ accountName: accountName ?? config.api.storeId,
131
+ returnUrl: '/',
132
+ callbackUrl: '/',
133
+ fingerprint: null,
134
+ }),
135
+ })
136
+
137
+ if (!response.ok) {
138
+ throw {
139
+ response: {},
140
+ }
141
+ }
142
+ } catch (error) {
143
+ console.error('Error starting login:', error)
144
+ throw error
145
+ }
146
+ }