@greatapps/common 1.1.791 → 1.1.792

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 (28) hide show
  1. package/dist/components/account/AccountUnsavedChangesDialog.mjs +34 -0
  2. package/dist/components/account/AccountUnsavedChangesDialog.mjs.map +1 -0
  3. package/dist/components/account/MyAccountModal.mjs +28 -22
  4. package/dist/components/account/MyAccountModal.mjs.map +1 -1
  5. package/dist/components/account/sections/MyProfileSection.mjs +20 -22
  6. package/dist/components/account/sections/MyProfileSection.mjs.map +1 -1
  7. package/dist/components/account/sections/PreferencesSection.mjs +15 -20
  8. package/dist/components/account/sections/PreferencesSection.mjs.map +1 -1
  9. package/dist/i18n/messages/en-us.mjs +5 -0
  10. package/dist/i18n/messages/en-us.mjs.map +1 -1
  11. package/dist/i18n/messages/es-es.mjs +5 -0
  12. package/dist/i18n/messages/es-es.mjs.map +1 -1
  13. package/dist/i18n/messages/pt-br.mjs +5 -0
  14. package/dist/i18n/messages/pt-br.mjs.map +1 -1
  15. package/dist/middlewares/create-auth-middleware.mjs +1 -1
  16. package/dist/middlewares/create-auth-middleware.mjs.map +1 -1
  17. package/dist/store/useAccountUnsavedChanges.mjs +31 -0
  18. package/dist/store/useAccountUnsavedChanges.mjs.map +1 -0
  19. package/package.json +1 -1
  20. package/src/components/account/AccountUnsavedChangesDialog.tsx +49 -0
  21. package/src/components/account/MyAccountModal.tsx +41 -34
  22. package/src/components/account/sections/MyProfileSection.tsx +24 -20
  23. package/src/components/account/sections/PreferencesSection.tsx +17 -21
  24. package/src/i18n/messages/en-us.ts +5 -0
  25. package/src/i18n/messages/es-es.ts +5 -0
  26. package/src/i18n/messages/pt-br.ts +5 -0
  27. package/src/middlewares/create-auth-middleware.ts +1 -1
  28. package/src/store/useAccountUnsavedChanges.ts +52 -0
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useTranslations } from "next-intl";
4
+ import { IconAlertTriangle } from "@tabler/icons-react";
5
+ import { Button } from "../ui/buttons/Button";
6
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "../ui/overlay/Dialog";
7
+ import { Separator } from "../ui/data-display/Separator";
8
+ import { useAccountUnsavedChanges } from "../../store/useAccountUnsavedChanges";
9
+ function AccountUnsavedChangesDialog() {
10
+ const translate = useTranslations();
11
+ const { pendingAction, save, discard, dismiss } = useAccountUnsavedChanges();
12
+ return /* @__PURE__ */ jsx(Dialog, { open: pendingAction !== null, onOpenChange: (nextOpen) => !nextOpen && dismiss(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "flex flex-col p-0 gap-0 md:w-[386px]! lg:w-[386px]! max-w-[386px]! max-[386px]:w-[calc(100%-32px)]", children: [
13
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "sr-only", children: [
14
+ /* @__PURE__ */ jsx(DialogTitle, { children: translate("common.account.unsavedChanges.title") }),
15
+ /* @__PURE__ */ jsx(DialogDescription, { children: translate("common.account.unsavedChanges.description") })
16
+ ] }),
17
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 p-5", children: [
18
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center bg-gray-50 rounded-lg size-10", children: /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, className: "text-gray-950" }) }),
19
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
20
+ /* @__PURE__ */ jsx("span", { className: "paragraph-medium-semibold text-gray-950", children: translate("common.account.unsavedChanges.title") }),
21
+ /* @__PURE__ */ jsx("span", { className: "paragraph-small-regular text-gray-600", children: translate("common.account.unsavedChanges.description") })
22
+ ] })
23
+ ] }),
24
+ /* @__PURE__ */ jsx(Separator, { className: "w-full" }),
25
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-5", children: [
26
+ /* @__PURE__ */ jsx(Button, { onClick: save, className: "h-10!", children: translate("common.actions.save") }),
27
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: discard, className: "h-10!", children: translate("common.account.unsavedChanges.discard") })
28
+ ] })
29
+ ] }) });
30
+ }
31
+ export {
32
+ AccountUnsavedChangesDialog as default
33
+ };
34
+ //# sourceMappingURL=AccountUnsavedChangesDialog.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/account/AccountUnsavedChangesDialog.tsx"],"sourcesContent":["'use client';\n\nimport { useTranslations } from 'next-intl';\nimport { IconAlertTriangle } from '@tabler/icons-react';\nimport { Button } from '../ui/buttons/Button';\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui/overlay/Dialog';\nimport { Separator } from '../ui/data-display/Separator';\nimport { useAccountUnsavedChanges } from '../../store/useAccountUnsavedChanges';\n\nexport default function AccountUnsavedChangesDialog() {\n const translate = useTranslations();\n const { pendingAction, save, discard, dismiss } = useAccountUnsavedChanges();\n\n return (\n <Dialog open={pendingAction !== null} onOpenChange={(nextOpen) => !nextOpen && dismiss()}>\n <DialogContent className=\"flex flex-col p-0 gap-0 md:w-[386px]! lg:w-[386px]! max-w-[386px]! max-[386px]:w-[calc(100%-32px)]\">\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{translate('common.account.unsavedChanges.title')}</DialogTitle>\n <DialogDescription>{translate('common.account.unsavedChanges.description')}</DialogDescription>\n </DialogHeader>\n\n <div className=\"flex flex-col gap-3 p-5\">\n <div className=\"flex items-center justify-center bg-gray-50 rounded-lg size-10\">\n <IconAlertTriangle size={24} className=\"text-gray-950\" />\n </div>\n <div className=\"flex flex-col gap-2\">\n <span className=\"paragraph-medium-semibold text-gray-950\">\n {translate('common.account.unsavedChanges.title')}\n </span>\n <span className=\"paragraph-small-regular text-gray-600\">\n {translate('common.account.unsavedChanges.description')}\n </span>\n </div>\n </div>\n\n <Separator className=\"w-full\" />\n\n <div className=\"flex items-center gap-2 p-5\">\n <Button onClick={save} className=\"h-10!\">\n {translate('common.actions.save')}\n </Button>\n <Button variant=\"secondary\" onClick={discard} className=\"h-10!\">\n {translate('common.account.unsavedChanges.discard')}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":";AAgBQ,SACE,KADF;AAdR,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,cAAc;AACvB,SAAS,QAAQ,eAAe,mBAAmB,cAAc,mBAAmB;AACpF,SAAS,iBAAiB;AAC1B,SAAS,gCAAgC;AAE1B,SAAR,8BAA+C;AACpD,QAAM,YAAY,gBAAgB;AAClC,QAAM,EAAE,eAAe,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AAE3E,SACE,oBAAC,UAAO,MAAM,kBAAkB,MAAM,cAAc,CAAC,aAAa,CAAC,YAAY,QAAQ,GACrF,+BAAC,iBAAc,WAAU,sGACvB;AAAA,yBAAC,gBAAa,WAAU,WACtB;AAAA,0BAAC,eAAa,oBAAU,qCAAqC,GAAE;AAAA,MAC/D,oBAAC,qBAAmB,oBAAU,2CAA2C,GAAE;AAAA,OAC7E;AAAA,IAEA,qBAAC,SAAI,WAAU,2BACb;AAAA,0BAAC,SAAI,WAAU,kEACb,8BAAC,qBAAkB,MAAM,IAAI,WAAU,iBAAgB,GACzD;AAAA,MACA,qBAAC,SAAI,WAAU,uBACb;AAAA,4BAAC,UAAK,WAAU,2CACb,oBAAU,qCAAqC,GAClD;AAAA,QACA,oBAAC,UAAK,WAAU,yCACb,oBAAU,2CAA2C,GACxD;AAAA,SACF;AAAA,OACF;AAAA,IAEA,oBAAC,aAAU,WAAU,UAAS;AAAA,IAE9B,qBAAC,SAAI,WAAU,+BACb;AAAA,0BAAC,UAAO,SAAS,MAAM,WAAU,SAC9B,oBAAU,qBAAqB,GAClC;AAAA,MACA,oBAAC,UAAO,SAAQ,aAAY,SAAS,SAAS,WAAU,SACrD,oBAAU,uCAAuC,GACpD;AAAA,OACF;AAAA,KACF,GACF;AAEJ;","names":[]}
@@ -27,9 +27,11 @@ import { ProjectsSection } from "./sections/ProjectsSection";
27
27
  import { TeamSection } from "./sections/TeamSection";
28
28
  import { AffiliatesSection } from "./sections/AffiliatesSection";
29
29
  import useMyAccountModal from "./hooks/useMyAccountModal";
30
+ import AccountUnsavedChangesDialog from "./AccountUnsavedChangesDialog";
30
31
  import { AccountSectionType } from "../../enums/AccountSectionType";
31
32
  import { WorkspaceSectionType } from "../../enums/WorkspaceSectionType";
32
33
  import { useAccountModals } from "../../store/useAccountModals";
34
+ import { useAccountUnsavedChanges } from "../../store/useAccountUnsavedChanges";
33
35
  import { useAuth } from "../../providers/auth.provider";
34
36
  import { useManagementPermissions } from "../../modules/auth/hooks/useManagementPermissions";
35
37
  import { useCurrentSubscription } from "../../modules/subscriptions/hooks/use-current-subscription.hook";
@@ -43,6 +45,7 @@ function MyAccountModal() {
43
45
  const { isExternalContracting } = useExternalContracting();
44
46
  const { whitelabel } = useWhitelabel();
45
47
  const { data: { data: [subscription] = [] } = {} } = useCurrentSubscription();
48
+ const guardUnsavedChanges = useAccountUnsavedChanges((state) => state.guard);
46
49
  const open = activeModal === "account";
47
50
  const isBlockingSubscription = !!subscription && subscription.type !== "free" && subscription.type !== "trial" && subscription.active === true && !subscription.date_cancellation && !hasSubscriptionExpired(subscription.date_due);
48
51
  const handleDeleteAccount = () => {
@@ -59,14 +62,14 @@ function MyAccountModal() {
59
62
  icon: IconUser,
60
63
  label: translate("common.account.profile.title"),
61
64
  visible: true,
62
- content: /* @__PURE__ */ jsx(MyProfileSection, { onClose: close })
65
+ content: /* @__PURE__ */ jsx(MyProfileSection, {})
63
66
  },
64
67
  {
65
68
  value: AccountSectionType.PREFERENCES,
66
69
  icon: IconSettings2,
67
70
  label: translate("common.preferences.title"),
68
71
  visible: true,
69
- content: /* @__PURE__ */ jsx(PreferencesSection, { onClose: close })
72
+ content: /* @__PURE__ */ jsx(PreferencesSection, {})
70
73
  },
71
74
  {
72
75
  value: AccountSectionType.TOKEN,
@@ -140,26 +143,29 @@ function MyAccountModal() {
140
143
  });
141
144
  const sectionTitle = [...visibleSections, ...changePasswordSection].find((section) => section.value === activeSection)?.label ?? translate("common.account.workspace.title");
142
145
  const renderNavItems = (sections) => sections.map((section) => /* @__PURE__ */ jsx(AccountModalNavItem, { value: section.value, icon: section.icon, label: section.label }, section.value));
143
- return /* @__PURE__ */ jsxs(
144
- AccountModalLayout,
145
- {
146
- open,
147
- onClose: close,
148
- title: translate("common.account.workspace.title"),
149
- sectionTitle,
150
- activeSection: activeSection ?? "",
151
- onSectionChange: setActiveSection,
152
- isLoading: isProfileLoading,
153
- navigation: /* @__PURE__ */ jsxs(Fragment, { children: [
154
- visibleWorkspaceSections.length > 0 && /* @__PURE__ */ jsx(AccountModalNavGroup, { label: translate("common.account.workspace.groups.workspace"), children: renderNavItems(visibleWorkspaceSections) }),
155
- /* @__PURE__ */ jsx(AccountModalNavGroup, { label: translate("common.account.workspace.groups.account"), children: renderNavItems(visibleAccountSections) })
156
- ] }),
157
- children: [
158
- visibleSections.map((section) => /* @__PURE__ */ jsx(AccountModalSection, { value: section.value, children: section.content }, section.value)),
159
- changePasswordSection.map((section) => /* @__PURE__ */ jsx(AccountModalSection, { value: section.value, children: section.content }, section.value))
160
- ]
161
- }
162
- );
146
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
147
+ /* @__PURE__ */ jsxs(
148
+ AccountModalLayout,
149
+ {
150
+ open,
151
+ onClose: () => guardUnsavedChanges(close),
152
+ title: translate("common.account.workspace.title"),
153
+ sectionTitle,
154
+ activeSection: activeSection ?? "",
155
+ onSectionChange: (section) => guardUnsavedChanges(() => setActiveSection(section)),
156
+ isLoading: isProfileLoading,
157
+ navigation: /* @__PURE__ */ jsxs(Fragment, { children: [
158
+ visibleWorkspaceSections.length > 0 && /* @__PURE__ */ jsx(AccountModalNavGroup, { label: translate("common.account.workspace.groups.workspace"), children: renderNavItems(visibleWorkspaceSections) }),
159
+ /* @__PURE__ */ jsx(AccountModalNavGroup, { label: translate("common.account.workspace.groups.account"), children: renderNavItems(visibleAccountSections) })
160
+ ] }),
161
+ children: [
162
+ visibleSections.map((section) => /* @__PURE__ */ jsx(AccountModalSection, { value: section.value, children: section.content }, section.value)),
163
+ changePasswordSection.map((section) => /* @__PURE__ */ jsx(AccountModalSection, { value: section.value, children: section.content }, section.value))
164
+ ]
165
+ }
166
+ ),
167
+ /* @__PURE__ */ jsx(AccountUnsavedChangesDialog, {})
168
+ ] });
163
169
  }
164
170
  export {
165
171
  MyAccountModal as default
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/account/MyAccountModal.tsx"],"sourcesContent":["'use client';\n\nimport type { ReactNode } from 'react';\nimport {\n IconCreditCard,\n IconFolder,\n IconKey,\n IconLink,\n IconLock,\n IconSettings2,\n IconUser,\n IconUsers,\n type TablerIcon,\n} from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport {\n AccountModalLayout,\n AccountModalNavGroup,\n AccountModalNavItem,\n AccountModalSection,\n} from './AccountModalLayout';\nimport { MyProfileSection } from './sections/MyProfileSection';\nimport { PreferencesSection } from './sections/PreferencesSection';\nimport { TokenSection } from './sections/TokenSection';\nimport { SecuritySection } from './sections/SecuritySection';\nimport { ChangePasswordSection } from './sections/ChangePasswordSection';\nimport { SubscriptionSection } from './sections/SubscriptionSection';\nimport { ProjectsSection } from './sections/ProjectsSection';\nimport { TeamSection } from './sections/TeamSection';\nimport { AffiliatesSection } from './sections/AffiliatesSection';\nimport useMyAccountModal from './hooks/useMyAccountModal';\nimport { AccountSectionType } from '../../enums/AccountSectionType';\nimport { WorkspaceSectionType } from '../../enums/WorkspaceSectionType';\nimport { useAccountModals, type MyAccountSection } from '../../store/useAccountModals';\nimport { useAuth } from '../../providers/auth.provider';\nimport { useManagementPermissions } from '../../modules/auth/hooks/useManagementPermissions';\nimport { useCurrentSubscription } from '../../modules/subscriptions/hooks/use-current-subscription.hook';\nimport { hasSubscriptionExpired } from '../../modules/subscriptions/utils/has-subscription-expired';\nimport { useExternalContracting, useWhitelabel } from '../../providers/whitelabel.provider';\n\ntype MyAccountNavSection = {\n value: MyAccountSection;\n icon: TablerIcon;\n label: string;\n visible: boolean;\n content: ReactNode;\n};\n\n/** Modal Minha conta: assinatura, projetos, equipe e afiliados (workspace), e preferências, token e segurança. */\nexport default function MyAccountModal() {\n const translate = useTranslations();\n const { activeModal, initialSection, openDeleteAccount, openIsntPossibleDelete, close } = useAccountModals();\n const { isOwner, isOwnerOrAdmin } = useManagementPermissions();\n const { isLoading: isProfileLoading } = useAuth();\n const { isExternalContracting } = useExternalContracting();\n const { whitelabel } = useWhitelabel();\n const { data: { data: [subscription] = [] } = {} } = useCurrentSubscription();\n\n const open = activeModal === 'account';\n\n // Assinatura paga vigente impede excluir a conta: o fluxo manda para o aviso em vez da exclusão.\n const isBlockingSubscription =\n !!subscription &&\n subscription.type !== 'free' &&\n subscription.type !== 'trial' &&\n subscription.active === true &&\n !subscription.date_cancellation &&\n !hasSubscriptionExpired(subscription.date_due);\n\n const handleDeleteAccount = () => {\n close();\n if (isBlockingSubscription) {\n openIsntPossibleDelete();\n } else {\n openDeleteAccount();\n }\n };\n\n const accountSections: MyAccountNavSection[] = [\n {\n value: AccountSectionType.MY_PROFILE,\n icon: IconUser,\n label: translate('common.account.profile.title'),\n visible: true,\n content: <MyProfileSection onClose={close} />,\n },\n {\n value: AccountSectionType.PREFERENCES,\n icon: IconSettings2,\n label: translate('common.preferences.title'),\n visible: true,\n content: <PreferencesSection onClose={close} />,\n },\n {\n value: AccountSectionType.TOKEN,\n icon: IconKey,\n label: translate('common.account.token.title'),\n visible: isOwner,\n content: <TokenSection />,\n },\n {\n value: AccountSectionType.SECURITY,\n icon: IconLock,\n label: translate('common.account.security.title'),\n visible: isOwner,\n content: (\n <SecuritySection\n setActiveSection={(section) => setActiveSection(section)}\n onClose={close}\n onDeleteAccount={handleDeleteAccount}\n />\n ),\n },\n ];\n\n const workspaceSections: MyAccountNavSection[] = [\n {\n value: WorkspaceSectionType.SUBSCRIPTION,\n icon: IconCreditCard,\n label: translate('common.account.workspace.nav.subscription'),\n visible: isOwnerOrAdmin && !isExternalContracting,\n content: <SubscriptionSection />,\n },\n {\n value: WorkspaceSectionType.PROJECTS,\n icon: IconFolder,\n label: translate('common.account.workspace.nav.projects'),\n visible: true,\n content: <ProjectsSection />,\n },\n {\n value: WorkspaceSectionType.TEAM,\n icon: IconUsers,\n label: translate('common.account.workspace.nav.team'),\n visible: true,\n content: <TeamSection />,\n },\n {\n value: WorkspaceSectionType.AFFILIATES,\n icon: IconLink,\n label: translate('common.account.workspace.nav.affiliates'),\n visible: whitelabel?.id === 1 && isOwnerOrAdmin,\n content: <AffiliatesSection />,\n },\n ];\n\n const visibleAccountSections = accountSections.filter((section) => section.visible);\n const visibleWorkspaceSections = workspaceSections.filter((section) => section.visible);\n const visibleSections = [...visibleWorkspaceSections, ...visibleAccountSections];\n\n // Troca de senha não tem item no menu: abre a partir de Segurança.\n const changePasswordSection: MyAccountNavSection[] = isOwner\n ? [\n {\n value: AccountSectionType.CHANGE_PASSWORD,\n icon: IconLock,\n label: translate('common.account.changePassword.title'),\n visible: false,\n content: <ChangePasswordSection onBack={() => setActiveSection(AccountSectionType.SECURITY)} />,\n },\n ]\n : [];\n\n const { activeSection, setActiveSection } = useMyAccountModal({\n isOpen: open,\n initialSection,\n availableSections: [...visibleSections, ...changePasswordSection].map((section) => section.value),\n isReady: !isProfileLoading,\n });\n\n const sectionTitle =\n [...visibleSections, ...changePasswordSection].find((section) => section.value === activeSection)?.label ??\n translate('common.account.workspace.title');\n\n const renderNavItems = (sections: MyAccountNavSection[]) =>\n sections.map((section) => (\n <AccountModalNavItem key={section.value} value={section.value} icon={section.icon} label={section.label} />\n ));\n\n return (\n <AccountModalLayout\n open={open}\n onClose={close}\n title={translate('common.account.workspace.title')}\n sectionTitle={sectionTitle}\n activeSection={activeSection ?? ''}\n onSectionChange={setActiveSection}\n isLoading={isProfileLoading}\n navigation={\n <>\n {visibleWorkspaceSections.length > 0 && (\n <AccountModalNavGroup label={translate('common.account.workspace.groups.workspace')}>\n {renderNavItems(visibleWorkspaceSections)}\n </AccountModalNavGroup>\n )}\n <AccountModalNavGroup label={translate('common.account.workspace.groups.account')}>\n {renderNavItems(visibleAccountSections)}\n </AccountModalNavGroup>\n </>\n }\n >\n {visibleSections.map((section) => (\n <AccountModalSection key={section.value} value={section.value}>\n {section.content}\n </AccountModalSection>\n ))}\n\n {changePasswordSection.map((section) => (\n <AccountModalSection key={section.value} value={section.value}>\n {section.content}\n </AccountModalSection>\n ))}\n </AccountModalLayout>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/account/MyAccountModal.tsx"],"sourcesContent":["'use client';\n\nimport type { ReactNode } from 'react';\nimport {\n IconCreditCard,\n IconFolder,\n IconKey,\n IconLink,\n IconLock,\n IconSettings2,\n IconUser,\n IconUsers,\n type TablerIcon,\n} from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport {\n AccountModalLayout,\n AccountModalNavGroup,\n AccountModalNavItem,\n AccountModalSection,\n} from './AccountModalLayout';\nimport { MyProfileSection } from './sections/MyProfileSection';\nimport { PreferencesSection } from './sections/PreferencesSection';\nimport { TokenSection } from './sections/TokenSection';\nimport { SecuritySection } from './sections/SecuritySection';\nimport { ChangePasswordSection } from './sections/ChangePasswordSection';\nimport { SubscriptionSection } from './sections/SubscriptionSection';\nimport { ProjectsSection } from './sections/ProjectsSection';\nimport { TeamSection } from './sections/TeamSection';\nimport { AffiliatesSection } from './sections/AffiliatesSection';\nimport useMyAccountModal from './hooks/useMyAccountModal';\nimport AccountUnsavedChangesDialog from './AccountUnsavedChangesDialog';\nimport { AccountSectionType } from '../../enums/AccountSectionType';\nimport { WorkspaceSectionType } from '../../enums/WorkspaceSectionType';\nimport { useAccountModals, type MyAccountSection } from '../../store/useAccountModals';\nimport { useAccountUnsavedChanges } from '../../store/useAccountUnsavedChanges';\nimport { useAuth } from '../../providers/auth.provider';\nimport { useManagementPermissions } from '../../modules/auth/hooks/useManagementPermissions';\nimport { useCurrentSubscription } from '../../modules/subscriptions/hooks/use-current-subscription.hook';\nimport { hasSubscriptionExpired } from '../../modules/subscriptions/utils/has-subscription-expired';\nimport { useExternalContracting, useWhitelabel } from '../../providers/whitelabel.provider';\n\ntype MyAccountNavSection = {\n value: MyAccountSection;\n icon: TablerIcon;\n label: string;\n visible: boolean;\n content: ReactNode;\n};\n\n/** Modal Minha conta: assinatura, projetos, equipe e afiliados (workspace), e preferências, token e segurança. */\nexport default function MyAccountModal() {\n const translate = useTranslations();\n const { activeModal, initialSection, openDeleteAccount, openIsntPossibleDelete, close } = useAccountModals();\n const { isOwner, isOwnerOrAdmin } = useManagementPermissions();\n const { isLoading: isProfileLoading } = useAuth();\n const { isExternalContracting } = useExternalContracting();\n const { whitelabel } = useWhitelabel();\n const { data: { data: [subscription] = [] } = {} } = useCurrentSubscription();\n const guardUnsavedChanges = useAccountUnsavedChanges((state) => state.guard);\n\n const open = activeModal === 'account';\n\n // Assinatura paga vigente impede excluir a conta: o fluxo manda para o aviso em vez da exclusão.\n const isBlockingSubscription =\n !!subscription &&\n subscription.type !== 'free' &&\n subscription.type !== 'trial' &&\n subscription.active === true &&\n !subscription.date_cancellation &&\n !hasSubscriptionExpired(subscription.date_due);\n\n const handleDeleteAccount = () => {\n close();\n if (isBlockingSubscription) {\n openIsntPossibleDelete();\n } else {\n openDeleteAccount();\n }\n };\n\n const accountSections: MyAccountNavSection[] = [\n {\n value: AccountSectionType.MY_PROFILE,\n icon: IconUser,\n label: translate('common.account.profile.title'),\n visible: true,\n content: <MyProfileSection />,\n },\n {\n value: AccountSectionType.PREFERENCES,\n icon: IconSettings2,\n label: translate('common.preferences.title'),\n visible: true,\n content: <PreferencesSection />,\n },\n {\n value: AccountSectionType.TOKEN,\n icon: IconKey,\n label: translate('common.account.token.title'),\n visible: isOwner,\n content: <TokenSection />,\n },\n {\n value: AccountSectionType.SECURITY,\n icon: IconLock,\n label: translate('common.account.security.title'),\n visible: isOwner,\n content: (\n <SecuritySection\n setActiveSection={(section) => setActiveSection(section)}\n onClose={close}\n onDeleteAccount={handleDeleteAccount}\n />\n ),\n },\n ];\n\n const workspaceSections: MyAccountNavSection[] = [\n {\n value: WorkspaceSectionType.SUBSCRIPTION,\n icon: IconCreditCard,\n label: translate('common.account.workspace.nav.subscription'),\n visible: isOwnerOrAdmin && !isExternalContracting,\n content: <SubscriptionSection />,\n },\n {\n value: WorkspaceSectionType.PROJECTS,\n icon: IconFolder,\n label: translate('common.account.workspace.nav.projects'),\n visible: true,\n content: <ProjectsSection />,\n },\n {\n value: WorkspaceSectionType.TEAM,\n icon: IconUsers,\n label: translate('common.account.workspace.nav.team'),\n visible: true,\n content: <TeamSection />,\n },\n {\n value: WorkspaceSectionType.AFFILIATES,\n icon: IconLink,\n label: translate('common.account.workspace.nav.affiliates'),\n visible: whitelabel?.id === 1 && isOwnerOrAdmin,\n content: <AffiliatesSection />,\n },\n ];\n\n const visibleAccountSections = accountSections.filter((section) => section.visible);\n const visibleWorkspaceSections = workspaceSections.filter((section) => section.visible);\n const visibleSections = [...visibleWorkspaceSections, ...visibleAccountSections];\n\n // Troca de senha não tem item no menu: abre a partir de Segurança.\n const changePasswordSection: MyAccountNavSection[] = isOwner\n ? [\n {\n value: AccountSectionType.CHANGE_PASSWORD,\n icon: IconLock,\n label: translate('common.account.changePassword.title'),\n visible: false,\n content: <ChangePasswordSection onBack={() => setActiveSection(AccountSectionType.SECURITY)} />,\n },\n ]\n : [];\n\n const { activeSection, setActiveSection } = useMyAccountModal({\n isOpen: open,\n initialSection,\n availableSections: [...visibleSections, ...changePasswordSection].map((section) => section.value),\n isReady: !isProfileLoading,\n });\n\n const sectionTitle =\n [...visibleSections, ...changePasswordSection].find((section) => section.value === activeSection)?.label ??\n translate('common.account.workspace.title');\n\n const renderNavItems = (sections: MyAccountNavSection[]) =>\n sections.map((section) => (\n <AccountModalNavItem key={section.value} value={section.value} icon={section.icon} label={section.label} />\n ));\n\n return (\n <>\n <AccountModalLayout\n open={open}\n onClose={() => guardUnsavedChanges(close)}\n title={translate('common.account.workspace.title')}\n sectionTitle={sectionTitle}\n activeSection={activeSection ?? ''}\n onSectionChange={(section) => guardUnsavedChanges(() => setActiveSection(section))}\n isLoading={isProfileLoading}\n navigation={\n <>\n {visibleWorkspaceSections.length > 0 && (\n <AccountModalNavGroup label={translate('common.account.workspace.groups.workspace')}>\n {renderNavItems(visibleWorkspaceSections)}\n </AccountModalNavGroup>\n )}\n <AccountModalNavGroup label={translate('common.account.workspace.groups.account')}>\n {renderNavItems(visibleAccountSections)}\n </AccountModalNavGroup>\n </>\n }\n >\n {visibleSections.map((section) => (\n <AccountModalSection key={section.value} value={section.value}>\n {section.content}\n </AccountModalSection>\n ))}\n\n {changePasswordSection.map((section) => (\n <AccountModalSection key={section.value} value={section.value}>\n {section.content}\n </AccountModalSection>\n ))}\n </AccountModalLayout>\n\n <AccountUnsavedChangesDialog />\n </>\n );\n}\n"],"mappings":"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","names":[]}
@@ -25,6 +25,7 @@ import { ChangeEmailModal } from "./ChangeEmailModal";
25
25
  import { NotificationsStep } from "./team/steps/NotificationsStep";
26
26
  import { ProjectSelectionStep } from "./team/steps/ProjectSelectionStep";
27
27
  import { FormSection } from "./shared/FormSection";
28
+ import { useAccountUnsavedChanges } from "../../../store/useAccountUnsavedChanges";
28
29
  import { buildUserFormDefaults, buildUserFormSchema } from "./team/user-form.schema";
29
30
  const TAB_PADDING = "px-4 py-5 lg:px-8 lg:py-6";
30
31
  const tabContentClasses = cn(
@@ -33,7 +34,7 @@ const tabContentClasses = cn(
33
34
  "[&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300"
34
35
  );
35
36
  const tabTriggerClasses = "cursor-pointer gap-2 px-3 py-4";
36
- function MyProfileSection({ onClose, storageUrl }) {
37
+ function MyProfileSection({ storageUrl }) {
37
38
  const translate = useTranslations();
38
39
  const genderOptions = GENDER_OPTIONS.map((option) => ({
39
40
  ...option,
@@ -49,6 +50,9 @@ function MyProfileSection({ onClose, storageUrl }) {
49
50
  const [photoRemoved, setPhotoRemoved] = useState(false);
50
51
  const [changePhoneOpen, setChangePhoneOpen] = useState(false);
51
52
  const [changeEmailOpen, setChangeEmailOpen] = useState(false);
53
+ const setDirtyForm = useAccountUnsavedChanges((state) => state.setDirtyForm);
54
+ const completeSave = useAccountUnsavedChanges((state) => state.completeSave);
55
+ const dismissUnsavedChanges = useAccountUnsavedChanges((state) => state.dismiss);
52
56
  const profileFormSchema = useMemo(
53
57
  () => buildUserFormSchema({
54
58
  required: translate("common.validations.required"),
@@ -66,7 +70,7 @@ function MyProfileSection({ onClose, storageUrl }) {
66
70
  handleSubmit,
67
71
  watch,
68
72
  setValue,
69
- formState: { errors, isSubmitting }
73
+ formState: { errors, isSubmitting, isDirty }
70
74
  } = form;
71
75
  useEffect(() => {
72
76
  if (user) form.reset(buildUserFormDefaults(user));
@@ -106,7 +110,7 @@ function MyProfileSection({ onClose, storageUrl }) {
106
110
  toastId
107
111
  }
108
112
  ));
109
- onClose();
113
+ completeSave();
110
114
  } catch (error) {
111
115
  toast.custom((toastId) => /* @__PURE__ */ jsx(
112
116
  Toast,
@@ -116,8 +120,20 @@ function MyProfileSection({ onClose, storageUrl }) {
116
120
  toastId
117
121
  }
118
122
  ));
123
+ dismissUnsavedChanges();
119
124
  }
120
125
  };
126
+ const hasChanges = isDirty || selectedPhoto !== null || photoRemoved;
127
+ const discardChanges = () => {
128
+ form.reset();
129
+ setSelectedPhoto(null);
130
+ setPhotoRemoved(false);
131
+ };
132
+ useEffect(() => {
133
+ if (!hasChanges) return;
134
+ setDirtyForm({ submit: handleSubmit(onSubmit), discard: discardChanges });
135
+ return () => setDirtyForm(null);
136
+ });
121
137
  return /* @__PURE__ */ jsxs(Fragment, { children: [
122
138
  /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "flex flex-col h-full", children: [
123
139
  /* @__PURE__ */ jsx(
@@ -295,25 +311,7 @@ function MyProfileSection({ onClose, storageUrl }) {
295
311
  /* @__PURE__ */ jsx(TabsContent, { value: "projects", className: tabContentClasses, children: /* @__PURE__ */ jsx(ProjectSelectionStep, { mode: "edit", readOnly: !isOwnerOrAdmin, className: TAB_PADDING }) }),
296
312
  /* @__PURE__ */ jsx(TabsContent, { value: "notifications", className: tabContentClasses, children: /* @__PURE__ */ jsx(NotificationsStep, { className: TAB_PADDING }) })
297
313
  ] }),
298
- /* @__PURE__ */ jsxs("div", { className: "fixed bottom-0 left-0 right-0 lg:absolute flex items-center h-20 px-4 lg:px-8 border-t border-gray-200 justify-between bg-white z-10", children: [
299
- /* @__PURE__ */ jsx(
300
- Button,
301
- {
302
- variant: "secondary",
303
- className: "h-10!",
304
- type: "button",
305
- disabled: isSubmitting,
306
- onClick: () => {
307
- form.reset();
308
- setSelectedPhoto(null);
309
- setPhotoRemoved(false);
310
- onClose();
311
- },
312
- children: translate("common.actions.cancel")
313
- }
314
- ),
315
- /* @__PURE__ */ jsx(Button, { className: "h-10! cursor-pointer", type: "submit", disabled: isSubmitting, children: isSubmitting ? translate("common.actions.saving") : translate("common.actions.saveChanges") })
316
- ] })
314
+ /* @__PURE__ */ jsx("div", { className: "fixed bottom-0 left-0 right-0 lg:absolute flex items-center h-20 px-4 lg:px-8 border-t border-gray-200 justify-end bg-white z-10", children: /* @__PURE__ */ jsx(Button, { className: "h-10! cursor-pointer", type: "submit", disabled: !hasChanges || isSubmitting, children: isSubmitting ? translate("common.actions.saving") : translate("common.actions.saveChanges") }) })
317
315
  ] }) }),
318
316
  /* @__PURE__ */ jsx(ChangePhoneModal, { open: changePhoneOpen, onClose: () => setChangePhoneOpen(false) }),
319
317
  /* @__PURE__ */ jsx(ChangeEmailModal, { open: changeEmailOpen, onClose: () => setChangeEmailOpen(false) })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/account/sections/MyProfileSection.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useState, useRef } from 'react';\nimport Image from 'next/image';\nimport { useTranslations } from 'next-intl';\nimport { FormProvider, useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { toast } from 'sonner';\nimport { IconBell, IconStack2, IconTrash, IconUpload, IconUser } from '@tabler/icons-react';\nimport { ACCEPTED_IMAGE_FORMATS_STRING } from '../../../modules/images/constants/image.constants';\nimport { Button } from '../../ui/buttons/Button';\nimport { Separator } from '../../ui/data-display/Separator';\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '../../ui/data-display/Tabs';\nimport { Toast } from '../../ui/feedback/Toast';\nimport { FormField } from '../../ui/form/FormField';\nimport { SelectField } from '../../ui/form/SelectField';\nimport PhoneInput from '../../ui/form/PhoneInput';\nimport { useAuth } from '../../../providers/auth.provider';\nimport { useManagementPermissions } from '../../../modules/auth/hooks/useManagementPermissions';\nimport { useUpdateAccountUser } from '../../../modules/accounts/hooks/useAccountManagement';\nimport { cn } from '../../../infra/utils/clsx';\nimport { GENDER_OPTIONS } from '../constants';\nimport { ChangePhoneModal } from './ChangePhoneModal';\nimport { ChangeEmailModal } from './ChangeEmailModal';\nimport { NotificationsStep } from './team/steps/NotificationsStep';\nimport { ProjectSelectionStep } from './team/steps/ProjectSelectionStep';\nimport { FormSection } from './shared/FormSection';\nimport { buildUserFormDefaults, buildUserFormSchema, type UserFormData } from './team/user-form.schema';\n\n/** Espaçamento das seções do modal, aplicado dentro de cada aba. */\nconst TAB_PADDING = 'px-4 py-5 lg:px-8 lg:py-6';\n\n/** Cada aba rola sozinha: a barra de abas fica fixa no topo e o rodapé de ações no fim. */\nconst tabContentClasses = cn(\n 'flex-1 min-h-0 overflow-y-auto overscroll-contain pb-24 lg:pb-28',\n '[scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent',\n '[&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300'\n);\n\nconst tabTriggerClasses = 'cursor-pointer gap-2 px-3 py-4';\n\ninterface MyProfileSectionProps {\n onClose: () => void;\n storageUrl?: string;\n}\n\nexport function MyProfileSection({ onClose, storageUrl }: MyProfileSectionProps) {\n const translate = useTranslations();\n const genderOptions = GENDER_OPTIONS.map((option) => ({\n ...option,\n label: translate(\n `common.account.profile.genderOptions.${option.value === 'masculino' ? 'male' : 'female'}`,\n ),\n }));\n const { user } = useAuth();\n const { isOwnerOrAdmin } = useManagementPermissions();\n const updateAccountUser = useUpdateAccountUser();\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [selectedPhoto, setSelectedPhoto] = useState<File | null>(null);\n const [photoRemoved, setPhotoRemoved] = useState(false);\n const [changePhoneOpen, setChangePhoneOpen] = useState(false);\n const [changeEmailOpen, setChangeEmailOpen] = useState(false);\n\n // Mesmo formulário do sheet da equipe: as abas Projetos e Notificações são os passos de lá.\n const profileFormSchema = useMemo(\n () =>\n buildUserFormSchema({\n required: translate('common.validations.required'),\n invalidEmail: translate('common.validations.invalidEmail'),\n }),\n [translate]\n );\n\n const form = useForm<UserFormData>({\n resolver: zodResolver(profileFormSchema),\n mode: 'onChange',\n defaultValues: buildUserFormDefaults(user ?? undefined),\n });\n\n const {\n register,\n handleSubmit,\n watch,\n setValue,\n formState: { errors, isSubmitting },\n } = form;\n\n // O usuário autenticado chega por query: repõe os valores quando ele carrega depois da montagem.\n useEffect(() => {\n if (user) form.reset(buildUserFormDefaults(user));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [user?.id]);\n\n const baseStorageUrl = storageUrl ?? process.env.NEXT_PUBLIC_STORAGE_URL ?? '';\n\n const avatarPreview = selectedPhoto\n ? URL.createObjectURL(selectedPhoto)\n : !photoRemoved && user?.photo\n ? `${baseStorageUrl}/${user.photo}`\n : null;\n\n const handlePhotoChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0] ?? null;\n if (file) {\n setSelectedPhoto(file);\n setPhotoRemoved(false);\n }\n };\n\n const handleRemovePhoto = () => {\n setSelectedPhoto(null);\n setPhotoRemoved(true);\n };\n\n const onSubmit = async (data: UserFormData) => {\n try {\n await updateAccountUser.mutateAsync({\n name: data.name,\n last_name: data.lastName,\n gender: data.gender,\n receive_sms: data.receiveSms,\n receive_email: data.receiveEmail,\n ...(selectedPhoto ? { photo: selectedPhoto } : {}),\n ...(photoRemoved ? { photo: '' } : {}),\n });\n\n form.reset(data);\n setSelectedPhoto(null);\n setPhotoRemoved(false);\n toast.custom((toastId) => (\n <Toast\n variant=\"success\"\n message={translate('common.account.profile.savedSuccess')}\n toastId={toastId}\n />\n ));\n onClose();\n } catch (error) {\n toast.custom((toastId) => (\n <Toast\n variant=\"error\"\n message={error instanceof Error ? error.message : translate('common.account.profile.saveError')}\n toastId={toastId}\n />\n ));\n }\n };\n\n return (\n <>\n <FormProvider {...form}>\n <form onSubmit={handleSubmit(onSubmit)} className=\"flex flex-col h-full\">\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={ACCEPTED_IMAGE_FORMATS_STRING}\n className=\"hidden\"\n onChange={handlePhotoChange}\n />\n\n <Tabs defaultValue=\"information\" className=\"flex flex-col flex-1 min-h-0 gap-0\">\n {/* `px-1 lg:px-5` + `px-3` do gatilho: o primeiro rótulo alinha com o conteúdo das abas. */}\n <TabsList className=\"shrink-0 mt-1.5 px-1 lg:px-5 overflow-x-auto scrollbar-hide\">\n <TabsTrigger className={tabTriggerClasses} value=\"information\">\n <IconUser size={16} />\n {translate('common.account.workspace.team.sheet.tabInformation')}\n </TabsTrigger>\n <TabsTrigger className={tabTriggerClasses} value=\"projects\">\n <IconStack2 size={16} />\n {translate('common.account.workspace.team.sheet.tabProjects')}\n </TabsTrigger>\n <TabsTrigger className={tabTriggerClasses} value=\"notifications\">\n <IconBell size={16} />\n {translate('common.account.workspace.team.sheet.tabNotifications')}\n </TabsTrigger>\n </TabsList>\n\n <TabsContent\n value=\"information\"\n className={cn(tabContentClasses, TAB_PADDING, 'flex flex-col gap-6 lg:gap-8')}\n >\n <FormSection\n title={translate('common.account.profile.yourData')}\n description={translate('common.account.profile.yourDataDescription')}\n >\n <div className=\"flex items-center gap-4\">\n {avatarPreview ? (\n <Image\n src={avatarPreview}\n alt={translate('common.account.profile.avatarAlt')}\n width={48}\n height={48}\n className=\"size-12 rounded-full object-cover cursor-pointer\"\n onClick={() => fileInputRef.current?.click()}\n />\n ) : (\n <div\n className=\"size-12 rounded-full bg-gray-50 border border-dashed border-gray-200 flex items-center justify-center cursor-pointer\"\n onClick={() => fileInputRef.current?.click()}\n >\n <IconUpload size={20} className=\"text-gray-400\" />\n </div>\n )}\n {avatarPreview ? (\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"secondary\"\n className=\"w-fit h-8!\"\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n >\n {translate('common.account.profile.changePhoto')}\n </Button>\n <Button type=\"button\" variant=\"ghost\" className=\"size-8! p-0\" onClick={handleRemovePhoto}>\n <IconTrash size={16} className=\"text-gray-500\" />\n </Button>\n </div>\n ) : (\n <Button\n variant=\"secondary\"\n className=\"w-fit h-8!\"\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n >\n {translate('common.account.profile.changePhoto')}\n </Button>\n )}\n </div>\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex gap-4\">\n <FormField\n label={translate('common.account.profile.name')}\n placeholder={translate('common.account.profile.namePlaceholder')}\n error={!!errors.name}\n errorMessage={errors.name?.message}\n {...register('name')}\n />\n <FormField\n label={translate('common.account.profile.lastName')}\n placeholder={translate('common.account.profile.lastNamePlaceholder')}\n error={!!errors.lastName}\n errorMessage={errors.lastName?.message}\n {...register('lastName')}\n />\n </div>\n <SelectField\n label={translate('common.account.profile.gender')}\n placeholder={translate('common.account.profile.genderPlaceholder')}\n options={genderOptions}\n value={GENDER_OPTIONS.find((option) => option.apiValue === watch('gender'))?.value ?? ''}\n className=\"h-10!\"\n onChange={(value) =>\n setValue(\n 'gender',\n GENDER_OPTIONS.find((option) => option.value === value)?.apiValue ?? 1,\n { shouldDirty: true }\n )\n }\n />\n <div className=\"relative\">\n <PhoneInput\n label={translate('common.account.profile.phone')}\n value={user?.phone ?? ''}\n disabled\n classnameContainer=\"bg-gray-50 border-0!\"\n />\n <button\n type=\"button\"\n className=\"absolute right-3 top-5.5 h-10 flex items-center paragraph-xsmall-semibold text-gray-950 underline cursor-pointer\"\n onClick={() => setChangePhoneOpen(true)}\n >\n {translate('common.account.profile.change')}\n </button>\n </div>\n </div>\n </FormSection>\n\n <Separator />\n\n <FormSection\n title={translate('common.account.profile.accessData')}\n description={translate('common.account.profile.accessDataDescription')}\n >\n <div className=\"relative\">\n <div className=\"cursor-not-allowed\">\n <FormField\n label={translate('common.account.profile.email')}\n value={user?.email ?? ''}\n disabled\n classnameContainer=\"bg-gray-50 border-0!\"\n />\n </div>\n <button\n type=\"button\"\n className=\"absolute right-3 top-5.5 h-10 flex items-center paragraph-xsmall-semibold text-gray-950 underline cursor-pointer\"\n onClick={() => setChangeEmailOpen(true)}\n >\n {translate('common.account.profile.change')}\n </button>\n </div>\n </FormSection>\n </TabsContent>\n\n {/* Só proprietário e administrador mudam vínculos de projeto — e ambos já veem todos. */}\n <TabsContent value=\"projects\" className={tabContentClasses}>\n <ProjectSelectionStep mode=\"edit\" readOnly={!isOwnerOrAdmin} className={TAB_PADDING} />\n </TabsContent>\n\n <TabsContent value=\"notifications\" className={tabContentClasses}>\n <NotificationsStep className={TAB_PADDING} />\n </TabsContent>\n </Tabs>\n\n <div className=\"fixed bottom-0 left-0 right-0 lg:absolute flex items-center h-20 px-4 lg:px-8 border-t border-gray-200 justify-between bg-white z-10\">\n <Button\n variant=\"secondary\"\n className=\"h-10!\"\n type=\"button\"\n disabled={isSubmitting}\n onClick={() => {\n form.reset();\n setSelectedPhoto(null);\n setPhotoRemoved(false);\n onClose();\n }}\n >\n {translate('common.actions.cancel')}\n </Button>\n <Button className=\"h-10! cursor-pointer\" type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? translate('common.actions.saving') : translate('common.actions.saveChanges')}\n </Button>\n </div>\n </form>\n </FormProvider>\n\n <ChangePhoneModal open={changePhoneOpen} onClose={() => setChangePhoneOpen(false)} />\n <ChangeEmailModal open={changeEmailOpen} onClose={() => setChangeEmailOpen(false)} />\n </>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/account/sections/MyProfileSection.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useState, useRef } from 'react';\nimport Image from 'next/image';\nimport { useTranslations } from 'next-intl';\nimport { FormProvider, useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { toast } from 'sonner';\nimport { IconBell, IconStack2, IconTrash, IconUpload, IconUser } from '@tabler/icons-react';\nimport { ACCEPTED_IMAGE_FORMATS_STRING } from '../../../modules/images/constants/image.constants';\nimport { Button } from '../../ui/buttons/Button';\nimport { Separator } from '../../ui/data-display/Separator';\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '../../ui/data-display/Tabs';\nimport { Toast } from '../../ui/feedback/Toast';\nimport { FormField } from '../../ui/form/FormField';\nimport { SelectField } from '../../ui/form/SelectField';\nimport PhoneInput from '../../ui/form/PhoneInput';\nimport { useAuth } from '../../../providers/auth.provider';\nimport { useManagementPermissions } from '../../../modules/auth/hooks/useManagementPermissions';\nimport { useUpdateAccountUser } from '../../../modules/accounts/hooks/useAccountManagement';\nimport { cn } from '../../../infra/utils/clsx';\nimport { GENDER_OPTIONS } from '../constants';\nimport { ChangePhoneModal } from './ChangePhoneModal';\nimport { ChangeEmailModal } from './ChangeEmailModal';\nimport { NotificationsStep } from './team/steps/NotificationsStep';\nimport { ProjectSelectionStep } from './team/steps/ProjectSelectionStep';\nimport { FormSection } from './shared/FormSection';\nimport { useAccountUnsavedChanges } from '../../../store/useAccountUnsavedChanges';\nimport { buildUserFormDefaults, buildUserFormSchema, type UserFormData } from './team/user-form.schema';\n\n/** Espaçamento das seções do modal, aplicado dentro de cada aba. */\nconst TAB_PADDING = 'px-4 py-5 lg:px-8 lg:py-6';\n\n/** Cada aba rola sozinha: a barra de abas fica fixa no topo e o rodapé de ações no fim. */\nconst tabContentClasses = cn(\n 'flex-1 min-h-0 overflow-y-auto overscroll-contain pb-24 lg:pb-28',\n '[scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent',\n '[&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300'\n);\n\nconst tabTriggerClasses = 'cursor-pointer gap-2 px-3 py-4';\n\ninterface MyProfileSectionProps {\n storageUrl?: string;\n}\n\nexport function MyProfileSection({ storageUrl }: MyProfileSectionProps) {\n const translate = useTranslations();\n const genderOptions = GENDER_OPTIONS.map((option) => ({\n ...option,\n label: translate(\n `common.account.profile.genderOptions.${option.value === 'masculino' ? 'male' : 'female'}`,\n ),\n }));\n const { user } = useAuth();\n const { isOwnerOrAdmin } = useManagementPermissions();\n const updateAccountUser = useUpdateAccountUser();\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [selectedPhoto, setSelectedPhoto] = useState<File | null>(null);\n const [photoRemoved, setPhotoRemoved] = useState(false);\n const [changePhoneOpen, setChangePhoneOpen] = useState(false);\n const [changeEmailOpen, setChangeEmailOpen] = useState(false);\n const setDirtyForm = useAccountUnsavedChanges((state) => state.setDirtyForm);\n const completeSave = useAccountUnsavedChanges((state) => state.completeSave);\n const dismissUnsavedChanges = useAccountUnsavedChanges((state) => state.dismiss);\n\n // Mesmo formulário do sheet da equipe: as abas Projetos e Notificações são os passos de lá.\n const profileFormSchema = useMemo(\n () =>\n buildUserFormSchema({\n required: translate('common.validations.required'),\n invalidEmail: translate('common.validations.invalidEmail'),\n }),\n [translate]\n );\n\n const form = useForm<UserFormData>({\n resolver: zodResolver(profileFormSchema),\n mode: 'onChange',\n defaultValues: buildUserFormDefaults(user ?? undefined),\n });\n\n const {\n register,\n handleSubmit,\n watch,\n setValue,\n formState: { errors, isSubmitting, isDirty },\n } = form;\n\n // O usuário autenticado chega por query: repõe os valores quando ele carrega depois da montagem.\n useEffect(() => {\n if (user) form.reset(buildUserFormDefaults(user));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [user?.id]);\n\n const baseStorageUrl = storageUrl ?? process.env.NEXT_PUBLIC_STORAGE_URL ?? '';\n\n const avatarPreview = selectedPhoto\n ? URL.createObjectURL(selectedPhoto)\n : !photoRemoved && user?.photo\n ? `${baseStorageUrl}/${user.photo}`\n : null;\n\n const handlePhotoChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0] ?? null;\n if (file) {\n setSelectedPhoto(file);\n setPhotoRemoved(false);\n }\n };\n\n const handleRemovePhoto = () => {\n setSelectedPhoto(null);\n setPhotoRemoved(true);\n };\n\n const onSubmit = async (data: UserFormData) => {\n try {\n await updateAccountUser.mutateAsync({\n name: data.name,\n last_name: data.lastName,\n gender: data.gender,\n receive_sms: data.receiveSms,\n receive_email: data.receiveEmail,\n ...(selectedPhoto ? { photo: selectedPhoto } : {}),\n ...(photoRemoved ? { photo: '' } : {}),\n });\n\n form.reset(data);\n setSelectedPhoto(null);\n setPhotoRemoved(false);\n toast.custom((toastId) => (\n <Toast\n variant=\"success\"\n message={translate('common.account.profile.savedSuccess')}\n toastId={toastId}\n />\n ));\n completeSave();\n } catch (error) {\n toast.custom((toastId) => (\n <Toast\n variant=\"error\"\n message={error instanceof Error ? error.message : translate('common.account.profile.saveError')}\n toastId={toastId}\n />\n ));\n dismissUnsavedChanges();\n }\n };\n\n const hasChanges = isDirty || selectedPhoto !== null || photoRemoved;\n\n const discardChanges = () => {\n form.reset();\n setSelectedPhoto(null);\n setPhotoRemoved(false);\n };\n\n useEffect(() => {\n if (!hasChanges) return;\n setDirtyForm({ submit: handleSubmit(onSubmit), discard: discardChanges });\n return () => setDirtyForm(null);\n });\n\n return (\n <>\n <FormProvider {...form}>\n <form onSubmit={handleSubmit(onSubmit)} className=\"flex flex-col h-full\">\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={ACCEPTED_IMAGE_FORMATS_STRING}\n className=\"hidden\"\n onChange={handlePhotoChange}\n />\n\n <Tabs defaultValue=\"information\" className=\"flex flex-col flex-1 min-h-0 gap-0\">\n {/* `px-1 lg:px-5` + `px-3` do gatilho: o primeiro rótulo alinha com o conteúdo das abas. */}\n <TabsList className=\"shrink-0 mt-1.5 px-1 lg:px-5 overflow-x-auto scrollbar-hide\">\n <TabsTrigger className={tabTriggerClasses} value=\"information\">\n <IconUser size={16} />\n {translate('common.account.workspace.team.sheet.tabInformation')}\n </TabsTrigger>\n <TabsTrigger className={tabTriggerClasses} value=\"projects\">\n <IconStack2 size={16} />\n {translate('common.account.workspace.team.sheet.tabProjects')}\n </TabsTrigger>\n <TabsTrigger className={tabTriggerClasses} value=\"notifications\">\n <IconBell size={16} />\n {translate('common.account.workspace.team.sheet.tabNotifications')}\n </TabsTrigger>\n </TabsList>\n\n <TabsContent\n value=\"information\"\n className={cn(tabContentClasses, TAB_PADDING, 'flex flex-col gap-6 lg:gap-8')}\n >\n <FormSection\n title={translate('common.account.profile.yourData')}\n description={translate('common.account.profile.yourDataDescription')}\n >\n <div className=\"flex items-center gap-4\">\n {avatarPreview ? (\n <Image\n src={avatarPreview}\n alt={translate('common.account.profile.avatarAlt')}\n width={48}\n height={48}\n className=\"size-12 rounded-full object-cover cursor-pointer\"\n onClick={() => fileInputRef.current?.click()}\n />\n ) : (\n <div\n className=\"size-12 rounded-full bg-gray-50 border border-dashed border-gray-200 flex items-center justify-center cursor-pointer\"\n onClick={() => fileInputRef.current?.click()}\n >\n <IconUpload size={20} className=\"text-gray-400\" />\n </div>\n )}\n {avatarPreview ? (\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"secondary\"\n className=\"w-fit h-8!\"\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n >\n {translate('common.account.profile.changePhoto')}\n </Button>\n <Button type=\"button\" variant=\"ghost\" className=\"size-8! p-0\" onClick={handleRemovePhoto}>\n <IconTrash size={16} className=\"text-gray-500\" />\n </Button>\n </div>\n ) : (\n <Button\n variant=\"secondary\"\n className=\"w-fit h-8!\"\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n >\n {translate('common.account.profile.changePhoto')}\n </Button>\n )}\n </div>\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex gap-4\">\n <FormField\n label={translate('common.account.profile.name')}\n placeholder={translate('common.account.profile.namePlaceholder')}\n error={!!errors.name}\n errorMessage={errors.name?.message}\n {...register('name')}\n />\n <FormField\n label={translate('common.account.profile.lastName')}\n placeholder={translate('common.account.profile.lastNamePlaceholder')}\n error={!!errors.lastName}\n errorMessage={errors.lastName?.message}\n {...register('lastName')}\n />\n </div>\n <SelectField\n label={translate('common.account.profile.gender')}\n placeholder={translate('common.account.profile.genderPlaceholder')}\n options={genderOptions}\n value={GENDER_OPTIONS.find((option) => option.apiValue === watch('gender'))?.value ?? ''}\n className=\"h-10!\"\n onChange={(value) =>\n setValue(\n 'gender',\n GENDER_OPTIONS.find((option) => option.value === value)?.apiValue ?? 1,\n { shouldDirty: true }\n )\n }\n />\n <div className=\"relative\">\n <PhoneInput\n label={translate('common.account.profile.phone')}\n value={user?.phone ?? ''}\n disabled\n classnameContainer=\"bg-gray-50 border-0!\"\n />\n <button\n type=\"button\"\n className=\"absolute right-3 top-5.5 h-10 flex items-center paragraph-xsmall-semibold text-gray-950 underline cursor-pointer\"\n onClick={() => setChangePhoneOpen(true)}\n >\n {translate('common.account.profile.change')}\n </button>\n </div>\n </div>\n </FormSection>\n\n <Separator />\n\n <FormSection\n title={translate('common.account.profile.accessData')}\n description={translate('common.account.profile.accessDataDescription')}\n >\n <div className=\"relative\">\n <div className=\"cursor-not-allowed\">\n <FormField\n label={translate('common.account.profile.email')}\n value={user?.email ?? ''}\n disabled\n classnameContainer=\"bg-gray-50 border-0!\"\n />\n </div>\n <button\n type=\"button\"\n className=\"absolute right-3 top-5.5 h-10 flex items-center paragraph-xsmall-semibold text-gray-950 underline cursor-pointer\"\n onClick={() => setChangeEmailOpen(true)}\n >\n {translate('common.account.profile.change')}\n </button>\n </div>\n </FormSection>\n </TabsContent>\n\n {/* Só proprietário e administrador mudam vínculos de projeto — e ambos já veem todos. */}\n <TabsContent value=\"projects\" className={tabContentClasses}>\n <ProjectSelectionStep mode=\"edit\" readOnly={!isOwnerOrAdmin} className={TAB_PADDING} />\n </TabsContent>\n\n <TabsContent value=\"notifications\" className={tabContentClasses}>\n <NotificationsStep className={TAB_PADDING} />\n </TabsContent>\n </Tabs>\n\n <div className=\"fixed bottom-0 left-0 right-0 lg:absolute flex items-center h-20 px-4 lg:px-8 border-t border-gray-200 justify-end bg-white z-10\">\n <Button className=\"h-10! cursor-pointer\" type=\"submit\" disabled={!hasChanges || isSubmitting}>\n {isSubmitting ? translate('common.actions.saving') : translate('common.actions.saveChanges')}\n </Button>\n </div>\n </form>\n </FormProvider>\n\n <ChangePhoneModal open={changePhoneOpen} onClose={() => setChangePhoneOpen(false)} />\n <ChangeEmailModal open={changeEmailOpen} onClose={() => setChangeEmailOpen(false)} />\n </>\n );\n}\n"],"mappings":"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","names":[]}
@@ -31,8 +31,9 @@ import {
31
31
  } from "../constants";
32
32
  import { useTimezones } from "../../../modules/accounts/hooks/useTimezones";
33
33
  import { useCurrencies } from "../../../modules/accounts/hooks/useCurrencies";
34
+ import { useAccountUnsavedChanges } from "../../../store/useAccountUnsavedChanges";
34
35
  import { getCurrencyForGateway } from "../../../utils/format/currency";
35
- function PreferencesSection({ onClose }) {
36
+ function PreferencesSection() {
36
37
  const translate = useTranslations();
37
38
  const translateLanguage = useTranslations("common.languages");
38
39
  const currentLocale = useLocale();
@@ -59,7 +60,10 @@ function PreferencesSection({ onClose }) {
59
60
  const [confirmGlobalOpen, setConfirmGlobalOpen] = useState(false);
60
61
  const [pendingSubmit, setPendingSubmit] = useState(null);
61
62
  const [isSaving, setIsSaving] = useState(false);
62
- const { watch, setValue, handleSubmit, reset, formState: { dirtyFields } } = useForm({
63
+ const setDirtyForm = useAccountUnsavedChanges((state) => state.setDirtyForm);
64
+ const completeSave = useAccountUnsavedChanges((state) => state.completeSave);
65
+ const dismissUnsavedChanges = useAccountUnsavedChanges((state) => state.dismiss);
66
+ const { watch, setValue, handleSubmit, reset, formState: { dirtyFields, isDirty } } = useForm({
63
67
  defaultValues: {
64
68
  companyName: "",
65
69
  language: "",
@@ -100,7 +104,7 @@ function PreferencesSection({ onClose }) {
100
104
  }
101
105
  reset(data);
102
106
  toast.custom((toastId) => /* @__PURE__ */ jsx(Toast, { variant: "success", message: translate("common.preferences.savedSuccess"), toastId }));
103
- onClose();
107
+ completeSave();
104
108
  if (data.language !== currentLocale) {
105
109
  setLocaleCookie(data.language, whitelabel?.domain);
106
110
  router.refresh();
@@ -114,6 +118,7 @@ function PreferencesSection({ onClose }) {
114
118
  toastId
115
119
  }
116
120
  ));
121
+ dismissUnsavedChanges();
117
122
  } finally {
118
123
  setIsSaving(false);
119
124
  }
@@ -142,7 +147,13 @@ function PreferencesSection({ onClose }) {
142
147
  const handleCancelGlobal = () => {
143
148
  setConfirmGlobalOpen(false);
144
149
  setPendingSubmit(null);
150
+ dismissUnsavedChanges();
145
151
  };
152
+ useEffect(() => {
153
+ if (!isDirty) return;
154
+ setDirtyForm({ submit: handleSubmit(onSubmit), discard: () => reset() });
155
+ return () => setDirtyForm(null);
156
+ });
146
157
  return /* @__PURE__ */ jsxs(Fragment, { children: [
147
158
  /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "flex flex-col h-full", children: [
148
159
  /* @__PURE__ */ jsxs("div", { className: "overflow-y-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300 overscroll-contain px-4 py-5 pb-24 lg:px-8 lg:py-6 lg:pb-28 flex flex-col gap-6 lg:gap-8 flex-1", children: [
@@ -233,23 +244,7 @@ function PreferencesSection({ onClose }) {
233
244
  }
234
245
  )
235
246
  ] }),
236
- /* @__PURE__ */ jsxs("div", { className: "fixed bottom-0 left-0 right-0 lg:absolute flex items-center h-20 px-4 lg:px-8 border-t border-gray-200 justify-between bg-white z-10", children: [
237
- /* @__PURE__ */ jsx(
238
- Button,
239
- {
240
- variant: "secondary",
241
- className: "h-10!",
242
- type: "button",
243
- disabled: isSaving,
244
- onClick: () => {
245
- reset();
246
- onClose();
247
- },
248
- children: translate("common.actions.cancel")
249
- }
250
- ),
251
- /* @__PURE__ */ jsx(Button, { className: "h-10!", type: "submit", disabled: isSaving, children: isSaving ? translate("common.actions.saving") : translate("common.actions.saveChanges") })
252
- ] })
247
+ /* @__PURE__ */ jsx("div", { className: "fixed bottom-0 left-0 right-0 lg:absolute flex items-center h-20 px-4 lg:px-8 border-t border-gray-200 justify-end bg-white z-10", children: /* @__PURE__ */ jsx(Button, { className: "h-10!", type: "submit", disabled: !isDirty || isSaving, children: isSaving ? translate("common.actions.saving") : translate("common.actions.saveChanges") }) })
253
248
  ] }),
254
249
  /* @__PURE__ */ jsx(
255
250
  ConfirmGlobalPreferencesModal,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/account/sections/PreferencesSection.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useEffect } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslations, useLocale } from 'next-intl';\nimport { useRouter } from 'next/navigation';\nimport { toast } from 'sonner';\nimport { IconLock } from '@tabler/icons-react';\nimport { cn } from '../../../infra/utils/clsx';\nimport { isLocale, type Locale } from '../../../i18n/config';\nimport { setLocaleCookie } from '../../../utils/intl/locale-cookie';\nimport { localeToApiLanguage } from '../../../utils/intl/locales';\nimport { Button } from '../../ui/buttons/Button';\nimport { Separator } from '../../ui/data-display/Separator';\nimport { Toast } from '../../ui/feedback/Toast';\nimport { FormField } from '../../ui/form/FormField';\nimport { ComboboxField } from '../../ui/form/ComboboxField';\nimport { SelectField } from '../../ui/form/SelectField';\nimport ConfirmGlobalPreferencesModal from '../ConfirmGlobalPreferencesModal';\nimport { FormSection } from './shared/FormSection';\nimport { useAuth } from '../../../providers/auth.provider';\nimport { useWhitelabel } from '../../../providers/whitelabel.provider';\nimport { UserProfile } from '../../../modules/users/schema';\nimport type { UpdateAccountRequest } from '../../../modules/accounts/types';\nimport {\n useUpdateAccountUser,\n useUpdateAccount,\n} from '../../../modules/accounts/hooks/useAccountManagement';\nimport {\n LANGUAGE_OPTIONS,\n TIME_FORMAT_OPTIONS,\n} from '../constants';\nimport { useTimezones } from '../../../modules/accounts/hooks/useTimezones';\nimport { useCurrencies } from '../../../modules/accounts/hooks/useCurrencies';\nimport { getCurrencyForGateway } from '../../../utils/format/currency';\n\ninterface PreferencesFormValues {\n companyName: string;\n language: string;\n currency: string;\n timezone: string;\n timeFormat: string;\n receive_sms: boolean;\n receive_email: boolean;\n}\n\ninterface PreferencesSectionProps {\n onClose: () => void;\n}\n\nexport function PreferencesSection({ onClose }: PreferencesSectionProps) {\n // Padrão raiz: um único `translate` chamado pela chave inteira (`common.<modulo>.<chave>`).\n const translate = useTranslations();\n // Exceção: chave dinâmica (option.value) numa lista → namespace escopado.\n const translateLanguage = useTranslations('common.languages');\n const currentLocale = useLocale();\n const router = useRouter();\n const { data: timezones = [] } = useTimezones();\n const { data: currencies = [] } = useCurrencies();\n const { user, account } = useAuth();\n const { whitelabel } = useWhitelabel();\n const languageOptions = LANGUAGE_OPTIONS.map((option) => ({\n ...option,\n label: translateLanguage(option.value as Locale),\n displayValue: translateLanguage(option.value as Locale),\n }));\n const timeFormatOptions = TIME_FORMAT_OPTIONS.map((option) => ({\n ...option,\n label: translate(\n `common.preferences.timeFormatOptions.${option.value === '24h' ? 'h24' : 'h12'}`,\n ),\n }));\n const isOwner = user?.profile === UserProfile.owner;\n const canEditPreferences = isOwner || user?.profile === UserProfile.admin;\n const updateAccountUser = useUpdateAccountUser();\n const updateAccount = useUpdateAccount();\n const [confirmGlobalOpen, setConfirmGlobalOpen] = useState(false);\n const [pendingSubmit, setPendingSubmit] = useState<{\n data: PreferencesFormValues;\n accountFields: UpdateAccountRequest;\n } | null>(null);\n const [isSaving, setIsSaving] = useState(false);\n\n const { watch, setValue, handleSubmit, reset, formState: { dirtyFields } } =\n useForm<PreferencesFormValues>({\n defaultValues: {\n companyName: '',\n language: '',\n currency: '',\n timezone: '',\n timeFormat: '24h',\n receive_sms: false,\n receive_email: false,\n },\n });\n\n useEffect(() => {\n if (!user || !account) return;\n reset({\n companyName: account.name ?? '',\n language: LANGUAGE_OPTIONS.find(\n (o) => o.apiValue === user.language?.toLowerCase()\n )?.value ?? 'pt-br',\n // A moeda é derivada do gateway da conta (read-only para o usuário) —\n // espelha o que useCurrencyFormatter usa no display de preços.\n currency: getCurrencyForGateway(account.gateway).toUpperCase(),\n timezone: account.timezone,\n timeFormat: '24h',\n receive_sms: user.receive_sms ?? false,\n receive_email: user.receive_email ?? false,\n });\n }, [user, account, reset]);\n\n const doSave = async (data: PreferencesFormValues, accountFields: UpdateAccountRequest) => {\n setIsSaving(true);\n try {\n const apiLanguage = isLocale(data.language) ? localeToApiLanguage(data.language) : data.language.toLowerCase();\n await updateAccountUser.mutateAsync({\n language: apiLanguage,\n receive_sms: data.receive_sms,\n receive_email: data.receive_email,\n });\n\n if (canEditPreferences && Object.keys(accountFields).length > 0) {\n await updateAccount.mutateAsync(accountFields);\n }\n\n reset(data);\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translate('common.preferences.savedSuccess')} toastId={toastId} />\n ));\n onClose();\n if (data.language !== currentLocale) {\n setLocaleCookie(data.language, whitelabel?.domain);\n router.refresh();\n }\n } catch (error) {\n toast.custom((toastId) => (\n <Toast\n variant=\"error\"\n message={error instanceof Error ? error.message : translate('common.preferences.saveError')}\n toastId={toastId}\n />\n ));\n } finally {\n setIsSaving(false);\n }\n };\n\n const onSubmit = (data: PreferencesFormValues) => {\n const accountFields: UpdateAccountRequest = {\n ...(isOwner && dirtyFields.companyName && { name: data.companyName }),\n // currency é read-only (derivada do gateway) — não enviada no update.\n ...(canEditPreferences && dirtyFields.timezone && { timezone: data.timezone }),\n };\n const globalFieldChanged = Object.keys(accountFields).length > 0;\n\n if (globalFieldChanged) {\n setPendingSubmit({ data, accountFields });\n setConfirmGlobalOpen(true);\n } else {\n doSave(data, accountFields);\n }\n };\n\n const handleConfirmGlobal = () => {\n setConfirmGlobalOpen(false);\n if (pendingSubmit) {\n doSave(pendingSubmit.data, pendingSubmit.accountFields);\n setPendingSubmit(null);\n }\n };\n\n const handleCancelGlobal = () => {\n setConfirmGlobalOpen(false);\n setPendingSubmit(null);\n };\n\n return (\n <>\n <form onSubmit={handleSubmit(onSubmit)} className=\"flex flex-col h-full\">\n <div className=\"overflow-y-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300 overscroll-contain px-4 py-5 pb-24 lg:px-8 lg:py-6 lg:pb-28 flex flex-col gap-6 lg:gap-8 flex-1\">\n <FormSection\n title={translate('common.preferences.general')}\n description={translate('common.preferences.generalDescription')}\n >\n <div className=\"flex flex-col gap-4\">\n <FormField\n label={translate('common.preferences.companyName')}\n placeholder={translate('common.preferences.companyNamePlaceholder')}\n classnameContainer={cn('w-full', !isOwner && 'cursor-not-allowed opacity-50')}\n value={watch('companyName')}\n onChange={(e) => setValue('companyName', e.target.value, { shouldDirty: true })}\n disabled={!isOwner}\n />\n\n <div className=\"flex flex-col lg:flex-row items-center gap-3\">\n <ComboboxField\n label={translate('common.preferences.language')}\n placeholder={translate('common.preferences.languagePlaceholder')}\n searchPlaceholder={translate('common.preferences.languageSearch')}\n containerClassName=\"w-full\"\n options={languageOptions}\n value={watch('language')}\n onChange={(value) => setValue('language', value, { shouldDirty: true })}\n />\n <ComboboxField\n label={translate('common.preferences.currency')}\n placeholder={translate('common.preferences.currencyPlaceholder')}\n searchPlaceholder={translate('common.preferences.currencySearch')}\n containerClassName=\"w-full\"\n options={currencies}\n value={watch('currency')}\n onChange={() => undefined}\n icon={<IconLock className=\"size-4 text-gray-400\" />}\n disabled\n />\n </div>\n </div>\n </FormSection>\n\n <Separator />\n\n <FormSection\n title={translate('common.preferences.timeDisplay')}\n description={translate('common.preferences.timeDisplayDescription')}\n >\n <div className=\"flex flex-col lg:flex-row items-center gap-3\">\n <ComboboxField\n label={translate('common.preferences.timezone')}\n placeholder={translate('common.preferences.timezonePlaceholder')}\n searchPlaceholder={translate('common.preferences.timezoneSearch')}\n containerClassName=\"w-full\"\n options={timezones}\n value={watch('timezone')}\n onChange={(value) => setValue('timezone', value, { shouldDirty: true })}\n icon={!canEditPreferences ? <IconLock className=\"size-4 text-gray-400\" /> : undefined}\n disabled={!canEditPreferences}\n />\n <SelectField\n label={translate('common.preferences.timeFormat')}\n placeholder={translate('common.preferences.timeFormatPlaceholder')}\n className=\"h-10!\"\n containerClassName=\"w-full\"\n options={timeFormatOptions}\n value={watch('timeFormat')}\n onChange={(value) =>\n setValue('timeFormat', value as string, { shouldDirty: true })\n }\n icon={<IconLock className=\"size-4 text-gray-400\" />}\n disabled\n />\n </div>\n </FormSection>\n </div>\n\n <div className=\"fixed bottom-0 left-0 right-0 lg:absolute flex items-center h-20 px-4 lg:px-8 border-t border-gray-200 justify-between bg-white z-10\">\n <Button\n variant=\"secondary\"\n className=\"h-10!\"\n type=\"button\"\n disabled={isSaving}\n onClick={() => {\n reset();\n onClose();\n }}\n >\n {translate('common.actions.cancel')}\n </Button>\n <Button className=\"h-10!\" type=\"submit\" disabled={isSaving}>\n {isSaving ? translate('common.actions.saving') : translate('common.actions.saveChanges')}\n </Button>\n </div>\n </form>\n\n <ConfirmGlobalPreferencesModal\n open={confirmGlobalOpen}\n onConfirm={handleConfirmGlobal}\n onCancel={handleCancelGlobal}\n />\n </>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/account/sections/PreferencesSection.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useEffect } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslations, useLocale } from 'next-intl';\nimport { useRouter } from 'next/navigation';\nimport { toast } from 'sonner';\nimport { IconLock } from '@tabler/icons-react';\nimport { cn } from '../../../infra/utils/clsx';\nimport { isLocale, type Locale } from '../../../i18n/config';\nimport { setLocaleCookie } from '../../../utils/intl/locale-cookie';\nimport { localeToApiLanguage } from '../../../utils/intl/locales';\nimport { Button } from '../../ui/buttons/Button';\nimport { Separator } from '../../ui/data-display/Separator';\nimport { Toast } from '../../ui/feedback/Toast';\nimport { FormField } from '../../ui/form/FormField';\nimport { ComboboxField } from '../../ui/form/ComboboxField';\nimport { SelectField } from '../../ui/form/SelectField';\nimport ConfirmGlobalPreferencesModal from '../ConfirmGlobalPreferencesModal';\nimport { FormSection } from './shared/FormSection';\nimport { useAuth } from '../../../providers/auth.provider';\nimport { useWhitelabel } from '../../../providers/whitelabel.provider';\nimport { UserProfile } from '../../../modules/users/schema';\nimport type { UpdateAccountRequest } from '../../../modules/accounts/types';\nimport {\n useUpdateAccountUser,\n useUpdateAccount,\n} from '../../../modules/accounts/hooks/useAccountManagement';\nimport {\n LANGUAGE_OPTIONS,\n TIME_FORMAT_OPTIONS,\n} from '../constants';\nimport { useTimezones } from '../../../modules/accounts/hooks/useTimezones';\nimport { useCurrencies } from '../../../modules/accounts/hooks/useCurrencies';\nimport { useAccountUnsavedChanges } from '../../../store/useAccountUnsavedChanges';\nimport { getCurrencyForGateway } from '../../../utils/format/currency';\n\ninterface PreferencesFormValues {\n companyName: string;\n language: string;\n currency: string;\n timezone: string;\n timeFormat: string;\n receive_sms: boolean;\n receive_email: boolean;\n}\n\nexport function PreferencesSection() {\n // Padrão raiz: um único `translate` chamado pela chave inteira (`common.<modulo>.<chave>`).\n const translate = useTranslations();\n // Exceção: chave dinâmica (option.value) numa lista → namespace escopado.\n const translateLanguage = useTranslations('common.languages');\n const currentLocale = useLocale();\n const router = useRouter();\n const { data: timezones = [] } = useTimezones();\n const { data: currencies = [] } = useCurrencies();\n const { user, account } = useAuth();\n const { whitelabel } = useWhitelabel();\n const languageOptions = LANGUAGE_OPTIONS.map((option) => ({\n ...option,\n label: translateLanguage(option.value as Locale),\n displayValue: translateLanguage(option.value as Locale),\n }));\n const timeFormatOptions = TIME_FORMAT_OPTIONS.map((option) => ({\n ...option,\n label: translate(\n `common.preferences.timeFormatOptions.${option.value === '24h' ? 'h24' : 'h12'}`,\n ),\n }));\n const isOwner = user?.profile === UserProfile.owner;\n const canEditPreferences = isOwner || user?.profile === UserProfile.admin;\n const updateAccountUser = useUpdateAccountUser();\n const updateAccount = useUpdateAccount();\n const [confirmGlobalOpen, setConfirmGlobalOpen] = useState(false);\n const [pendingSubmit, setPendingSubmit] = useState<{\n data: PreferencesFormValues;\n accountFields: UpdateAccountRequest;\n } | null>(null);\n const [isSaving, setIsSaving] = useState(false);\n const setDirtyForm = useAccountUnsavedChanges((state) => state.setDirtyForm);\n const completeSave = useAccountUnsavedChanges((state) => state.completeSave);\n const dismissUnsavedChanges = useAccountUnsavedChanges((state) => state.dismiss);\n\n const { watch, setValue, handleSubmit, reset, formState: { dirtyFields, isDirty } } =\n useForm<PreferencesFormValues>({\n defaultValues: {\n companyName: '',\n language: '',\n currency: '',\n timezone: '',\n timeFormat: '24h',\n receive_sms: false,\n receive_email: false,\n },\n });\n\n useEffect(() => {\n if (!user || !account) return;\n reset({\n companyName: account.name ?? '',\n language: LANGUAGE_OPTIONS.find(\n (o) => o.apiValue === user.language?.toLowerCase()\n )?.value ?? 'pt-br',\n // A moeda é derivada do gateway da conta (read-only para o usuário) —\n // espelha o que useCurrencyFormatter usa no display de preços.\n currency: getCurrencyForGateway(account.gateway).toUpperCase(),\n timezone: account.timezone,\n timeFormat: '24h',\n receive_sms: user.receive_sms ?? false,\n receive_email: user.receive_email ?? false,\n });\n }, [user, account, reset]);\n\n const doSave = async (data: PreferencesFormValues, accountFields: UpdateAccountRequest) => {\n setIsSaving(true);\n try {\n const apiLanguage = isLocale(data.language) ? localeToApiLanguage(data.language) : data.language.toLowerCase();\n await updateAccountUser.mutateAsync({\n language: apiLanguage,\n receive_sms: data.receive_sms,\n receive_email: data.receive_email,\n });\n\n if (canEditPreferences && Object.keys(accountFields).length > 0) {\n await updateAccount.mutateAsync(accountFields);\n }\n\n reset(data);\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translate('common.preferences.savedSuccess')} toastId={toastId} />\n ));\n completeSave();\n if (data.language !== currentLocale) {\n setLocaleCookie(data.language, whitelabel?.domain);\n router.refresh();\n }\n } catch (error) {\n toast.custom((toastId) => (\n <Toast\n variant=\"error\"\n message={error instanceof Error ? error.message : translate('common.preferences.saveError')}\n toastId={toastId}\n />\n ));\n dismissUnsavedChanges();\n } finally {\n setIsSaving(false);\n }\n };\n\n const onSubmit = (data: PreferencesFormValues) => {\n const accountFields: UpdateAccountRequest = {\n ...(isOwner && dirtyFields.companyName && { name: data.companyName }),\n // currency é read-only (derivada do gateway) — não enviada no update.\n ...(canEditPreferences && dirtyFields.timezone && { timezone: data.timezone }),\n };\n const globalFieldChanged = Object.keys(accountFields).length > 0;\n\n if (globalFieldChanged) {\n setPendingSubmit({ data, accountFields });\n setConfirmGlobalOpen(true);\n } else {\n doSave(data, accountFields);\n }\n };\n\n const handleConfirmGlobal = () => {\n setConfirmGlobalOpen(false);\n if (pendingSubmit) {\n doSave(pendingSubmit.data, pendingSubmit.accountFields);\n setPendingSubmit(null);\n }\n };\n\n const handleCancelGlobal = () => {\n setConfirmGlobalOpen(false);\n setPendingSubmit(null);\n dismissUnsavedChanges();\n };\n\n useEffect(() => {\n if (!isDirty) return;\n setDirtyForm({ submit: handleSubmit(onSubmit), discard: () => reset() });\n return () => setDirtyForm(null);\n });\n\n return (\n <>\n <form onSubmit={handleSubmit(onSubmit)} className=\"flex flex-col h-full\">\n <div className=\"overflow-y-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300 overscroll-contain px-4 py-5 pb-24 lg:px-8 lg:py-6 lg:pb-28 flex flex-col gap-6 lg:gap-8 flex-1\">\n <FormSection\n title={translate('common.preferences.general')}\n description={translate('common.preferences.generalDescription')}\n >\n <div className=\"flex flex-col gap-4\">\n <FormField\n label={translate('common.preferences.companyName')}\n placeholder={translate('common.preferences.companyNamePlaceholder')}\n classnameContainer={cn('w-full', !isOwner && 'cursor-not-allowed opacity-50')}\n value={watch('companyName')}\n onChange={(e) => setValue('companyName', e.target.value, { shouldDirty: true })}\n disabled={!isOwner}\n />\n\n <div className=\"flex flex-col lg:flex-row items-center gap-3\">\n <ComboboxField\n label={translate('common.preferences.language')}\n placeholder={translate('common.preferences.languagePlaceholder')}\n searchPlaceholder={translate('common.preferences.languageSearch')}\n containerClassName=\"w-full\"\n options={languageOptions}\n value={watch('language')}\n onChange={(value) => setValue('language', value, { shouldDirty: true })}\n />\n <ComboboxField\n label={translate('common.preferences.currency')}\n placeholder={translate('common.preferences.currencyPlaceholder')}\n searchPlaceholder={translate('common.preferences.currencySearch')}\n containerClassName=\"w-full\"\n options={currencies}\n value={watch('currency')}\n onChange={() => undefined}\n icon={<IconLock className=\"size-4 text-gray-400\" />}\n disabled\n />\n </div>\n </div>\n </FormSection>\n\n <Separator />\n\n <FormSection\n title={translate('common.preferences.timeDisplay')}\n description={translate('common.preferences.timeDisplayDescription')}\n >\n <div className=\"flex flex-col lg:flex-row items-center gap-3\">\n <ComboboxField\n label={translate('common.preferences.timezone')}\n placeholder={translate('common.preferences.timezonePlaceholder')}\n searchPlaceholder={translate('common.preferences.timezoneSearch')}\n containerClassName=\"w-full\"\n options={timezones}\n value={watch('timezone')}\n onChange={(value) => setValue('timezone', value, { shouldDirty: true })}\n icon={!canEditPreferences ? <IconLock className=\"size-4 text-gray-400\" /> : undefined}\n disabled={!canEditPreferences}\n />\n <SelectField\n label={translate('common.preferences.timeFormat')}\n placeholder={translate('common.preferences.timeFormatPlaceholder')}\n className=\"h-10!\"\n containerClassName=\"w-full\"\n options={timeFormatOptions}\n value={watch('timeFormat')}\n onChange={(value) =>\n setValue('timeFormat', value as string, { shouldDirty: true })\n }\n icon={<IconLock className=\"size-4 text-gray-400\" />}\n disabled\n />\n </div>\n </FormSection>\n </div>\n\n <div className=\"fixed bottom-0 left-0 right-0 lg:absolute flex items-center h-20 px-4 lg:px-8 border-t border-gray-200 justify-end bg-white z-10\">\n <Button className=\"h-10!\" type=\"submit\" disabled={!isDirty || isSaving}>\n {isSaving ? translate('common.actions.saving') : translate('common.actions.saveChanges')}\n </Button>\n </div>\n </form>\n\n <ConfirmGlobalPreferencesModal\n open={confirmGlobalOpen}\n onConfirm={handleConfirmGlobal}\n onCancel={handleCancelGlobal}\n />\n </>\n );\n}\n"],"mappings":"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","names":[]}
@@ -51,6 +51,11 @@ const messages = {
51
51
  saveError: "Error saving preferences"
52
52
  },
53
53
  account: {
54
+ unsavedChanges: {
55
+ title: "Leave without saving?",
56
+ description: "You have unsaved changes. Do you want to save before leaving?",
57
+ discard: "Leave without saving"
58
+ },
54
59
  notificationTypes: {
55
60
  projectUpdates: "Project updates",
56
61
  securityAlerts: "Security alerts",