@vtex/faststore 0.0.3 → 0.0.4

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.
@@ -0,0 +1,856 @@
1
+ "use client";
2
+ import { defineComponent } from "../../core/dist/define-component.js";
3
+ import "../../core/dist/index.js";
4
+ import { Field } from "../../particles/field/field.js";
5
+ import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
6
+ import { useDataOverride } from "../../sdk/data/data-override-context.js";
7
+ import { useSession } from "../session/session.js";
8
+ import { Input } from "../../particles/input/input.js";
9
+ import { PhoneField, applyPhoneMask, toE164, validatePhoneNumber } from "../../particles/phone-field/phone-field.js";
10
+ import { DocumentField, isSupportedDocumentType, isValidDocument, maskDocumentValue } from "../../particles/document-field/document-field.js";
11
+ import { Select } from "../../particles/select/select.js";
12
+ import { useToast } from "../../particles/toast/toast.js";
13
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
14
+ import { jsx, jsxs } from "react/jsx-runtime";
15
+ //#region src/matter/my-account-profile/my-account-profile.tsx
16
+ /**
17
+ * The `my-account-profile` compound (specs/sfs-3287-my-account-profile-root.md) — the data root
18
+ * plus form mechanics for the shopper's persistent account profile. `Root` owns the saved profile,
19
+ * the draft, the view↔edit toggle, and the single batched Save; it publishes a context whose
20
+ * registration API is the seam every field Control attaches to.
21
+ *
22
+ * Unlike `ClientProfileFormRoot`, the Root does not enumerate the field set: a Control registers
23
+ * its key plus an optional validator and normalizer, and the Root derives which fields to validate,
24
+ * which to send, and which to focus from the live registry only. A field absent from the
25
+ * composition is absent from the payload by construction (Decision 1).
26
+ */
27
+ const fragment = `
28
+ fragment MyAccountProfileData on Query {
29
+ customerProfile {
30
+ __typename
31
+ id
32
+ firstName
33
+ lastName
34
+ email
35
+ homePhone
36
+ mobilePhone
37
+ document
38
+ documentType
39
+ birthDate
40
+ gender
41
+ }
42
+ }
43
+ `;
44
+ const FIELD_KEYS = [
45
+ "firstName",
46
+ "lastName",
47
+ "homePhone",
48
+ "mobilePhone",
49
+ "document",
50
+ "birthDate",
51
+ "gender"
52
+ ];
53
+ const DEFAULT_SAVE_FAILED_MESSAGE = "Unable to save your profile. Please try again.";
54
+ const DEFAULT_SAVED_MESSAGE = "Your profile has been saved.";
55
+ function buildSavedValues($data) {
56
+ return {
57
+ firstName: $data?.firstName ?? "",
58
+ lastName: $data?.lastName ?? "",
59
+ homePhone: $data?.homePhone ?? "",
60
+ mobilePhone: $data?.mobilePhone ?? "",
61
+ document: $data?.document ?? "",
62
+ birthDate: $data?.birthDate ?? "",
63
+ gender: $data?.gender ?? ""
64
+ };
65
+ }
66
+ /**
67
+ * Native `<input type="date">` values and the profile's stored `birthDate` are both `yyyy-mm-dd`.
68
+ * `toNativeDate` stays defensive against a `yyyy-mm-ddTHH:mm:ss...` upstream value slipping through
69
+ * unnormalized — the resolver already strips the time component, this is a second line of defense —
70
+ * since the native input rejects anything but the date-only form outright.
71
+ */
72
+ function toNativeDate(stored) {
73
+ const [datePart] = stored.split("T");
74
+ return datePart ?? "";
75
+ }
76
+ function toStoredDate(native) {
77
+ return native;
78
+ }
79
+ const DEFAULT_GENDER_OPTIONS = [
80
+ {
81
+ value: "female",
82
+ label: "Female"
83
+ },
84
+ {
85
+ value: "male",
86
+ label: "Male"
87
+ },
88
+ {
89
+ value: "other",
90
+ label: "Other"
91
+ },
92
+ {
93
+ value: "prefer_not_to_say",
94
+ label: "Prefer not to say"
95
+ }
96
+ ];
97
+ const MY_ACCOUNT_PROFILE_UNSET = Symbol("MyAccountProfileContextUnset");
98
+ const MyAccountProfileContext = createContext(MY_ACCOUNT_PROFILE_UNSET);
99
+ /** Throws when used outside `MyAccountProfileRoot`, per ADR-0009. */
100
+ function useMyAccountProfile() {
101
+ const ctx = useContext(MyAccountProfileContext);
102
+ if (ctx === MY_ACCOUNT_PROFILE_UNSET) throw new Error("MyAccountProfile components must be used inside MyAccountProfileRoot.");
103
+ return ctx;
104
+ }
105
+ const MyAccountProfileRootDefinition = defineComponent({
106
+ name: "MyAccountProfileRoot",
107
+ importPath: "@vtex/faststore/components",
108
+ component: MyAccountProfileRoot,
109
+ fragment,
110
+ fetchStrategy: "reactive",
111
+ requiresContext: "session",
112
+ providesContext: "my-account-profile",
113
+ actions: { identity: ["customerProfileUpdate"] },
114
+ skeleton: MyAccountProfileSkeleton,
115
+ editor: {
116
+ label: "My Account Profile Root",
117
+ category: "identity"
118
+ },
119
+ schema: {
120
+ documentType: {
121
+ type: "string",
122
+ label: "Default document type (e.g. CPF)"
123
+ },
124
+ saveFailedMessage: {
125
+ type: "string",
126
+ default: DEFAULT_SAVE_FAILED_MESSAGE,
127
+ label: "Save failure message"
128
+ },
129
+ savedMessage: {
130
+ type: "string",
131
+ default: DEFAULT_SAVED_MESSAGE,
132
+ label: "Save success message"
133
+ }
134
+ },
135
+ guideline: "Data root + form mechanics for the persistent My Account profile: owns the saved profile, the draft, the view/edit toggle, and the single batched Save (dispatches only dirty, registered fields; an empty string erases, an omitted field is untouched). Self-gates on the authenticated (loggedIn) session; requires a SessionRoot ancestor and a mounted ToastProvider ancestor — Save success/failure surface as a toast, not an inline part. Field Controls register their key plus an optional validator and normalizer through useMyAccountProfile() — the field set is a merchant composition decision, never enumerated here. Compose EditButton (view state), SaveButton/CancelButton (edit state), and Email as children in any order. See my-account-profile.md."
136
+ });
137
+ function MyAccountProfileRoot({ $data: $dataProp, $actions: $actionsProp, documentType: documentTypeProp, saveFailedMessage = DEFAULT_SAVE_FAILED_MESSAGE, savedMessage = DEFAULT_SAVED_MESSAGE, className, children }) {
138
+ const { state } = useSession();
139
+ const toast = useToast();
140
+ const $data = useDataOverride($dataProp);
141
+ const $actions = useActionsOverride($actionsProp);
142
+ const resolved = $data != null;
143
+ const savedValues = useMemo(() => buildSavedValues($data), [$data]);
144
+ const savedEmail = $data?.email ?? "";
145
+ const savedDocumentType = $data?.documentType?.toUpperCase();
146
+ const activeDocumentType = savedDocumentType && isSupportedDocumentType(savedDocumentType) ? savedDocumentType : documentTypeProp;
147
+ const [mode, setMode] = useState("view");
148
+ const [draft, setDraft] = useState(savedValues);
149
+ const [fieldErrors, setFieldErrors] = useState({});
150
+ const [pending, setPending] = useState(false);
151
+ const [registry, setRegistry] = useState(() => /* @__PURE__ */ new Map());
152
+ const formRef = useRef(null);
153
+ const [prevSaved, setPrevSaved] = useState(savedValues);
154
+ const changedKeys = FIELD_KEYS.filter((key) => prevSaved[key] !== savedValues[key]);
155
+ if (changedKeys.length > 0) {
156
+ setPrevSaved(savedValues);
157
+ setDraft((prevDraft) => {
158
+ let next = prevDraft;
159
+ for (const key of changedKeys) {
160
+ if (prevDraft[key] !== prevSaved[key]) continue;
161
+ if (next === prevDraft) next = { ...prevDraft };
162
+ next[key] = savedValues[key];
163
+ }
164
+ return next;
165
+ });
166
+ }
167
+ const registerField = useCallback((registration) => {
168
+ setRegistry((prev) => {
169
+ const next = new Map(prev);
170
+ next.set(registration.key, registration);
171
+ return next;
172
+ });
173
+ return () => {
174
+ setRegistry((prev) => {
175
+ if (!prev.has(registration.key)) return prev;
176
+ const next = new Map(prev);
177
+ next.delete(registration.key);
178
+ return next;
179
+ });
180
+ };
181
+ }, []);
182
+ const setField = useCallback((key, value) => {
183
+ setDraft((prev) => ({
184
+ ...prev,
185
+ [key]: value
186
+ }));
187
+ setFieldErrors((prev) => {
188
+ if (!prev[key]) return prev;
189
+ const next = { ...prev };
190
+ delete next[key];
191
+ return next;
192
+ });
193
+ }, []);
194
+ const edit = useCallback(() => {
195
+ setDraft(savedValues);
196
+ setFieldErrors({});
197
+ setMode("edit");
198
+ }, [savedValues]);
199
+ const cancel = useCallback(() => {
200
+ setDraft(savedValues);
201
+ setFieldErrors({});
202
+ setMode("view");
203
+ }, [savedValues]);
204
+ function focusFirstInvalid(errors) {
205
+ const wrappers = formRef.current?.querySelectorAll("[data-fs-my-account-profile-field]");
206
+ if (!wrappers) return;
207
+ for (const wrapper of wrappers) {
208
+ const key = wrapper.dataset.fsMyAccountProfileField;
209
+ if (key && errors[key]) {
210
+ (wrapper.querySelector("input, select, textarea") ?? wrapper).focus();
211
+ return;
212
+ }
213
+ }
214
+ }
215
+ async function handleSubmit(event) {
216
+ event.preventDefault();
217
+ if (pending) return;
218
+ const dirtyKeys = Array.from(registry.keys()).filter((key) => draft[key] !== savedValues[key]);
219
+ if (dirtyKeys.length === 0) {
220
+ setFieldErrors({});
221
+ setMode("view");
222
+ toast.add({
223
+ title: savedMessage,
224
+ type: "success"
225
+ });
226
+ return;
227
+ }
228
+ const errors = {};
229
+ for (const key of dirtyKeys) {
230
+ const message = registry.get(key)?.validate?.(draft[key]);
231
+ if (message) errors[key] = message;
232
+ }
233
+ if (Object.keys(errors).length > 0) {
234
+ setFieldErrors(errors);
235
+ focusFirstInvalid(errors);
236
+ return;
237
+ }
238
+ setFieldErrors({});
239
+ const identity = $actions?.identity;
240
+ if (!identity?.customerProfileUpdate) {
241
+ toast.add({
242
+ title: saveFailedMessage,
243
+ type: "error"
244
+ });
245
+ return;
246
+ }
247
+ const payload = {};
248
+ for (const key of dirtyKeys) {
249
+ const registration = registry.get(key);
250
+ const rawValue = draft[key];
251
+ payload[key] = registration?.normalize ? registration.normalize(rawValue) : rawValue;
252
+ }
253
+ setPending(true);
254
+ try {
255
+ const userErrors = await identity.customerProfileUpdate(payload);
256
+ if (userErrors.length > 0) {
257
+ toast.add({
258
+ title: userErrors[0]?.message ?? saveFailedMessage,
259
+ type: "error"
260
+ });
261
+ return;
262
+ }
263
+ setMode("view");
264
+ toast.add({
265
+ title: savedMessage,
266
+ type: "success"
267
+ });
268
+ } catch (error) {
269
+ console.error("[MyAccountProfileRoot] customerProfileUpdate failed", error instanceof Error ? error.name : "UnknownError");
270
+ toast.add({
271
+ title: saveFailedMessage,
272
+ type: "error"
273
+ });
274
+ } finally {
275
+ setPending(false);
276
+ }
277
+ }
278
+ const contextValue = useMemo(() => ({
279
+ mode,
280
+ savedValues,
281
+ draft,
282
+ fieldErrors,
283
+ email: savedEmail,
284
+ activeDocumentType,
285
+ pending,
286
+ registerField,
287
+ setField,
288
+ edit,
289
+ cancel
290
+ }), [
291
+ mode,
292
+ savedValues,
293
+ draft,
294
+ fieldErrors,
295
+ savedEmail,
296
+ activeDocumentType,
297
+ pending,
298
+ registerField,
299
+ setField,
300
+ edit,
301
+ cancel
302
+ ]);
303
+ if (state !== "loggedIn") return null;
304
+ if (!resolved) return /* @__PURE__ */ jsx(MyAccountProfileSkeleton, { className });
305
+ return /* @__PURE__ */ jsx(MyAccountProfileContext, {
306
+ value: contextValue,
307
+ children: /* @__PURE__ */ jsx("form", {
308
+ ref: formRef,
309
+ onSubmit: handleSubmit,
310
+ className,
311
+ "data-fs-my-account-profile": "",
312
+ "data-fs-my-account-profile-mode": mode,
313
+ ...pending ? { "data-fs-my-account-profile-pending": "" } : {},
314
+ children
315
+ })
316
+ });
317
+ }
318
+ const MyAccountProfileEditButtonDefinition = defineComponent({
319
+ name: "MyAccountProfileEditButton",
320
+ importPath: "@vtex/faststore/components",
321
+ component: MyAccountProfileEditButton,
322
+ requiresContext: "my-account-profile",
323
+ editor: {
324
+ label: "My Account Profile Edit Button",
325
+ category: "identity"
326
+ },
327
+ schema: { label: {
328
+ type: "string",
329
+ default: "Edit",
330
+ label: "Button label"
331
+ } },
332
+ guideline: "Switches MyAccountProfileRoot from the view state to the edit state. Renders only in the view state. Must be a descendant of MyAccountProfileRoot."
333
+ });
334
+ function MyAccountProfileEditButton({ label = "Edit", className, children }) {
335
+ const { mode, edit } = useMyAccountProfile();
336
+ if (mode !== "view") return null;
337
+ return /* @__PURE__ */ jsx("button", {
338
+ type: "button",
339
+ onClick: edit,
340
+ className,
341
+ "data-fs-my-account-profile-edit": "",
342
+ children: children ?? label
343
+ });
344
+ }
345
+ const MyAccountProfileSaveButtonDefinition = defineComponent({
346
+ name: "MyAccountProfileSaveButton",
347
+ importPath: "@vtex/faststore/components",
348
+ component: MyAccountProfileSaveButton,
349
+ requiresContext: "my-account-profile",
350
+ editor: {
351
+ label: "My Account Profile Save Button",
352
+ category: "identity"
353
+ },
354
+ schema: { label: {
355
+ type: "string",
356
+ default: "Save",
357
+ label: "Button label"
358
+ } },
359
+ guideline: "Submits MyAccountProfileRoot's batched Save. Renders only in the edit state; disabled while the write is pending. Must be a descendant of MyAccountProfileRoot."
360
+ });
361
+ function MyAccountProfileSaveButton({ label = "Save", className, children }) {
362
+ const { mode, pending } = useMyAccountProfile();
363
+ if (mode !== "edit") return null;
364
+ return /* @__PURE__ */ jsx("button", {
365
+ type: "submit",
366
+ disabled: pending,
367
+ className,
368
+ "data-fs-my-account-profile-save": "",
369
+ children: children ?? label
370
+ });
371
+ }
372
+ const MyAccountProfileCancelButtonDefinition = defineComponent({
373
+ name: "MyAccountProfileCancelButton",
374
+ importPath: "@vtex/faststore/components",
375
+ component: MyAccountProfileCancelButton,
376
+ requiresContext: "my-account-profile",
377
+ editor: {
378
+ label: "My Account Profile Cancel Button",
379
+ category: "identity"
380
+ },
381
+ schema: { label: {
382
+ type: "string",
383
+ default: "Cancel",
384
+ label: "Button label"
385
+ } },
386
+ guideline: "Reverts every field to its last saved value and returns MyAccountProfileRoot to the view state, without persisting. Renders only in the edit state; disabled while the write is pending. Must be a descendant of MyAccountProfileRoot."
387
+ });
388
+ function MyAccountProfileCancelButton({ label = "Cancel", className, children }) {
389
+ const { mode, pending, cancel } = useMyAccountProfile();
390
+ if (mode !== "edit") return null;
391
+ return /* @__PURE__ */ jsx("button", {
392
+ type: "button",
393
+ disabled: pending,
394
+ onClick: cancel,
395
+ className,
396
+ "data-fs-my-account-profile-cancel": "",
397
+ children: children ?? label
398
+ });
399
+ }
400
+ const MyAccountProfileEmailDefinition = defineComponent({
401
+ name: "MyAccountProfileEmail",
402
+ importPath: "@vtex/faststore/components",
403
+ component: MyAccountProfileEmail,
404
+ requiresContext: "my-account-profile",
405
+ editor: {
406
+ label: "My Account Profile Email",
407
+ category: "identity"
408
+ },
409
+ schema: {},
410
+ guideline: "The shopper's read-only login identifier, or nothing when absent. Never editable — no edit-state rendering exists for it. Must be a descendant of MyAccountProfileRoot."
411
+ });
412
+ function MyAccountProfileEmail({ className }) {
413
+ const { email } = useMyAccountProfile();
414
+ if (!email) return null;
415
+ return /* @__PURE__ */ jsx("p", {
416
+ className,
417
+ "data-fs-my-account-profile-email": "",
418
+ children: email
419
+ });
420
+ }
421
+ const MyAccountProfileSkeletonDefinition = defineComponent({
422
+ name: "MyAccountProfileSkeleton",
423
+ importPath: "@vtex/faststore/components",
424
+ component: MyAccountProfileSkeleton,
425
+ editor: {
426
+ label: "My Account Profile Skeleton",
427
+ category: "identity"
428
+ },
429
+ schema: {},
430
+ guideline: "Loading placeholder rendered by MyAccountProfileRoot while $data is unresolved. Declares no context requirement — it renders before the Root has anything to provide."
431
+ });
432
+ function MyAccountProfileSkeleton({ className, ...props }) {
433
+ return /* @__PURE__ */ jsxs("div", {
434
+ ...props,
435
+ className,
436
+ "data-fs-my-account-profile-skeleton": "",
437
+ "aria-hidden": "true",
438
+ children: [
439
+ /* @__PURE__ */ jsx("span", { "data-fs-my-account-profile-skeleton-line": "" }),
440
+ /* @__PURE__ */ jsx("span", { "data-fs-my-account-profile-skeleton-line": "" }),
441
+ /* @__PURE__ */ jsx("span", { "data-fs-my-account-profile-skeleton-line": "" })
442
+ ]
443
+ });
444
+ }
445
+ MyAccountProfileRoot.Skeleton = MyAccountProfileSkeleton;
446
+ const MyAccountProfileFirstNameControlDefinition = defineComponent({
447
+ name: "MyAccountProfileFirstNameControl",
448
+ importPath: "@vtex/faststore/components",
449
+ component: MyAccountProfileFirstNameControl,
450
+ requiresContext: "my-account-profile",
451
+ editor: {
452
+ label: "My Account Profile First Name Control",
453
+ category: "identity"
454
+ },
455
+ schema: {
456
+ label: {
457
+ type: "string",
458
+ default: "First name",
459
+ label: "Label"
460
+ },
461
+ requiredMessage: {
462
+ type: "string",
463
+ default: "First name is required.",
464
+ label: "Required message"
465
+ }
466
+ },
467
+ guideline: "Draft-bound, required first-name field. Blocks Save with an inline message and focus when left empty. Must be a descendant of MyAccountProfileRoot."
468
+ });
469
+ function MyAccountProfileFirstNameControl({ label = "First name", requiredMessage = "First name is required.", className }) {
470
+ const { mode, draft, savedValues, fieldErrors, pending, registerField, setField } = useMyAccountProfile();
471
+ useEffect(() => {
472
+ return registerField({
473
+ key: "firstName",
474
+ validate: (value) => value.trim() ? null : requiredMessage
475
+ });
476
+ }, [requiredMessage, registerField]);
477
+ const error = fieldErrors.firstName;
478
+ if (mode === "view") return /* @__PURE__ */ jsx("p", {
479
+ className,
480
+ "data-fs-my-account-profile-first-name": "",
481
+ "data-fs-my-account-profile-field": "firstName",
482
+ children: savedValues.firstName
483
+ });
484
+ return /* @__PURE__ */ jsxs(Field.Root, {
485
+ className,
486
+ invalid: !!error,
487
+ disabled: pending,
488
+ "data-fs-my-account-profile-first-name": "",
489
+ "data-fs-my-account-profile-field": "firstName",
490
+ children: [
491
+ /* @__PURE__ */ jsx(Field.Label, { children: label }),
492
+ /* @__PURE__ */ jsx(Field.Control, {
493
+ value: draft.firstName,
494
+ onChange: (event) => setField("firstName", event.target.value),
495
+ disabled: pending
496
+ }),
497
+ error ? /* @__PURE__ */ jsx(Field.Error, {
498
+ match: true,
499
+ children: error
500
+ }) : null
501
+ ]
502
+ });
503
+ }
504
+ const MyAccountProfileLastNameControlDefinition = defineComponent({
505
+ name: "MyAccountProfileLastNameControl",
506
+ importPath: "@vtex/faststore/components",
507
+ component: MyAccountProfileLastNameControl,
508
+ requiresContext: "my-account-profile",
509
+ editor: {
510
+ label: "My Account Profile Last Name Control",
511
+ category: "identity"
512
+ },
513
+ schema: {
514
+ label: {
515
+ type: "string",
516
+ default: "Last name",
517
+ label: "Label"
518
+ },
519
+ requiredMessage: {
520
+ type: "string",
521
+ default: "Last name is required.",
522
+ label: "Required message"
523
+ }
524
+ },
525
+ guideline: "Draft-bound, required last-name field. Blocks Save with an inline message and focus when left empty. Must be a descendant of MyAccountProfileRoot."
526
+ });
527
+ function MyAccountProfileLastNameControl({ label = "Last name", requiredMessage = "Last name is required.", className }) {
528
+ const { mode, draft, savedValues, fieldErrors, pending, registerField, setField } = useMyAccountProfile();
529
+ useEffect(() => {
530
+ return registerField({
531
+ key: "lastName",
532
+ validate: (value) => value.trim() ? null : requiredMessage
533
+ });
534
+ }, [requiredMessage, registerField]);
535
+ const error = fieldErrors.lastName;
536
+ if (mode === "view") return /* @__PURE__ */ jsx("p", {
537
+ className,
538
+ "data-fs-my-account-profile-last-name": "",
539
+ "data-fs-my-account-profile-field": "lastName",
540
+ children: savedValues.lastName
541
+ });
542
+ return /* @__PURE__ */ jsxs(Field.Root, {
543
+ className,
544
+ invalid: !!error,
545
+ disabled: pending,
546
+ "data-fs-my-account-profile-last-name": "",
547
+ "data-fs-my-account-profile-field": "lastName",
548
+ children: [
549
+ /* @__PURE__ */ jsx(Field.Label, { children: label }),
550
+ /* @__PURE__ */ jsx(Field.Control, {
551
+ value: draft.lastName,
552
+ onChange: (event) => setField("lastName", event.target.value),
553
+ disabled: pending
554
+ }),
555
+ error ? /* @__PURE__ */ jsx(Field.Error, {
556
+ match: true,
557
+ children: error
558
+ }) : null
559
+ ]
560
+ });
561
+ }
562
+ const MyAccountProfilePhoneControlDefinition = defineComponent({
563
+ name: "MyAccountProfilePhoneControl",
564
+ importPath: "@vtex/faststore/components",
565
+ component: MyAccountProfilePhoneControl,
566
+ requiresContext: "my-account-profile",
567
+ editor: {
568
+ label: "My Account Profile Phone Control",
569
+ category: "identity"
570
+ },
571
+ schema: {
572
+ label: {
573
+ type: "string",
574
+ default: "Phone",
575
+ label: "Label"
576
+ },
577
+ field: {
578
+ type: ["homePhone", "mobilePhone"],
579
+ default: "mobilePhone",
580
+ label: "Bound field"
581
+ },
582
+ country: {
583
+ type: ["US", "BR"],
584
+ default: "US",
585
+ label: "Country"
586
+ },
587
+ invalidMessage: {
588
+ type: "string",
589
+ default: "Invalid phone number",
590
+ label: "Invalid message"
591
+ }
592
+ },
593
+ guideline: "Draft-bound, optional phone field bound to either homePhone or mobilePhone via the field prop (Decision 1) — compose it twice to expose both numbers. Normalizes to E.164 on the Root's Save. Absent from the composition means absent from the payload. Must be a descendant of MyAccountProfileRoot."
594
+ });
595
+ function MyAccountProfilePhoneControl({ label = "Phone", field = "mobilePhone", country = "US", invalidMessage = "Invalid phone number", className }) {
596
+ const { mode, draft, savedValues, fieldErrors, pending, registerField, setField } = useMyAccountProfile();
597
+ useEffect(() => {
598
+ return registerField({
599
+ key: field,
600
+ validate: (value) => value.trim() && !validatePhoneNumber(value, country) ? invalidMessage : null,
601
+ normalize: (value) => value.trim() ? toE164(value, country) : value
602
+ });
603
+ }, [
604
+ field,
605
+ country,
606
+ invalidMessage,
607
+ registerField
608
+ ]);
609
+ const error = fieldErrors[field];
610
+ if (mode === "view") return /* @__PURE__ */ jsx("p", {
611
+ className,
612
+ "data-fs-my-account-profile-phone": field,
613
+ "data-fs-my-account-profile-field": field,
614
+ children: savedValues[field] ? applyPhoneMask(savedValues[field], country) : ""
615
+ });
616
+ return /* @__PURE__ */ jsx("div", {
617
+ "data-fs-my-account-profile-field": field,
618
+ children: /* @__PURE__ */ jsx(PhoneField, {
619
+ className,
620
+ label,
621
+ country,
622
+ requirement: "optional",
623
+ value: draft[field],
624
+ onValueChange: (value) => setField(field, value),
625
+ invalid: !!error,
626
+ error: error ?? void 0,
627
+ errorInvalid: invalidMessage,
628
+ isDisabled: pending,
629
+ "data-fs-my-account-profile-phone": field
630
+ })
631
+ });
632
+ }
633
+ const MyAccountProfileTaxIdControlDefinition = defineComponent({
634
+ name: "MyAccountProfileTaxIdControl",
635
+ importPath: "@vtex/faststore/components",
636
+ component: MyAccountProfileTaxIdControl,
637
+ requiresContext: "my-account-profile",
638
+ editor: {
639
+ label: "My Account Profile Tax ID Control",
640
+ category: "identity"
641
+ },
642
+ schema: {
643
+ label: {
644
+ type: "string",
645
+ default: "National tax ID",
646
+ label: "Label"
647
+ },
648
+ invalidMessage: {
649
+ type: "string",
650
+ default: "Invalid document number.",
651
+ label: "Invalid message"
652
+ },
653
+ lockedReason: {
654
+ type: "string",
655
+ default: "Your document ID can't be edited.",
656
+ label: "Locked reason"
657
+ }
658
+ },
659
+ guideline: "Draft-bound, optional national tax-ID field (CPF in V0). Self-gates on the Root's active document type. A saved value renders as a disabled, locked input in the edit state rather than disappearing (Decision 4); an unsaved value renders as an editable field. Must be a descendant of MyAccountProfileRoot."
660
+ });
661
+ function MyAccountProfileTaxIdControl({ label = "National tax ID", invalidMessage = "Invalid document number.", lockedReason = "Your document ID can't be edited.", className }) {
662
+ const { mode, draft, savedValues, fieldErrors, pending, activeDocumentType, registerField, setField } = useMyAccountProfile();
663
+ const documentType = activeDocumentType && isSupportedDocumentType(activeDocumentType) ? activeDocumentType : void 0;
664
+ const hasSavedDocument = savedValues.document.trim() !== "";
665
+ useEffect(() => {
666
+ if (!documentType || hasSavedDocument) return;
667
+ return registerField({
668
+ key: "document",
669
+ validate: (value) => value.trim() && !isValidDocument(value, documentType) ? invalidMessage : null,
670
+ normalize: (value) => value.replaceAll(/\D/g, "")
671
+ });
672
+ }, [
673
+ documentType,
674
+ hasSavedDocument,
675
+ invalidMessage,
676
+ registerField
677
+ ]);
678
+ if (!documentType) return null;
679
+ const error = fieldErrors.document;
680
+ if (mode === "view") return /* @__PURE__ */ jsx("p", {
681
+ className,
682
+ "data-fs-my-account-profile-tax-id": "",
683
+ "data-fs-my-account-profile-field": "document",
684
+ children: savedValues.document ? maskDocumentValue(savedValues.document, documentType) : ""
685
+ });
686
+ if (hasSavedDocument) return /* @__PURE__ */ jsxs(Field.Root, {
687
+ className,
688
+ "data-fs-my-account-profile-tax-id": "",
689
+ "data-fs-my-account-profile-field": "document",
690
+ children: [
691
+ /* @__PURE__ */ jsx(Field.Label, { children: label }),
692
+ /* @__PURE__ */ jsx(Field.Control, {
693
+ readOnly: true,
694
+ "aria-disabled": "true",
695
+ title: lockedReason,
696
+ value: maskDocumentValue(savedValues.document, documentType),
697
+ "data-fs-my-account-profile-tax-id-readonly": ""
698
+ }),
699
+ /* @__PURE__ */ jsx(Field.Description, { children: lockedReason })
700
+ ]
701
+ });
702
+ return /* @__PURE__ */ jsx(DocumentField, {
703
+ className,
704
+ label,
705
+ documentType,
706
+ value: draft.document,
707
+ onValueChange: (value) => setField("document", value),
708
+ invalid: !!error,
709
+ errorMessage: error,
710
+ disabled: pending,
711
+ "data-fs-my-account-profile-tax-id": "",
712
+ "data-fs-my-account-profile-field": "document"
713
+ });
714
+ }
715
+ const MyAccountProfileBirthDateControlDefinition = defineComponent({
716
+ name: "MyAccountProfileBirthDateControl",
717
+ importPath: "@vtex/faststore/components",
718
+ component: MyAccountProfileBirthDateControl,
719
+ requiresContext: "my-account-profile",
720
+ editor: {
721
+ label: "My Account Profile Birth Date Control",
722
+ category: "identity"
723
+ },
724
+ schema: { label: {
725
+ type: "string",
726
+ default: "Birth date",
727
+ label: "Label"
728
+ } },
729
+ guideline: "Draft-bound, optional birth-date field over a native date input. Maps the native yyyy-mm-dd value to the stored profile format on read and write. Must be a descendant of MyAccountProfileRoot."
730
+ });
731
+ function MyAccountProfileBirthDateControl({ label = "Birth date", className }) {
732
+ const { mode, draft, savedValues, pending, registerField, setField } = useMyAccountProfile();
733
+ useEffect(() => {
734
+ return registerField({
735
+ key: "birthDate",
736
+ normalize: (value) => toStoredDate(value)
737
+ });
738
+ }, [registerField]);
739
+ if (mode === "view") return /* @__PURE__ */ jsx("p", {
740
+ className,
741
+ "data-fs-my-account-profile-birth-date": "",
742
+ "data-fs-my-account-profile-field": "birthDate",
743
+ children: savedValues.birthDate
744
+ });
745
+ function handleChange(event) {
746
+ setField("birthDate", toStoredDate(event.target.value));
747
+ }
748
+ return /* @__PURE__ */ jsxs(Field.Root, {
749
+ className,
750
+ "data-fs-my-account-profile-birth-date": "",
751
+ "data-fs-my-account-profile-field": "birthDate",
752
+ children: [/* @__PURE__ */ jsx(Field.Label, { children: label }), /* @__PURE__ */ jsx(Input, {
753
+ type: "date",
754
+ value: toNativeDate(draft.birthDate),
755
+ onChange: handleChange,
756
+ disabled: pending
757
+ })]
758
+ });
759
+ }
760
+ const MyAccountProfileGenderControlDefinition = defineComponent({
761
+ name: "MyAccountProfileGenderControl",
762
+ importPath: "@vtex/faststore/components",
763
+ component: MyAccountProfileGenderControl,
764
+ requiresContext: "my-account-profile",
765
+ editor: {
766
+ label: "My Account Profile Gender Control",
767
+ category: "identity"
768
+ },
769
+ schema: {
770
+ label: {
771
+ type: "string",
772
+ default: "Gender",
773
+ label: "Label"
774
+ },
775
+ placeholder: {
776
+ type: "string",
777
+ default: "Select",
778
+ label: "Placeholder"
779
+ },
780
+ clearLabel: {
781
+ type: "string",
782
+ default: "Clear",
783
+ label: "Clear button label"
784
+ }
785
+ },
786
+ guideline: "Draft-bound, optional gender field over a store-configurable set of options (the options prop). Free-string storage — no server-side enum. A Clear button erases a selected value back to empty, since the Select itself offers no such option. Must be a descendant of MyAccountProfileRoot."
787
+ });
788
+ function MyAccountProfileGenderControl({ label = "Gender", placeholder = "Select", clearLabel = "Clear", options = DEFAULT_GENDER_OPTIONS, className }) {
789
+ const { mode, draft, savedValues, pending, registerField, setField } = useMyAccountProfile();
790
+ useEffect(() => {
791
+ return registerField({ key: "gender" });
792
+ }, [registerField]);
793
+ if (mode === "view") {
794
+ const selected = options.find((option) => option.value === savedValues.gender);
795
+ return /* @__PURE__ */ jsx("p", {
796
+ className,
797
+ "data-fs-my-account-profile-gender": "",
798
+ "data-fs-my-account-profile-field": "gender",
799
+ children: selected?.label ?? savedValues.gender
800
+ });
801
+ }
802
+ return /* @__PURE__ */ jsxs("div", {
803
+ className,
804
+ "data-fs-my-account-profile-gender": "",
805
+ "data-fs-my-account-profile-field": "gender",
806
+ children: [/* @__PURE__ */ jsxs(Select.Root, {
807
+ value: draft.gender,
808
+ onValueChange: (value) => setField("gender", value ?? ""),
809
+ disabled: pending,
810
+ children: [
811
+ /* @__PURE__ */ jsx(Select.Label, { children: label }),
812
+ /* @__PURE__ */ jsxs(Select.Trigger, { children: [/* @__PURE__ */ jsx(Select.Value, { placeholder }), /* @__PURE__ */ jsx(Select.Icon, {})] }),
813
+ /* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx(Select.Positioner, { children: /* @__PURE__ */ jsx(Select.Popup, { children: /* @__PURE__ */ jsx(Select.List, { children: options.map((option) => /* @__PURE__ */ jsx(Select.Item, {
814
+ value: option.value,
815
+ children: /* @__PURE__ */ jsx(Select.ItemText, { children: option.label })
816
+ }, option.value)) }) }) }) })
817
+ ]
818
+ }), draft.gender ? /* @__PURE__ */ jsx("button", {
819
+ type: "button",
820
+ onClick: () => setField("gender", ""),
821
+ disabled: pending,
822
+ "data-fs-my-account-profile-gender-clear": "",
823
+ children: clearLabel
824
+ }) : null]
825
+ });
826
+ }
827
+ const MyAccountProfile = {
828
+ Root: MyAccountProfileRoot,
829
+ RootDefinition: MyAccountProfileRootDefinition,
830
+ EditButton: MyAccountProfileEditButton,
831
+ EditButtonDefinition: MyAccountProfileEditButtonDefinition,
832
+ SaveButton: MyAccountProfileSaveButton,
833
+ SaveButtonDefinition: MyAccountProfileSaveButtonDefinition,
834
+ CancelButton: MyAccountProfileCancelButton,
835
+ CancelButtonDefinition: MyAccountProfileCancelButtonDefinition,
836
+ Email: MyAccountProfileEmail,
837
+ EmailDefinition: MyAccountProfileEmailDefinition,
838
+ Skeleton: MyAccountProfileSkeleton,
839
+ SkeletonDefinition: MyAccountProfileSkeletonDefinition,
840
+ FirstNameControl: MyAccountProfileFirstNameControl,
841
+ FirstNameControlDefinition: MyAccountProfileFirstNameControlDefinition,
842
+ LastNameControl: MyAccountProfileLastNameControl,
843
+ LastNameControlDefinition: MyAccountProfileLastNameControlDefinition,
844
+ PhoneControl: MyAccountProfilePhoneControl,
845
+ PhoneControlDefinition: MyAccountProfilePhoneControlDefinition,
846
+ TaxIdControl: MyAccountProfileTaxIdControl,
847
+ TaxIdControlDefinition: MyAccountProfileTaxIdControlDefinition,
848
+ BirthDateControl: MyAccountProfileBirthDateControl,
849
+ BirthDateControlDefinition: MyAccountProfileBirthDateControlDefinition,
850
+ GenderControl: MyAccountProfileGenderControl,
851
+ GenderControlDefinition: MyAccountProfileGenderControlDefinition
852
+ };
853
+ //#endregion
854
+ export { MyAccountProfile, MyAccountProfileBirthDateControl, MyAccountProfileBirthDateControlDefinition, MyAccountProfileCancelButton, MyAccountProfileCancelButtonDefinition, MyAccountProfileEditButton, MyAccountProfileEditButtonDefinition, MyAccountProfileEmail, MyAccountProfileEmailDefinition, MyAccountProfileFirstNameControl, MyAccountProfileFirstNameControlDefinition, MyAccountProfileGenderControl, MyAccountProfileGenderControlDefinition, MyAccountProfileLastNameControl, MyAccountProfileLastNameControlDefinition, MyAccountProfilePhoneControl, MyAccountProfilePhoneControlDefinition, MyAccountProfileRoot, MyAccountProfileRootDefinition, MyAccountProfileSaveButton, MyAccountProfileSaveButtonDefinition, MyAccountProfileSkeleton, MyAccountProfileSkeletonDefinition, MyAccountProfileTaxIdControl, MyAccountProfileTaxIdControlDefinition, useMyAccountProfile };
855
+
856
+ //# sourceMappingURL=my-account-profile.js.map