@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.
- package/dist/components/account/AccountUnsavedChangesDialog.mjs +34 -0
- package/dist/components/account/AccountUnsavedChangesDialog.mjs.map +1 -0
- package/dist/components/account/MyAccountModal.mjs +28 -22
- package/dist/components/account/MyAccountModal.mjs.map +1 -1
- package/dist/components/account/sections/MyProfileSection.mjs +20 -22
- package/dist/components/account/sections/MyProfileSection.mjs.map +1 -1
- package/dist/components/account/sections/PreferencesSection.mjs +15 -20
- package/dist/components/account/sections/PreferencesSection.mjs.map +1 -1
- package/dist/i18n/messages/en-us.mjs +5 -0
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +5 -0
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +5 -0
- package/dist/i18n/messages/pt-br.mjs.map +1 -1
- package/dist/middlewares/create-auth-middleware.mjs +1 -1
- package/dist/middlewares/create-auth-middleware.mjs.map +1 -1
- package/dist/store/useAccountUnsavedChanges.mjs +31 -0
- package/dist/store/useAccountUnsavedChanges.mjs.map +1 -0
- package/package.json +1 -1
- package/src/components/account/AccountUnsavedChangesDialog.tsx +49 -0
- package/src/components/account/MyAccountModal.tsx +41 -34
- package/src/components/account/sections/MyProfileSection.tsx +24 -20
- package/src/components/account/sections/PreferencesSection.tsx +17 -21
- package/src/i18n/messages/en-us.ts +5 -0
- package/src/i18n/messages/es-es.ts +5 -0
- package/src/i18n/messages/pt-br.ts +5 -0
- package/src/middlewares/create-auth-middleware.ts +1 -1
- 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, {
|
|
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, {
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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":";AAuFe,SA0GL,UA1GK,KA0GL,YA1GK;AApFf;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AACtC,SAAS,2BAA2B;AACpC,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,OAAO,uBAAuB;AAC9B,OAAO,iCAAiC;AACxC,SAAS,0BAA0B;AACnC,SAAS,4BAA4B;AACrC,SAAS,wBAA+C;AACxD,SAAS,gCAAgC;AACzC,SAAS,eAAe;AACxB,SAAS,gCAAgC;AACzC,SAAS,8BAA8B;AACvC,SAAS,8BAA8B;AACvC,SAAS,wBAAwB,qBAAqB;AAWvC,SAAR,iBAAkC;AACvC,QAAM,YAAY,gBAAgB;AAClC,QAAM,EAAE,aAAa,gBAAgB,mBAAmB,wBAAwB,MAAM,IAAI,iBAAiB;AAC3G,QAAM,EAAE,SAAS,eAAe,IAAI,yBAAyB;AAC7D,QAAM,EAAE,WAAW,iBAAiB,IAAI,QAAQ;AAChD,QAAM,EAAE,sBAAsB,IAAI,uBAAuB;AACzD,QAAM,EAAE,WAAW,IAAI,cAAc;AACrC,QAAM,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,uBAAuB;AAC5E,QAAM,sBAAsB,yBAAyB,CAAC,UAAU,MAAM,KAAK;AAE3E,QAAM,OAAO,gBAAgB;AAG7B,QAAM,yBACJ,CAAC,CAAC,gBACF,aAAa,SAAS,UACtB,aAAa,SAAS,WACtB,aAAa,WAAW,QACxB,CAAC,aAAa,qBACd,CAAC,uBAAuB,aAAa,QAAQ;AAE/C,QAAM,sBAAsB,MAAM;AAChC,UAAM;AACN,QAAI,wBAAwB;AAC1B,6BAAuB;AAAA,IACzB,OAAO;AACL,wBAAkB;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,kBAAyC;AAAA,IAC7C;AAAA,MACE,OAAO,mBAAmB;AAAA,MAC1B,MAAM;AAAA,MACN,OAAO,UAAU,8BAA8B;AAAA,MAC/C,SAAS;AAAA,MACT,SAAS,oBAAC,oBAAiB;AAAA,IAC7B;AAAA,IACA;AAAA,MACE,OAAO,mBAAmB;AAAA,MAC1B,MAAM;AAAA,MACN,OAAO,UAAU,0BAA0B;AAAA,MAC3C,SAAS;AAAA,MACT,SAAS,oBAAC,sBAAmB;AAAA,IAC/B;AAAA,IACA;AAAA,MACE,OAAO,mBAAmB;AAAA,MAC1B,MAAM;AAAA,MACN,OAAO,UAAU,4BAA4B;AAAA,MAC7C,SAAS;AAAA,MACT,SAAS,oBAAC,gBAAa;AAAA,IACzB;AAAA,IACA;AAAA,MACE,OAAO,mBAAmB;AAAA,MAC1B,MAAM;AAAA,MACN,OAAO,UAAU,+BAA+B;AAAA,MAChD,SAAS;AAAA,MACT,SACE;AAAA,QAAC;AAAA;AAAA,UACC,kBAAkB,CAAC,YAAY,iBAAiB,OAAO;AAAA,UACvD,SAAS;AAAA,UACT,iBAAiB;AAAA;AAAA,MACnB;AAAA,IAEJ;AAAA,EACF;AAEA,QAAM,oBAA2C;AAAA,IAC/C;AAAA,MACE,OAAO,qBAAqB;AAAA,MAC5B,MAAM;AAAA,MACN,OAAO,UAAU,2CAA2C;AAAA,MAC5D,SAAS,kBAAkB,CAAC;AAAA,MAC5B,SAAS,oBAAC,uBAAoB;AAAA,IAChC;AAAA,IACA;AAAA,MACE,OAAO,qBAAqB;AAAA,MAC5B,MAAM;AAAA,MACN,OAAO,UAAU,uCAAuC;AAAA,MACxD,SAAS;AAAA,MACT,SAAS,oBAAC,mBAAgB;AAAA,IAC5B;AAAA,IACA;AAAA,MACE,OAAO,qBAAqB;AAAA,MAC5B,MAAM;AAAA,MACN,OAAO,UAAU,mCAAmC;AAAA,MACpD,SAAS;AAAA,MACT,SAAS,oBAAC,eAAY;AAAA,IACxB;AAAA,IACA;AAAA,MACE,OAAO,qBAAqB;AAAA,MAC5B,MAAM;AAAA,MACN,OAAO,UAAU,yCAAyC;AAAA,MAC1D,SAAS,YAAY,OAAO,KAAK;AAAA,MACjC,SAAS,oBAAC,qBAAkB;AAAA,IAC9B;AAAA,EACF;AAEA,QAAM,yBAAyB,gBAAgB,OAAO,CAAC,YAAY,QAAQ,OAAO;AAClF,QAAM,2BAA2B,kBAAkB,OAAO,CAAC,YAAY,QAAQ,OAAO;AACtF,QAAM,kBAAkB,CAAC,GAAG,0BAA0B,GAAG,sBAAsB;AAG/E,QAAM,wBAA+C,UACjD;AAAA,IACE;AAAA,MACE,OAAO,mBAAmB;AAAA,MAC1B,MAAM;AAAA,MACN,OAAO,UAAU,qCAAqC;AAAA,MACtD,SAAS;AAAA,MACT,SAAS,oBAAC,yBAAsB,QAAQ,MAAM,iBAAiB,mBAAmB,QAAQ,GAAG;AAAA,IAC/F;AAAA,EACF,IACA,CAAC;AAEL,QAAM,EAAE,eAAe,iBAAiB,IAAI,kBAAkB;AAAA,IAC5D,QAAQ;AAAA,IACR;AAAA,IACA,mBAAmB,CAAC,GAAG,iBAAiB,GAAG,qBAAqB,EAAE,IAAI,CAAC,YAAY,QAAQ,KAAK;AAAA,IAChG,SAAS,CAAC;AAAA,EACZ,CAAC;AAED,QAAM,eACJ,CAAC,GAAG,iBAAiB,GAAG,qBAAqB,EAAE,KAAK,CAAC,YAAY,QAAQ,UAAU,aAAa,GAAG,SACnG,UAAU,gCAAgC;AAE5C,QAAM,iBAAiB,CAAC,aACtB,SAAS,IAAI,CAAC,YACZ,oBAAC,uBAAwC,OAAO,QAAQ,OAAO,MAAM,QAAQ,MAAM,OAAO,QAAQ,SAAxE,QAAQ,KAAuE,CAC1G;AAEH,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,SAAS,MAAM,oBAAoB,KAAK;AAAA,QACxC,OAAO,UAAU,gCAAgC;AAAA,QACjD;AAAA,QACA,eAAe,iBAAiB;AAAA,QAChC,iBAAiB,CAAC,YAAY,oBAAoB,MAAM,iBAAiB,OAAO,CAAC;AAAA,QACjF,WAAW;AAAA,QACX,YACE,iCACG;AAAA,mCAAyB,SAAS,KACjC,oBAAC,wBAAqB,OAAO,UAAU,2CAA2C,GAC/E,yBAAe,wBAAwB,GAC1C;AAAA,UAEF,oBAAC,wBAAqB,OAAO,UAAU,yCAAyC,GAC7E,yBAAe,sBAAsB,GACxC;AAAA,WACF;AAAA,QAGD;AAAA,0BAAgB,IAAI,CAAC,YACpB,oBAAC,uBAAwC,OAAO,QAAQ,OACrD,kBAAQ,WADe,QAAQ,KAElC,CACD;AAAA,UAEA,sBAAsB,IAAI,CAAC,YAC1B,oBAAC,uBAAwC,OAAO,QAAQ,OACrD,kBAAQ,WADe,QAAQ,KAElC,CACD;AAAA;AAAA;AAAA,IACH;AAAA,IAEA,oBAAC,+BAA4B;AAAA,KAC/B;AAEJ;","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({
|
|
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
|
-
|
|
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__ */
|
|
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(
|
|
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
|
|
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
|
-
|
|
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__ */
|
|
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",
|