@stackcraftmx/calcita 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/README.md +139 -0
  2. package/dist/DataTable/AuditTrailDialog.js +18 -14
  3. package/dist/DataTable/AuditTrailDialog.js.map +1 -1
  4. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +12 -6
  5. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
  6. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +8 -3
  7. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  8. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +26 -18
  9. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
  10. package/dist/DataTable/EmptyState.js +8 -3
  11. package/dist/DataTable/EmptyState.js.map +1 -1
  12. package/dist/DataTable/FilterChips.js +11 -5
  13. package/dist/DataTable/FilterChips.js.map +1 -1
  14. package/dist/DataTable/GoToPage.js +9 -4
  15. package/dist/DataTable/GoToPage.js.map +1 -1
  16. package/dist/DataTable/ServerTable.js +74 -61
  17. package/dist/DataTable/ServerTable.js.map +1 -1
  18. package/dist/DataTable/columns.js +1 -8
  19. package/dist/DataTable/columns.js.map +1 -1
  20. package/dist/access/PermissionMatrix.js +18 -9
  21. package/dist/access/PermissionMatrix.js.map +1 -1
  22. package/dist/account/AccountSecuritySection.js +8 -3
  23. package/dist/account/AccountSecuritySection.js.map +1 -1
  24. package/dist/account/DemoTotpCodeButton.js +8 -3
  25. package/dist/account/DemoTotpCodeButton.js.map +1 -1
  26. package/dist/account/ProfileScreen.js +19 -13
  27. package/dist/account/ProfileScreen.js.map +1 -1
  28. package/dist/account/SignInScreen.js +6 -2
  29. package/dist/account/SignInScreen.js.map +1 -1
  30. package/dist/appShell/AccountMenu.js +13 -5
  31. package/dist/appShell/AccountMenu.js.map +1 -1
  32. package/dist/appShell/AppLayout.js +100 -71
  33. package/dist/appShell/AppLayout.js.map +1 -1
  34. package/dist/appShell/AppearanceSettings.js +21 -13
  35. package/dist/appShell/AppearanceSettings.js.map +1 -1
  36. package/dist/appShell/AuthLayout.js +26 -13
  37. package/dist/appShell/AuthLayout.js.map +1 -1
  38. package/dist/appShell/BarActions.js +10 -5
  39. package/dist/appShell/BarActions.js.map +1 -1
  40. package/dist/appShell/BarCapsule.js +9 -4
  41. package/dist/appShell/BarCapsule.js.map +1 -1
  42. package/dist/appShell/BarSearch.js +7 -3
  43. package/dist/appShell/BarSearch.js.map +1 -1
  44. package/dist/appShell/HeaderTools.js +20 -15
  45. package/dist/appShell/HeaderTools.js.map +1 -1
  46. package/dist/appShell/MobileTabBar.js +25 -14
  47. package/dist/appShell/MobileTabBar.js.map +1 -1
  48. package/dist/appShell/SettingsMenu.js +7 -3
  49. package/dist/appShell/SettingsMenu.js.map +1 -1
  50. package/dist/appShell/SideMenu.js +53 -35
  51. package/dist/appShell/SideMenu.js.map +1 -1
  52. package/dist/appShell/SideMenuEntry.js +18 -9
  53. package/dist/appShell/SideMenuEntry.js.map +1 -1
  54. package/dist/appShell/SubmenuActionsMenu.js +6 -2
  55. package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
  56. package/dist/appShell/TabBarEditor.js +16 -7
  57. package/dist/appShell/TabBarEditor.js.map +1 -1
  58. package/dist/appShell/TopMenu.js +12 -7
  59. package/dist/appShell/TopMenu.js.map +1 -1
  60. package/dist/appShell/Wordmark.js +6 -2
  61. package/dist/appShell/Wordmark.js.map +1 -1
  62. package/dist/auth/PasswordChecklist.js +14 -7
  63. package/dist/auth/PasswordChecklist.js.map +1 -1
  64. package/dist/calendar/EventCalendar.js +26 -19
  65. package/dist/calendar/EventCalendar.js.map +1 -1
  66. package/dist/calendar/EventDetails.js +28 -13
  67. package/dist/calendar/EventDetails.js.map +1 -1
  68. package/dist/downloads/DownloadManager.js +34 -18
  69. package/dist/downloads/DownloadManager.js.map +1 -1
  70. package/dist/feedback/ConfirmDialog.js +22 -17
  71. package/dist/feedback/ConfirmDialog.js.map +1 -1
  72. package/dist/feedback/ModalWaiting.js +12 -5
  73. package/dist/feedback/ModalWaiting.js.map +1 -1
  74. package/dist/feedback/NewBuildNotice.js +16 -8
  75. package/dist/feedback/NewBuildNotice.js.map +1 -1
  76. package/dist/filepond/FileDropzone.js +39 -31
  77. package/dist/filepond/FileDropzone.js.map +1 -1
  78. package/dist/forms/FormProblemAlert.js +4 -1
  79. package/dist/forms/FormProblemAlert.js.map +1 -1
  80. package/dist/forms/auth/SignInForm.js +6 -2
  81. package/dist/forms/auth/SignInForm.js.map +1 -1
  82. package/dist/forms/security/SecondStepForm.js +6 -2
  83. package/dist/forms/security/SecondStepForm.js.map +1 -1
  84. package/dist/forms/security/TwoStepVerificationCard.js +19 -9
  85. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  86. package/dist/hookForm/FormProblemAlert.js +4 -1
  87. package/dist/hookForm/FormProblemAlert.js.map +1 -1
  88. package/dist/inbox/InboxLayout.js +47 -29
  89. package/dist/inbox/InboxLayout.js.map +1 -1
  90. package/dist/inbox/MessageList.js +53 -32
  91. package/dist/inbox/MessageList.js.map +1 -1
  92. package/dist/inbox/MessageView.js +31 -15
  93. package/dist/inbox/MessageView.js.map +1 -1
  94. package/dist/index.js +4 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/inputs/ColorInput.js +22 -17
  97. package/dist/inputs/ColorInput.js.map +1 -1
  98. package/dist/inputs/FormSection.js +64 -64
  99. package/dist/inputs/FormSection.js.map +1 -1
  100. package/dist/inputs/SelectInput.js +21 -16
  101. package/dist/inputs/SelectInput.js.map +1 -1
  102. package/dist/inputs/TextInput.js +25 -20
  103. package/dist/inputs/TextInput.js.map +1 -1
  104. package/dist/inputs/WizardLayout.js +21 -15
  105. package/dist/inputs/WizardLayout.js.map +1 -1
  106. package/dist/kanban/KanbanBoard.js +82 -50
  107. package/dist/kanban/KanbanBoard.js.map +1 -1
  108. package/dist/maps/MapView.js +19 -14
  109. package/dist/maps/MapView.js.map +1 -1
  110. package/dist/pwa/InstallAppSetting.js +4 -1
  111. package/dist/pwa/InstallAppSetting.js.map +1 -1
  112. package/dist/pwa/InstallInstructionsDialog.js +6 -2
  113. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  114. package/dist/pwa/InstallPromptBanner.js +10 -5
  115. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  116. package/dist/security/ActivityLog.js +6 -2
  117. package/dist/security/ActivityLog.js.map +1 -1
  118. package/dist/security/RecoveryCodesDialog.js +8 -3
  119. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  120. package/dist/security/SessionsCard.js +12 -5
  121. package/dist/security/SessionsCard.js.map +1 -1
  122. package/dist/theme/components/glassComponents.js +19 -16
  123. package/dist/theme/components/glassComponents.js.map +1 -1
  124. package/dist/theme/components/minimalComponents.js +6 -2
  125. package/dist/theme/components/minimalComponents.js.map +1 -1
  126. package/dist/theme/createToolkitTheme.js +8 -1
  127. package/dist/theme/createToolkitTheme.js.map +1 -1
  128. package/dist/theme/cssTokens.js +23 -0
  129. package/dist/theme/cssTokens.js.map +1 -0
  130. package/dist/theme/glassSurface.js +9 -3
  131. package/dist/theme/glassSurface.js.map +1 -1
  132. package/dist/theme/glassToasterStyles.js +2 -3
  133. package/dist/theme/glassToasterStyles.js.map +1 -1
  134. package/dist/theme/themePieces.js +16 -0
  135. package/dist/theme/themePieces.js.map +1 -0
  136. package/dist/types/DataTable/AuditTrailDialog.d.ts +1 -1
  137. package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +1 -1
  138. package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +1 -1
  139. package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +1 -1
  140. package/dist/types/DataTable/EmptyState.d.ts +1 -1
  141. package/dist/types/DataTable/FilterChips.d.ts +1 -1
  142. package/dist/types/DataTable/GoToPage.d.ts +1 -1
  143. package/dist/types/DataTable/ServerTable.d.ts +1 -1
  144. package/dist/types/access/PermissionMatrix.d.ts +1 -1
  145. package/dist/types/account/AccountSecuritySection.d.ts +1 -1
  146. package/dist/types/account/DemoTotpCodeButton.d.ts +1 -1
  147. package/dist/types/account/ProfileScreen.d.ts +1 -1
  148. package/dist/types/account/SignInScreen.d.ts +1 -1
  149. package/dist/types/appShell/AccountMenu.d.ts +1 -1
  150. package/dist/types/appShell/AppLayout.d.ts +1 -1
  151. package/dist/types/appShell/AppearanceSettings.d.ts +1 -1
  152. package/dist/types/appShell/AuthLayout.d.ts +1 -1
  153. package/dist/types/appShell/BarActions.d.ts +1 -1
  154. package/dist/types/appShell/BarCapsule.d.ts +1 -1
  155. package/dist/types/appShell/BarSearch.d.ts +1 -1
  156. package/dist/types/appShell/HeaderTools.d.ts +1 -1
  157. package/dist/types/appShell/MobileTabBar.d.ts +1 -1
  158. package/dist/types/appShell/SettingsMenu.d.ts +1 -1
  159. package/dist/types/appShell/SideMenu.d.ts +1 -1
  160. package/dist/types/appShell/SideMenuEntry.d.ts +1 -1
  161. package/dist/types/appShell/SubmenuActionsMenu.d.ts +1 -1
  162. package/dist/types/appShell/TabBarEditor.d.ts +1 -1
  163. package/dist/types/appShell/TopMenu.d.ts +1 -1
  164. package/dist/types/appShell/Wordmark.d.ts +1 -1
  165. package/dist/types/auth/PasswordChecklist.d.ts +1 -1
  166. package/dist/types/calendar/EventCalendar.d.ts +1 -1
  167. package/dist/types/calendar/EventDetails.d.ts +1 -1
  168. package/dist/types/feedback/ConfirmDialog.d.ts +1 -1
  169. package/dist/types/feedback/ModalWaiting.d.ts +1 -1
  170. package/dist/types/feedback/NewBuildNotice.d.ts +1 -1
  171. package/dist/types/filepond/FileDropzone.d.ts +1 -1
  172. package/dist/types/forms/auth/SignInForm.d.ts +1 -1
  173. package/dist/types/forms/security/SecondStepForm.d.ts +1 -1
  174. package/dist/types/forms/security/TwoStepVerificationCard.d.ts +1 -1
  175. package/dist/types/inbox/InboxLayout.d.ts +1 -1
  176. package/dist/types/inbox/MessageList.d.ts +1 -1
  177. package/dist/types/inbox/MessageView.d.ts +1 -1
  178. package/dist/types/index.d.ts +1 -0
  179. package/dist/types/inputs/ColorInput.d.ts +1 -1
  180. package/dist/types/inputs/FormSection.d.ts +2 -2
  181. package/dist/types/inputs/SelectInput.d.ts +1 -1
  182. package/dist/types/inputs/TextInput.d.ts +1 -1
  183. package/dist/types/inputs/WizardLayout.d.ts +1 -1
  184. package/dist/types/kanban/KanbanBoard.d.ts +1 -1
  185. package/dist/types/maps/MapView.d.ts +1 -1
  186. package/dist/types/pwa/InstallInstructionsDialog.d.ts +1 -1
  187. package/dist/types/pwa/InstallPromptBanner.d.ts +1 -1
  188. package/dist/types/security/ActivityLog.d.ts +1 -1
  189. package/dist/types/security/RecoveryCodesDialog.d.ts +1 -1
  190. package/dist/types/security/SessionsCard.d.ts +1 -1
  191. package/dist/types/theme/createToolkitTheme.d.ts +12 -1
  192. package/dist/types/theme/cssTokens.d.ts +46 -0
  193. package/dist/types/theme/glassSurface.d.ts +3 -0
  194. package/dist/types/theme/themePieces.d.ts +20 -0
  195. package/dist/types/ui/actions/ActionsMenu.d.ts +1 -1
  196. package/dist/types/ui/actions/DownloadMenu.d.ts +1 -1
  197. package/dist/types/ui/actions/SelectionActions.d.ts +1 -1
  198. package/dist/types/ui/filters/DateRangeFilter.d.ts +1 -1
  199. package/dist/types/ui/filters/PeriodRangeSelector.d.ts +1 -1
  200. package/dist/types/ui/filters/QuickFilters.d.ts +1 -1
  201. package/dist/types/ui/filters/SegmentedControl.d.ts +7 -2
  202. package/dist/types/ui/labels/StatusChip.d.ts +1 -1
  203. package/dist/types/ui/labels/Tag.d.ts +1 -1
  204. package/dist/types/ui/layout/CollapsibleSection.d.ts +1 -1
  205. package/dist/types/ui/layout/ContentDialog.d.ts +1 -1
  206. package/dist/types/ui/layout/DefinitionList.d.ts +1 -1
  207. package/dist/types/ui/layout/DetailHeader.d.ts +1 -1
  208. package/dist/types/ui/layout/ErrorPage.d.ts +1 -1
  209. package/dist/types/ui/layout/GlassPanel.d.ts +1 -1
  210. package/dist/types/ui/layout/PageHeader.d.ts +1 -1
  211. package/dist/types/ui/layout/PageHeading.d.ts +1 -1
  212. package/dist/types/ui/layout/ProfileHeader.d.ts +1 -1
  213. package/dist/types/ui/layout/ScrollableRow.d.ts +1 -1
  214. package/dist/types/ui/layout/SectionCard.d.ts +1 -1
  215. package/dist/types/ui/metrics/BarList.d.ts +1 -1
  216. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +1 -1
  217. package/dist/types/ui/metrics/ChartTooltip.d.ts +1 -1
  218. package/dist/types/ui/metrics/ColumnChart.d.ts +1 -1
  219. package/dist/types/ui/metrics/LineChart.d.ts +1 -1
  220. package/dist/types/ui/metrics/MetricCard.d.ts +1 -1
  221. package/dist/types/ui/metrics/TableToggle.d.ts +1 -1
  222. package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +1 -1
  223. package/dist/types/ui/navigation/FontSizeToggle.d.ts +1 -1
  224. package/dist/types/ui/navigation/LanguageToggle.d.ts +1 -1
  225. package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +1 -1
  226. package/dist/types/ui/timeline/GroupedTimeline.d.ts +1 -1
  227. package/dist/types/upload/FileDropzone.d.ts +1 -1
  228. package/dist/ui/actions/ActionsMenu.js +9 -4
  229. package/dist/ui/actions/ActionsMenu.js.map +1 -1
  230. package/dist/ui/actions/DownloadMenu.js +21 -17
  231. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  232. package/dist/ui/actions/SelectionActions.js +15 -7
  233. package/dist/ui/actions/SelectionActions.js.map +1 -1
  234. package/dist/ui/filters/DateRangeFilter.js +11 -5
  235. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  236. package/dist/ui/filters/PeriodRangeSelector.js +11 -5
  237. package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
  238. package/dist/ui/filters/QuickFilters.js +16 -8
  239. package/dist/ui/filters/QuickFilters.js.map +1 -1
  240. package/dist/ui/filters/SegmentedControl.js +12 -5
  241. package/dist/ui/filters/SegmentedControl.js.map +1 -1
  242. package/dist/ui/labels/StatusChip.js +35 -25
  243. package/dist/ui/labels/StatusChip.js.map +1 -1
  244. package/dist/ui/labels/Tag.js +8 -3
  245. package/dist/ui/labels/Tag.js.map +1 -1
  246. package/dist/ui/layout/CollapsibleSection.js +18 -8
  247. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  248. package/dist/ui/layout/ContentDialog.js +13 -6
  249. package/dist/ui/layout/ContentDialog.js.map +1 -1
  250. package/dist/ui/layout/DefinitionList.js +13 -6
  251. package/dist/ui/layout/DefinitionList.js.map +1 -1
  252. package/dist/ui/layout/DetailHeader.js +14 -6
  253. package/dist/ui/layout/DetailHeader.js.map +1 -1
  254. package/dist/ui/layout/ErrorPage.js +20 -15
  255. package/dist/ui/layout/ErrorPage.js.map +1 -1
  256. package/dist/ui/layout/FullPageLoader.js +4 -1
  257. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  258. package/dist/ui/layout/GlassPanel.js +8 -4
  259. package/dist/ui/layout/GlassPanel.js.map +1 -1
  260. package/dist/ui/layout/PageHeader.js +10 -4
  261. package/dist/ui/layout/PageHeader.js.map +1 -1
  262. package/dist/ui/layout/PageHeading.js +17 -8
  263. package/dist/ui/layout/PageHeading.js.map +1 -1
  264. package/dist/ui/layout/ProfileHeader.js +21 -10
  265. package/dist/ui/layout/ProfileHeader.js.map +1 -1
  266. package/dist/ui/layout/ScrollableRow.js +19 -15
  267. package/dist/ui/layout/ScrollableRow.js.map +1 -1
  268. package/dist/ui/layout/SectionCard.js +10 -4
  269. package/dist/ui/layout/SectionCard.js.map +1 -1
  270. package/dist/ui/metrics/BarList.js +9 -4
  271. package/dist/ui/metrics/BarList.js.map +1 -1
  272. package/dist/ui/metrics/CalendarHeatmap.js +40 -21
  273. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  274. package/dist/ui/metrics/ChartTooltip.js +6 -2
  275. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  276. package/dist/ui/metrics/ColumnChart.js +22 -18
  277. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  278. package/dist/ui/metrics/LineChart.js +20 -16
  279. package/dist/ui/metrics/LineChart.js.map +1 -1
  280. package/dist/ui/metrics/MetricCard.js +42 -24
  281. package/dist/ui/metrics/MetricCard.js.map +1 -1
  282. package/dist/ui/metrics/TableToggle.js +7 -2
  283. package/dist/ui/metrics/TableToggle.js.map +1 -1
  284. package/dist/ui/metrics/chartParts.js +14 -5
  285. package/dist/ui/metrics/chartParts.js.map +1 -1
  286. package/dist/ui/navigation/ColorSchemeToggle.js +8 -4
  287. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  288. package/dist/ui/navigation/FontSizeToggle.js +6 -2
  289. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  290. package/dist/ui/navigation/LanguageToggle.js +8 -4
  291. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  292. package/dist/ui/navigation/PageBreadcrumbs.js +12 -6
  293. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  294. package/dist/ui/timeline/GroupedTimeline.js +26 -13
  295. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  296. package/dist/upload/FileDropzone.js +35 -25
  297. package/dist/upload/FileDropzone.js.map +1 -1
  298. package/package.json +1 -1
@@ -11,20 +11,28 @@ import Stack from "@mui/material/Stack";
11
11
  import Typography from "@mui/material/Typography";
12
12
  import { groupStateOf, withPermissionGroup, withPermission } from "./permissionGroups.js";
13
13
  import ComponentTexts from "../toolkit/ComponentTexts.js";
14
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
15
+ const THEME_NAME = "CalcitaPermissionMatrix";
16
+ const PermissionsPermission = piecePart(THEME_NAME, "Permission", FormControlLabel);
17
+ const PermissionsCheckbox = piecePart(THEME_NAME, "Checkbox", Checkbox);
18
+ const PermissionsGroup = piecePart(THEME_NAME, "Group", Paper);
19
+ const PermissionsGroupHeader = piecePart(THEME_NAME, "GroupHeader", Stack);
20
+ const PermissionsGroupTitle = piecePart(THEME_NAME, "GroupTitle", Typography);
21
+ const PermissionsGrid = piecePart(THEME_NAME, "Grid", Box);
14
22
  function PermissionOption({ permission, isGranted, readOnly, onToggle }) {
15
23
  return /* @__PURE__ */ jsx(
16
- FormControlLabel,
24
+ PermissionsPermission,
17
25
  {
18
- sx: { alignItems: "flex-start", m: 0, gap: 0.5 },
26
+ ownSx: { alignItems: "flex-start", m: 0, gap: 0.5 },
19
27
  control: /* @__PURE__ */ jsx(
20
- Checkbox,
28
+ PermissionsCheckbox,
21
29
  {
22
30
  size: "small",
23
31
  checked: isGranted,
24
32
  disabled: readOnly,
25
33
  onChange: (event) => onToggle(event.target.checked),
26
34
  slotProps: { input: { "data-permission": permission.key } },
27
- sx: { mt: -0.5 }
35
+ ownSx: { mt: -0.5 }
28
36
  }
29
37
  ),
30
38
  label: /* @__PURE__ */ jsxs(Box, { children: [
@@ -38,8 +46,8 @@ function PermissionGroup({ group, value, onChange, isReadOnly }) {
38
46
  const { t } = useToolkitTranslation("access");
39
47
  const titleId = useId();
40
48
  const state = groupStateOf(group, value);
41
- return /* @__PURE__ */ jsxs(Paper, { variant: "outlined", role: "group", "aria-labelledby": titleId, "data-group": group.key, sx: { overflow: "hidden" }, children: [
42
- /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center", px: 1.5, py: 0.75, borderBottom: 1, borderColor: "divider", bgcolor: "action.hover" }, children: [
49
+ return /* @__PURE__ */ jsxs(PermissionsGroup, { variant: "outlined", role: "group", "aria-labelledby": titleId, "data-group": group.key, ownSx: { overflow: "hidden" }, children: [
50
+ /* @__PURE__ */ jsxs(PermissionsGroupHeader, { direction: "row", spacing: 1, ownSx: { alignItems: "center", px: 1.5, py: 0.75, borderBottom: 1, borderColor: "divider", bgcolor: "action.hover" }, children: [
43
51
  /* @__PURE__ */ jsx(
44
52
  Checkbox,
45
53
  {
@@ -51,7 +59,7 @@ function PermissionGroup({ group, value, onChange, isReadOnly }) {
51
59
  slotProps: { input: { "aria-label": t("permissions.allOf", { group: group.label }) } }
52
60
  }
53
61
  ),
54
- /* @__PURE__ */ jsx(Typography, { id: titleId, variant: "subtitle2", component: "h3", sx: { flexGrow: 1 }, children: group.label }),
62
+ /* @__PURE__ */ jsx(PermissionsGroupTitle, { id: titleId, variant: "subtitle2", component: "h3", ownSx: { flexGrow: 1 }, children: group.label }),
55
63
  /* @__PURE__ */ jsx(
56
64
  Chip,
57
65
  {
@@ -62,7 +70,7 @@ function PermissionGroup({ group, value, onChange, isReadOnly }) {
62
70
  }
63
71
  )
64
72
  ] }),
65
- /* @__PURE__ */ jsx(Box, { sx: { display: "grid", gridTemplateColumns: { xs: "1fr", md: "repeat(2, minmax(0, 1fr))" }, gap: 1.5, p: 2 }, children: group.permissions.map((permission) => /* @__PURE__ */ jsx(
73
+ /* @__PURE__ */ jsx(PermissionsGrid, { ownSx: { display: "grid", gridTemplateColumns: { xs: "1fr", md: "repeat(2, minmax(0, 1fr))" }, gap: 1.5, p: 2 }, children: group.permissions.map((permission) => /* @__PURE__ */ jsx(
66
74
  PermissionOption,
67
75
  {
68
76
  permission,
@@ -74,7 +82,8 @@ function PermissionGroup({ group, value, onChange, isReadOnly }) {
74
82
  )) })
75
83
  ] });
76
84
  }
77
- function PermissionMatrix({ groups, value, onChange, readOnly = false, texts }) {
85
+ function PermissionMatrix(props) {
86
+ const { groups, value, onChange, readOnly = false, texts } = usePieceProps(props, THEME_NAME);
78
87
  const isReadOnly = readOnly || !onChange;
79
88
  return /* @__PURE__ */ jsx(ComponentTexts, { namespace: "access", texts, children: /* @__PURE__ */ jsx(Stack, { spacing: 1.5, children: groups.map((group) => /* @__PURE__ */ jsx(PermissionGroup, { group, value, onChange, isReadOnly }, group.key)) }) });
80
89
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PermissionMatrix.js","sources":["../../src/access/PermissionMatrix.jsx"],"sourcesContent":["// Los permisos de un perfil, agrupados (un grupo por módulo), para la pantalla de «Perfiles y permisos»: cada grupo con\n// su casilla de «todos» (a medias si tiene algunos) y cuántos tiene, y cada permiso con su nombre y, si hace falta, qué\n// deja hacer en palabras normales.\n//\n// <PermissionMatrix groups={PERMISSION_GROUPS} value={profile.abilities} onChange={setAbilities} />\n//\n// Los permisos son los mismos textos que piden los menu.js y las rutas (access: {ability: 'orders.view'}), así que lo que\n// se palomea aquí es lo que el menú enseña o esconde. La lista de grupos la arma el proyecto (RBAC propio); guardar es\n// cosa de la pantalla.\n//\n// Genérico del paquete: function (regla de la casa). No sabe de negocio: todo llega ya traducido.\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Checkbox from '@mui/material/Checkbox';\nimport Chip from '@mui/material/Chip';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Paper from '@mui/material/Paper';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {groupStateOf, withPermission, withPermissionGroup} from '@/access/permissionGroups.js';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\n\n/**\n * Un permiso: su casilla, su nombre y qué deja hacer.\n *\n * @param {{permission: {key: string, label: string, description?: string}, isGranted: boolean, readOnly: boolean, onToggle: (isGranted: boolean) => void}} props\n */\nfunction PermissionOption({permission, isGranted, readOnly, onToggle}) {\n return (\n <FormControlLabel\n sx={{alignItems: 'flex-start', m: 0, gap: 0.5}}\n control={<Checkbox size=\"small\" checked={isGranted} disabled={readOnly} onChange={(event) => onToggle(event.target.checked)}\n slotProps={{input: {'data-permission': permission.key}}} sx={{mt: -0.5}} />}\n label={(\n <Box>\n <Typography variant=\"body2\">{permission.label}</Typography>\n {permission.description && <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\">{permission.description}</Typography>}\n </Box>\n )}\n />\n );\n}\n\n/**\n * Un grupo: su encabezado con «todos» y cuántos tiene, y sus permisos.\n *\n * OJO: es un grupo con role=\"group\" y no un <fieldset>: el navegador pinta el <legend> encima del borde y dejaba una franja\n * blanca arriba del encabezado.\n *\n * @param {{group: object, value: string[], onChange: Function, isReadOnly: boolean}} props\n */\nfunction PermissionGroup({group, value, onChange, isReadOnly}) {\n const {t} = useToolkitTranslation('access');\n const titleId = useId();\n const state = groupStateOf(group, value);\n return (\n <Paper variant=\"outlined\" role=\"group\" aria-labelledby={titleId} data-group={group.key} sx={{overflow: 'hidden'}}>\n <Stack direction=\"row\" spacing={1} sx={{alignItems: 'center', px: 1.5, py: 0.75, borderBottom: 1, borderColor: 'divider', bgcolor: 'action.hover'}}>\n <Checkbox size=\"small\" checked={state.isAll} indeterminate={state.isSome} disabled={isReadOnly || state.total === 0}\n onChange={(event) => onChange(withPermissionGroup(value, group, event.target.checked))}\n slotProps={{input: {'aria-label': t('permissions.allOf', {group: group.label})}}} />\n <Typography id={titleId} variant=\"subtitle2\" component=\"h3\" sx={{flexGrow: 1}}>{group.label}</Typography>\n <Chip size=\"small\" label={t('permissions.countOf', {count: state.grantedCount, total: state.total})}\n color={state.grantedCount > 0 ? 'primary' : 'default'} variant={state.isAll ? 'filled' : 'outlined'} />\n </Stack>\n <Box sx={{display: 'grid', gridTemplateColumns: {xs: '1fr', md: 'repeat(2, minmax(0, 1fr))'}, gap: 1.5, p: 2}}>\n {group.permissions.map((permission) => (\n <PermissionOption key={permission.key} permission={permission} isGranted={value.includes(permission.key)} readOnly={isReadOnly}\n onToggle={(isGranted) => onChange(withPermission(value, permission.key, isGranted))} />\n ))}\n </Box>\n </Paper>\n );\n}\n\n/**\n * @param {object} props\n * @param {Array<{key: string, label: string, permissions: Array<{key: string, label: string, description?: string}>}>} props.groups\n * @param {string[]} props.value los permisos del perfil\n * @param {(value: string[]) => void} [props.onChange] sin ella, o con readOnly, solo se ven\n * @param {boolean} [props.readOnly] un perfil que no se puede cambiar (el de administración)\n * @param {object} [props.texts] los textos que cambian, con la forma de `access` en los textos del paquete\n */\nexport default function PermissionMatrix({groups, value, onChange, readOnly = false, texts}) {\n const isReadOnly = readOnly || !onChange;\n return (\n <ComponentTexts namespace=\"access\" texts={texts}>\n <Stack spacing={1.5}>\n {groups.map((group) => <PermissionGroup key={group.key} group={group} value={value} onChange={onChange} isReadOnly={isReadOnly} />)}\n </Stack>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PermissionMatrix.js","sources":["../../src/access/PermissionMatrix.jsx"],"sourcesContent":["// Los permisos de un perfil, agrupados (un grupo por módulo), para la pantalla de «Perfiles y permisos»: cada grupo con\n// su casilla de «todos» (a medias si tiene algunos) y cuántos tiene, y cada permiso con su nombre y, si hace falta, qué\n// deja hacer en palabras normales.\n//\n// <PermissionMatrix groups={PERMISSION_GROUPS} value={profile.abilities} onChange={setAbilities} />\n//\n// Los permisos son los mismos textos que piden los menu.js y las rutas (access: {ability: 'orders.view'}), así que lo que\n// se palomea aquí es lo que el menú enseña o esconde. La lista de grupos la arma el proyecto (RBAC propio); guardar es\n// cosa de la pantalla.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaPermissionMatrix con defaultProps y\n// styleOverrides de sus partes: permission, checkbox, group, groupHeader, groupTitle, grid.\n//\n// Genérico del paquete: function (regla de la casa). No sabe de negocio: todo llega ya traducido.\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Checkbox from '@mui/material/Checkbox';\nimport Chip from '@mui/material/Chip';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Paper from '@mui/material/Paper';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {groupStateOf, withPermission, withPermissionGroup} from '@/access/permissionGroups.js';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaPermissionMatrix)\nconst THEME_NAME = 'CalcitaPermissionMatrix';\nconst PermissionsPermission = piecePart(THEME_NAME, 'Permission', FormControlLabel);\nconst PermissionsCheckbox = piecePart(THEME_NAME, 'Checkbox', Checkbox);\nconst PermissionsGroup = piecePart(THEME_NAME, 'Group', Paper);\nconst PermissionsGroupHeader = piecePart(THEME_NAME, 'GroupHeader', Stack);\nconst PermissionsGroupTitle = piecePart(THEME_NAME, 'GroupTitle', Typography);\nconst PermissionsGrid = piecePart(THEME_NAME, 'Grid', Box);\n\n/**\n * Un permiso: su casilla, su nombre y qué deja hacer.\n *\n * @param {{permission: {key: string, label: string, description?: string}, isGranted: boolean, readOnly: boolean, onToggle: (isGranted: boolean) => void}} props\n */\nfunction PermissionOption({permission, isGranted, readOnly, onToggle}) {\n return (\n <PermissionsPermission\n ownSx={{alignItems: 'flex-start', m: 0, gap: 0.5}}\n control={<PermissionsCheckbox size=\"small\" checked={isGranted} disabled={readOnly} onChange={(event) => onToggle(event.target.checked)}\n slotProps={{input: {'data-permission': permission.key}}} ownSx={{mt: -0.5}} />}\n label={(\n <Box>\n <Typography variant=\"body2\">{permission.label}</Typography>\n {permission.description && <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\">{permission.description}</Typography>}\n </Box>\n )}\n />\n );\n}\n\n/**\n * Un grupo: su encabezado con «todos» y cuántos tiene, y sus permisos.\n *\n * OJO: es un grupo con role=\"group\" y no un <fieldset>: el navegador pinta el <legend> encima del borde y dejaba una franja\n * blanca arriba del encabezado.\n *\n * @param {{group: object, value: string[], onChange: Function, isReadOnly: boolean}} props\n */\nfunction PermissionGroup({group, value, onChange, isReadOnly}) {\n const {t} = useToolkitTranslation('access');\n const titleId = useId();\n const state = groupStateOf(group, value);\n return (\n <PermissionsGroup variant=\"outlined\" role=\"group\" aria-labelledby={titleId} data-group={group.key} ownSx={{overflow: 'hidden'}}>\n <PermissionsGroupHeader direction=\"row\" spacing={1} ownSx={{alignItems: 'center', px: 1.5, py: 0.75, borderBottom: 1, borderColor: 'divider', bgcolor: 'action.hover'}}>\n <Checkbox size=\"small\" checked={state.isAll} indeterminate={state.isSome} disabled={isReadOnly || state.total === 0}\n onChange={(event) => onChange(withPermissionGroup(value, group, event.target.checked))}\n slotProps={{input: {'aria-label': t('permissions.allOf', {group: group.label})}}} />\n <PermissionsGroupTitle id={titleId} variant=\"subtitle2\" component=\"h3\" ownSx={{flexGrow: 1}}>{group.label}</PermissionsGroupTitle>\n <Chip size=\"small\" label={t('permissions.countOf', {count: state.grantedCount, total: state.total})}\n color={state.grantedCount > 0 ? 'primary' : 'default'} variant={state.isAll ? 'filled' : 'outlined'} />\n </PermissionsGroupHeader>\n <PermissionsGrid ownSx={{display: 'grid', gridTemplateColumns: {xs: '1fr', md: 'repeat(2, minmax(0, 1fr))'}, gap: 1.5, p: 2}}>\n {group.permissions.map((permission) => (\n <PermissionOption key={permission.key} permission={permission} isGranted={value.includes(permission.key)} readOnly={isReadOnly}\n onToggle={(isGranted) => onChange(withPermission(value, permission.key, isGranted))} />\n ))}\n </PermissionsGrid>\n </PermissionsGroup>\n );\n}\n\n/**\n * @param {object} props\n * @param {Array<{key: string, label: string, permissions: Array<{key: string, label: string, description?: string}>}>} props.groups\n * @param {string[]} props.value los permisos del perfil\n * @param {(value: string[]) => void} [props.onChange] sin ella, o con readOnly, solo se ven\n * @param {boolean} [props.readOnly] un perfil que no se puede cambiar (el de administración)\n * @param {object} [props.texts] los textos que cambian, con la forma de `access` en los textos del paquete\n */\nexport default function PermissionMatrix(props) {\n // lo que el proyecto puso en su tema (components.CalcitaPermissionMatrix.defaultProps) y encima lo que llega aquí\n const {groups, value, onChange, readOnly = false, texts} = usePieceProps(props, THEME_NAME);\n const isReadOnly = readOnly || !onChange;\n return (\n <ComponentTexts namespace=\"access\" texts={texts}>\n <Stack spacing={1.5}>\n {groups.map((group) => <PermissionGroup key={group.key} group={group} value={value} onChange={onChange} isReadOnly={isReadOnly} />)}\n </Stack>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -14,9 +14,14 @@ import { createPasskey, passkeysSupported } from "../security/passkeys.js";
14
14
  import SessionsCard from "../security/SessionsCard.js";
15
15
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
16
16
  import SectionCard from "../ui/layout/SectionCard.js";
17
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
18
+ const THEME_NAME = "CalcitaAccountSecurity";
19
+ const SecurityTitle = piecePart(THEME_NAME, "Title", Typography);
20
+ const SecurityForm = piecePart(THEME_NAME, "Form", Stack);
17
21
  const FORM_MAX_WIDTH = 520;
18
22
  const LOADING_HEIGHT_PIXELS = 220;
19
- function AccountSecuritySection({ showPasswordChange = true, texts }) {
23
+ function AccountSecuritySection(props) {
24
+ const { showPasswordChange = true, texts } = usePieceProps(props, THEME_NAME);
20
25
  const { t } = useToolkitTranslation("account", texts);
21
26
  const { connector, session } = useAccount();
22
27
  const [security, setSecurity] = useState(null);
@@ -50,8 +55,8 @@ function AccountSecuritySection({ showPasswordChange = true, texts }) {
50
55
  return /* @__PURE__ */ jsxs(Stack, { spacing: 3, children: [
51
56
  loadError && /* @__PURE__ */ jsx(Alert, { severity: "error", children: loadError }),
52
57
  showPasswordChange && /* @__PURE__ */ jsxs(SectionCard, { title: t("profile.password.title"), children: [
53
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: t("profile.password.text") }),
54
- /* @__PURE__ */ jsx(Stack, { sx: { maxWidth: FORM_MAX_WIDTH }, children: /* @__PURE__ */ jsx(ChangePasswordForm, { onSubmit: async (values) => {
58
+ /* @__PURE__ */ jsx(SecurityTitle, { variant: "body2", color: "text.secondary", ownSx: { mb: 2 }, children: t("profile.password.text") }),
59
+ /* @__PURE__ */ jsx(SecurityForm, { ownSx: { maxWidth: FORM_MAX_WIDTH }, children: /* @__PURE__ */ jsx(ChangePasswordForm, { onSubmit: async (values) => {
55
60
  await connector.changePassword(values);
56
61
  refreshActivity();
57
62
  } }) })
@@ -1 +1 @@
1
- {"version":3,"file":"AccountSecuritySection.js","sources":["../../src/account/AccountSecuritySection.jsx"],"sourcesContent":["// La seguridad de la cuenta, completa: cambiar la contraseña (ChangePasswordForm), la verificación en dos pasos\n// (TwoStepVerificationCard: llaves de acceso, app autenticadora y códigos de recuperación), las sesiones abiertas\n// (SessionsCard) y la bitácora (ActivityLog). Todo lo del servidor sale del conector de la cuenta.\n//\n// Las llaves de acceso se crean aquí con el navegador (createPasskey) y la respuesta va al servidor (savePasskey). La\n// bitácora se vuelve a pedir después de cada cambio: así se ve aparecer lo que se acaba de hacer.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useCallback, useEffect, useMemo, useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Skeleton from '@mui/material/Skeleton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {useAccount} from '@/account/AccountProvider.jsx';\nimport DemoTotpCodeButton from '@/account/DemoTotpCodeButton.jsx';\nimport ChangePasswordForm from '@/forms/auth/ChangePasswordForm.jsx';\nimport TwoStepVerificationCard from '@/forms/security/TwoStepVerificationCard.jsx';\nimport ActivityLog from '@/security/ActivityLog.jsx';\nimport {createPasskey, passkeysSupported} from '@/security/passkeys.js';\nimport SessionsCard from '@/security/SessionsCard.jsx';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\n\n// lo ancho del formulario de la contraseña: un renglón cómodo\nconst FORM_MAX_WIDTH = 520;\nconst LOADING_HEIGHT_PIXELS = 220;\n\n/**\n * @param {object} props\n * @param {boolean} [props.showPasswordChange] por omisión sí\n * @param {object} [props.texts] los textos que cambian, con la forma de `account` en los textos del paquete\n */\nexport default function AccountSecuritySection({showPasswordChange = true, texts}) {\n const {t} = useToolkitTranslation('account', texts);\n const {connector, session} = useAccount();\n const [security, setSecurity] = useState(null);\n const [sessions, setSessions] = useState(null);\n const [events, setEvents] = useState(null);\n const [loadError, setLoadError] = useState(null);\n const isDemo = typeof connector.demoTotpCode === 'function';\n\n const refreshActivity = useCallback(() => connector.listActivity().then(setEvents).catch((error) => setLoadError(error.message)), [connector]);\n const refreshSessions = useCallback(() => connector.listSessions().then(setSessions).catch((error) => setLoadError(error.message)), [connector]);\n\n useEffect(() => {\n connector.loadSecurity().then(setSecurity).catch((error) => setLoadError(error.message));\n refreshSessions();\n refreshActivity();\n }, [connector, refreshActivity, refreshSessions]);\n\n // lo que hace la tarjeta de dos pasos en cada caso, con el conector\n const actions = useMemo(() => ({\n addPasskey: async (name) => connector.savePasskey({name, credential: await createPasskey(await connector.passkeyRegistrationOptions())}),\n removePasskey: (passkey, password) => connector.removePasskey({passkeyId: passkey.id, password}),\n startTotp: async () => ({\n ...await connector.startTotp(),\n // en la demostración, el atajo del código abajo del campo\n extra: isDemo ? <DemoTotpCodeButton loadCode={connector.demoTotpCode} /> : undefined,\n }),\n confirmTotp: (code) => connector.confirmTotp(code),\n disableTotp: (password) => connector.disableTotp(password),\n regenerateRecoveryCodes: (password) => connector.regenerateRecoveryCodes(password),\n }), [connector, isDemo]);\n\n const changeSecurity = (nextSecurity) => {\n setSecurity(nextSecurity);\n refreshActivity();\n };\n\n return (\n <Stack spacing={3}>\n {loadError && <Alert severity=\"error\">{loadError}</Alert>}\n {showPasswordChange && (\n <SectionCard title={t('profile.password.title')}>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{mb: 2}}>{t('profile.password.text')}</Typography>\n <Stack sx={{maxWidth: FORM_MAX_WIDTH}}>\n <ChangePasswordForm onSubmit={async (values) => {\n await connector.changePassword(values);\n refreshActivity();\n }} />\n </Stack>\n </SectionCard>\n )}\n {security\n ? <TwoStepVerificationCard security={security} onSecurityChange={changeSecurity} actions={actions}\n canUsePasskeys={passkeysSupported()} account={session?.user.email}\n // en la demostración la llave es de verdad pero el servidor se olvida al recargar:\n // se avisa antes de crearla para que nadie se quede con una llave huérfana\n passkeyNotice={isDemo ? t('demo.passkeyNote') : undefined} />\n : <Skeleton variant=\"rounded\" height={LOADING_HEIGHT_PIXELS} />}\n {isDemo && <Alert severity=\"info\">{t('demo.securityNote')}</Alert>}\n <SessionsCard sessions={sessions} onCloseOthers={connector.closeOtherSessions} onClosed={() => {\n refreshSessions();\n refreshActivity();\n }} />\n <ActivityLog events={events} />\n </Stack>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AccountSecuritySection.js","sources":["../../src/account/AccountSecuritySection.jsx"],"sourcesContent":["// La seguridad de la cuenta, completa: cambiar la contraseña (ChangePasswordForm), la verificación en dos pasos\n// (TwoStepVerificationCard: llaves de acceso, app autenticadora y códigos de recuperación), las sesiones abiertas\n// (SessionsCard) y la bitácora (ActivityLog). Todo lo del servidor sale del conector de la cuenta.\n//\n// Las llaves de acceso se crean aquí con el navegador (createPasskey) y la respuesta va al servidor (savePasskey). La\n// bitácora se vuelve a pedir después de cada cambio: así se ve aparecer lo que se acaba de hacer.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaAccountSecurity con defaultProps y\n// styleOverrides de sus partes: title, form.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useCallback, useEffect, useMemo, useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Skeleton from '@mui/material/Skeleton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {useAccount} from '@/account/AccountProvider.jsx';\nimport DemoTotpCodeButton from '@/account/DemoTotpCodeButton.jsx';\nimport ChangePasswordForm from '@/forms/auth/ChangePasswordForm.jsx';\nimport TwoStepVerificationCard from '@/forms/security/TwoStepVerificationCard.jsx';\nimport ActivityLog from '@/security/ActivityLog.jsx';\nimport {createPasskey, passkeysSupported} from '@/security/passkeys.js';\nimport SessionsCard from '@/security/SessionsCard.jsx';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaAccountSecurity)\nconst THEME_NAME = 'CalcitaAccountSecurity';\nconst SecurityTitle = piecePart(THEME_NAME, 'Title', Typography);\nconst SecurityForm = piecePart(THEME_NAME, 'Form', Stack);\n\n// lo ancho del formulario de la contraseña: un renglón cómodo\nconst FORM_MAX_WIDTH = 520;\nconst LOADING_HEIGHT_PIXELS = 220;\n\n/**\n * @param {object} props\n * @param {boolean} [props.showPasswordChange] por omisión sí\n * @param {object} [props.texts] los textos que cambian, con la forma de `account` en los textos del paquete\n */\nexport default function AccountSecuritySection(props) {\n // lo que el proyecto puso en su tema (components.CalcitaAccountSecurity.defaultProps) y encima lo que llega aquí\n const {showPasswordChange = true, texts} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('account', texts);\n const {connector, session} = useAccount();\n const [security, setSecurity] = useState(null);\n const [sessions, setSessions] = useState(null);\n const [events, setEvents] = useState(null);\n const [loadError, setLoadError] = useState(null);\n const isDemo = typeof connector.demoTotpCode === 'function';\n\n const refreshActivity = useCallback(() => connector.listActivity().then(setEvents).catch((error) => setLoadError(error.message)), [connector]);\n const refreshSessions = useCallback(() => connector.listSessions().then(setSessions).catch((error) => setLoadError(error.message)), [connector]);\n\n useEffect(() => {\n connector.loadSecurity().then(setSecurity).catch((error) => setLoadError(error.message));\n refreshSessions();\n refreshActivity();\n }, [connector, refreshActivity, refreshSessions]);\n\n // lo que hace la tarjeta de dos pasos en cada caso, con el conector\n const actions = useMemo(() => ({\n addPasskey: async (name) => connector.savePasskey({name, credential: await createPasskey(await connector.passkeyRegistrationOptions())}),\n removePasskey: (passkey, password) => connector.removePasskey({passkeyId: passkey.id, password}),\n startTotp: async () => ({\n ...await connector.startTotp(),\n // en la demostración, el atajo del código abajo del campo\n extra: isDemo ? <DemoTotpCodeButton loadCode={connector.demoTotpCode} /> : undefined,\n }),\n confirmTotp: (code) => connector.confirmTotp(code),\n disableTotp: (password) => connector.disableTotp(password),\n regenerateRecoveryCodes: (password) => connector.regenerateRecoveryCodes(password),\n }), [connector, isDemo]);\n\n const changeSecurity = (nextSecurity) => {\n setSecurity(nextSecurity);\n refreshActivity();\n };\n\n return (\n <Stack spacing={3}>\n {loadError && <Alert severity=\"error\">{loadError}</Alert>}\n {showPasswordChange && (\n <SectionCard title={t('profile.password.title')}>\n <SecurityTitle variant=\"body2\" color=\"text.secondary\" ownSx={{mb: 2}}>{t('profile.password.text')}</SecurityTitle>\n <SecurityForm ownSx={{maxWidth: FORM_MAX_WIDTH}}>\n <ChangePasswordForm onSubmit={async (values) => {\n await connector.changePassword(values);\n refreshActivity();\n }} />\n </SecurityForm>\n </SectionCard>\n )}\n {security\n ? <TwoStepVerificationCard security={security} onSecurityChange={changeSecurity} actions={actions}\n canUsePasskeys={passkeysSupported()} account={session?.user.email}\n // en la demostración la llave es de verdad pero el servidor se olvida al recargar:\n // se avisa antes de crearla para que nadie se quede con una llave huérfana\n passkeyNotice={isDemo ? t('demo.passkeyNote') : undefined} />\n : <Skeleton variant=\"rounded\" height={LOADING_HEIGHT_PIXELS} />}\n {isDemo && <Alert severity=\"info\">{t('demo.securityNote')}</Alert>}\n <SessionsCard sessions={sessions} onCloseOthers={connector.closeOtherSessions} onClosed={() => {\n refreshSessions();\n refreshActivity();\n }} />\n <ActivityLog events={events} />\n </Stack>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -6,7 +6,12 @@ import Stack from "@mui/material/Stack";
6
6
  import Typography from "@mui/material/Typography";
7
7
  import ScienceOutlinedIcon from "@mui/icons-material/ScienceOutlined";
8
8
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
9
- function DemoTotpCodeButton({ loadCode }) {
9
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
10
+ const THEME_NAME = "CalcitaDemoTotpCode";
11
+ const TotpRoot = piecePart(THEME_NAME, "Root", Stack);
12
+ const TotpCode = piecePart(THEME_NAME, "Code", Typography);
13
+ function DemoTotpCodeButton(props) {
14
+ const { loadCode } = usePieceProps(props, THEME_NAME);
10
15
  const { t } = useToolkitTranslation("account");
11
16
  const { setFieldValue } = useFormikContext();
12
17
  const fillCode = async () => {
@@ -15,9 +20,9 @@ function DemoTotpCodeButton({ loadCode }) {
15
20
  setFieldValue("code", code);
16
21
  }
17
22
  };
18
- return /* @__PURE__ */ jsxs(Stack, { spacing: 0.5, sx: { alignItems: "center" }, children: [
23
+ return /* @__PURE__ */ jsxs(TotpRoot, { spacing: 0.5, ownSx: { alignItems: "center" }, children: [
19
24
  /* @__PURE__ */ jsx(Button, { size: "small", startIcon: /* @__PURE__ */ jsx(ScienceOutlinedIcon, {}), onClick: fillCode, children: t("demo.totpCode") }),
20
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { textAlign: "center" }, children: t("demo.totpCodeHelp") })
25
+ /* @__PURE__ */ jsx(TotpCode, { variant: "caption", color: "text.secondary", ownSx: { textAlign: "center" }, children: t("demo.totpCodeHelp") })
21
26
  ] });
22
27
  }
23
28
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"DemoTotpCodeButton.js","sources":["../../src/account/DemoTotpCodeButton.jsx"],"sourcesContent":["// El atajo de la DEMOSTRACIÓN para la app autenticadora: llena el campo «code» del formulario con el código que enseñaría\n// la app ahorita. Para quien prueba sin la app en su teléfono (y para las pruebas automáticas, que no escanean QR). Solo\n// sale cuando el conector trae demoTotpCode: con un backend de verdad no existe (el código solo lo conoce la app).\n//\n// Va dentro del formulario de formik (abajo del segundo paso o en el diálogo de activar la app).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikContext} from 'formik';\nimport Button from '@mui/material/Button';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport ScienceOutlinedIcon from '@mui/icons-material/ScienceOutlined';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n/**\n * @param {{loadCode: () => Promise<string|null>}} props\n */\nexport default function DemoTotpCodeButton({loadCode}) {\n const {t} = useToolkitTranslation('account');\n const {setFieldValue} = useFormikContext();\n\n const fillCode = async () => {\n const code = await loadCode();\n if (code) {\n setFieldValue('code', code);\n }\n };\n\n return (\n <Stack spacing={0.5} sx={{alignItems: 'center'}}>\n <Button size=\"small\" startIcon={<ScienceOutlinedIcon />} onClick={fillCode}>{t('demo.totpCode')}</Button>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{textAlign: 'center'}}>{t('demo.totpCodeHelp')}</Typography>\n </Stack>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAkBA,SAAwB,mBAAmB,EAAC,YAAW;AACnD,QAAM,EAAC,EAAA,IAAK,sBAAsB,SAAS;AAC3C,QAAM,EAAC,cAAA,IAAiB,iBAAA;AAExB,QAAM,WAAW,YAAY;AACzB,UAAM,OAAO,MAAM,SAAA;AACnB,QAAI,MAAM;AACN,oBAAc,QAAQ,IAAI;AAAA,IAC9B;AAAA,EACJ;AAEA,SACI,qBAAC,SAAM,SAAS,KAAK,IAAI,EAAC,YAAY,YAClC,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAO,MAAK,SAAQ,WAAW,oBAAC,qBAAA,CAAA,CAAoB,GAAI,SAAS,UAAW,UAAA,EAAE,eAAe,GAAE;AAAA,IAChG,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAiB,IAAI,EAAC,WAAW,SAAA,GAAY,UAAA,EAAE,mBAAmB,EAAA,CAAE;AAAA,EAAA,GAC5G;AAER;"}
1
+ {"version":3,"file":"DemoTotpCodeButton.js","sources":["../../src/account/DemoTotpCodeButton.jsx"],"sourcesContent":["// El atajo de la DEMOSTRACIÓN para la app autenticadora: llena el campo «code» del formulario con el código que enseñaría\n// la app ahorita. Para quien prueba sin la app en su teléfono (y para las pruebas automáticas, que no escanean QR). Solo\n// sale cuando el conector trae demoTotpCode: con un backend de verdad no existe (el código solo lo conoce la app).\n//\n// Va dentro del formulario de formik (abajo del segundo paso o en el diálogo de activar la app).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaDemoTotpCode con defaultProps y\n// styleOverrides de sus partes: root, code.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormikContext} from 'formik';\nimport Button from '@mui/material/Button';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport ScienceOutlinedIcon from '@mui/icons-material/ScienceOutlined';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaDemoTotpCode)\nconst THEME_NAME = 'CalcitaDemoTotpCode';\nconst TotpRoot = piecePart(THEME_NAME, 'Root', Stack);\nconst TotpCode = piecePart(THEME_NAME, 'Code', Typography);\n\n/**\n * @param {{loadCode: () => Promise<string|null>}} props\n */\nexport default function DemoTotpCodeButton(props) {\n // lo que el proyecto puso en su tema (components.CalcitaDemoTotpCode.defaultProps) y encima lo que llega aquí\n const {loadCode} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('account');\n const {setFieldValue} = useFormikContext();\n\n const fillCode = async () => {\n const code = await loadCode();\n if (code) {\n setFieldValue('code', code);\n }\n };\n\n return (\n <TotpRoot spacing={0.5} ownSx={{alignItems: 'center'}}>\n <Button size=\"small\" startIcon={<ScienceOutlinedIcon />} onClick={fillCode}>{t('demo.totpCode')}</Button>\n <TotpCode variant=\"caption\" color=\"text.secondary\" ownSx={{textAlign: 'center'}}>{t('demo.totpCodeHelp')}</TotpCode>\n </TotpRoot>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAoBA,MAAM,aAAa;AACnB,MAAM,WAAW,UAAU,YAAY,QAAQ,KAAK;AACpD,MAAM,WAAW,UAAU,YAAY,QAAQ,UAAU;AAKzD,SAAwB,mBAAmB,OAAO;AAE9C,QAAM,EAAC,SAAA,IAAY,cAAc,OAAO,UAAU;AAClD,QAAM,EAAC,EAAA,IAAK,sBAAsB,SAAS;AAC3C,QAAM,EAAC,cAAA,IAAiB,iBAAA;AAExB,QAAM,WAAW,YAAY;AACzB,UAAM,OAAO,MAAM,SAAA;AACnB,QAAI,MAAM;AACN,oBAAc,QAAQ,IAAI;AAAA,IAC9B;AAAA,EACJ;AAEA,SACI,qBAAC,YAAS,SAAS,KAAK,OAAO,EAAC,YAAY,YACxC,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAO,MAAK,SAAQ,WAAW,oBAAC,qBAAA,CAAA,CAAoB,GAAI,SAAS,UAAW,UAAA,EAAE,eAAe,GAAE;AAAA,IAChG,oBAAC,UAAA,EAAS,SAAQ,WAAU,OAAM,kBAAiB,OAAO,EAAC,WAAW,SAAA,GAAY,UAAA,EAAE,mBAAmB,EAAA,CAAE;AAAA,EAAA,GAC7G;AAER;"}
@@ -20,6 +20,11 @@ import { useToolkitNavigation } from "../toolkit/toolkitContext.js";
20
20
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
21
21
  import ProfileHeader from "../ui/layout/ProfileHeader.js";
22
22
  import SectionCard from "../ui/layout/SectionCard.js";
23
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
24
+ const THEME_NAME = "CalcitaProfileScreen";
25
+ const ProfileScreenSectionTitle = piecePart(THEME_NAME, "SectionTitle", Typography);
26
+ const ProfileScreenForm = piecePart(THEME_NAME, "Form", Stack);
27
+ const ProfileScreenPreferences = piecePart(THEME_NAME, "Preferences", Stack);
23
28
  const PROFILE_TAB = Object.freeze({ data: "data", security: "security", preferences: "preferences" });
24
29
  const TAB_PARAM = "tab";
25
30
  const PHONE_PATTERN = /^\+?\d{7,15}$/;
@@ -46,8 +51,8 @@ function ProfileDataSection({ extraFields, onProfileSaved, texts }) {
46
51
  }
47
52
  };
48
53
  return /* @__PURE__ */ jsxs(SectionCard, { title: t("profile.data.title"), children: [
49
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: t("profile.data.text") }),
50
- /* @__PURE__ */ jsx(Formik, { initialValues, validationSchema: schema, onSubmit: save, children: /* @__PURE__ */ jsx(Form, { noValidate: true, children: /* @__PURE__ */ jsxs(Stack, { spacing: 2, sx: { maxWidth: FORM_MAX_WIDTH }, children: [
54
+ /* @__PURE__ */ jsx(ProfileScreenSectionTitle, { variant: "body2", color: "text.secondary", ownSx: { mb: 2 }, children: t("profile.data.text") }),
55
+ /* @__PURE__ */ jsx(Formik, { initialValues, validationSchema: schema, onSubmit: save, children: /* @__PURE__ */ jsx(Form, { noValidate: true, children: /* @__PURE__ */ jsxs(ProfileScreenForm, { spacing: 2, ownSx: { maxWidth: FORM_MAX_WIDTH }, children: [
51
56
  /* @__PURE__ */ jsx(FormProblemAlert, {}),
52
57
  /* @__PURE__ */ jsx(FormTextField, { name: "name", label: t("profile.data.name"), autoComplete: "name" }),
53
58
  /* @__PURE__ */ jsx(
@@ -68,15 +73,16 @@ function ProfileDataSection({ extraFields, onProfileSaved, texts }) {
68
73
  ] }) }) })
69
74
  ] });
70
75
  }
71
- function ProfileScreen({
72
- showColorScheme = true,
73
- extraFields,
74
- extraTabs = [],
75
- hiddenTabs = [],
76
- onTabChange,
77
- onProfileSaved,
78
- texts
79
- }) {
76
+ function ProfileScreen(props) {
77
+ const {
78
+ showColorScheme = true,
79
+ extraFields,
80
+ extraTabs = [],
81
+ hiddenTabs = [],
82
+ onTabChange,
83
+ onProfileSaved,
84
+ texts
85
+ } = usePieceProps(props, THEME_NAME);
80
86
  const { t } = useToolkitTranslation("account", texts);
81
87
  const { session } = useAccount();
82
88
  const { useNavigate, useLocation } = useToolkitNavigation();
@@ -121,8 +127,8 @@ function ProfileScreen({
121
127
  activeTab === PROFILE_TAB.data && /* @__PURE__ */ jsx(ProfileDataSection, { extraFields, onProfileSaved, texts }),
122
128
  activeTab === PROFILE_TAB.security && /* @__PURE__ */ jsx(AccountSecuritySection, {}),
123
129
  activeTab === PROFILE_TAB.preferences && /* @__PURE__ */ jsxs(SectionCard, { title: t("profile.preferences.title"), children: [
124
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: t("profile.preferences.text") }),
125
- /* @__PURE__ */ jsx(Stack, { sx: { maxWidth: FORM_MAX_WIDTH, mx: -1 }, children: /* @__PURE__ */ jsx(AppearanceSettings, { showColorScheme }) })
130
+ /* @__PURE__ */ jsx(ProfileScreenSectionTitle, { variant: "body2", color: "text.secondary", ownSx: { mb: 1 }, children: t("profile.preferences.text") }),
131
+ /* @__PURE__ */ jsx(ProfileScreenPreferences, { ownSx: { maxWidth: FORM_MAX_WIDTH, mx: -1 }, children: /* @__PURE__ */ jsx(AppearanceSettings, { showColorScheme }) })
126
132
  ] }),
127
133
  activeExtraTab?.content
128
134
  ] });
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileScreen.js","sources":["../../src/account/ProfileScreen.jsx"],"sourcesContent":["// Mi perfil, completo: quién está dentro (ProfileHeader con sus perfiles como chips) y tres pestañas:\n// - Sus datos: nombre y teléfono (el correo se ve pero no se cambia aquí); al guardar se vuelve a leer la sesión, así la\n// barra y el menú de la cuenta ya dicen el nombre nuevo;\n// - Seguridad: AccountSecuritySection (contraseña, dos pasos, sesiones y bitácora);\n// - Preferencias: AppearanceSettings (tamaño de letra, idioma y modo de color).\n// La pestaña abierta va en la dirección (?tab=): se puede compartir o recargar sin perderla. Lo del servidor sale del\n// conector de la cuenta.\n//\n// Se extiende sin tocar el framework (cada proyecto tiene su perfil «especial»):\n// - extraFields: campos propios en «Sus datos» (el área, el número de empleado…), con su valor de inicio sacado del usuario\n// de la sesión, sus reglas de yup y sus campos. Al guardar viajan a connector.updateProfile junto con name y phone: un\n// backend que ya existe recibe lo suyo con sus nombres;\n// - extraTabs: pestañas propias después de las de la casa (facturación, notificaciones…); hiddenTabs esconde las de la casa;\n// - eventos: onTabChange y onProfileSaved (lo guardado y lo que contestó el servidor);\n// - texts: cualquier texto, con la forma de `account` en los textos del paquete.\n//\n// <ProfileScreen />\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useMemo} from 'react';\nimport {Form, Formik} from 'formik';\nimport Stack from '@mui/material/Stack';\nimport Tab from '@mui/material/Tab';\nimport Tabs from '@mui/material/Tabs';\nimport Typography from '@mui/material/Typography';\n\nimport AccountSecuritySection from '@/account/AccountSecuritySection.jsx';\nimport {useAccount} from '@/account/AccountProvider.jsx';\nimport AppearanceSettings from '@/appShell/AppearanceSettings.jsx';\nimport {toastSuccess} from '@/feedback/toast.jsx';\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport TextInput from '@/inputs/TextInput.jsx';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport ProfileHeader from '@/ui/layout/ProfileHeader.jsx';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\n\n/** Las pestañas, en el orden en que se pintan. */\nexport const PROFILE_TAB = Object.freeze({data: 'data', security: 'security', preferences: 'preferences'});\nconst TAB_PARAM = 'tab';\n// el teléfono: dígitos, con + opcional al inicio (formato internacional); lo largo que permite el plan de numeración\nconst PHONE_PATTERN = /^\\+?\\d{7,15}$/;\n// lo ancho de los formularios: un renglón cómodo, no toda la pantalla\nconst FORM_MAX_WIDTH = 520;\n\n/**\n * Sus datos: nombre, teléfono y los campos propios del proyecto (extraFields).\n *\n * @param {object} props\n * @param {object} [props.extraFields]\n * @param {Function} [props.onProfileSaved]\n * @param {object} [props.texts]\n */\nfunction ProfileDataSection({extraFields, onProfileSaved, texts}) {\n const {t} = useToolkitTranslation('account', texts);\n const {connector, session, refreshSession} = useAccount();\n const extraRules = extraFields?.rules;\n // el esquema se arma al pintar: yup toma sus mensajes al armarlo. Las reglas propias se suman a las de la casa\n const schema = useMemo(() => Yup.object({\n name: Yup.string().trim().required(),\n phone: Yup.string().trim().matches(PHONE_PATTERN, {message: {key: 'account:profile.data.phoneInvalid'}, excludeEmptyString: true}),\n ...extraRules,\n }), [extraRules]);\n const initialValues = {name: session.user.name ?? '', phone: session.user.phone ?? '', ...extraFields?.initialValues?.(session.user)};\n\n const save = async (values, formikHelpers) => {\n // lo de la casa limpio de espacios; lo propio, tal como lo dejaron sus campos\n const profile = {...values, name: values.name.trim(), phone: values.phone.trim()};\n try {\n const response = await connector.updateProfile(profile);\n await refreshSession();\n toastSuccess(response?.message ?? t('profile.data.saved'));\n onProfileSaved?.({values: profile, response});\n } catch (error) {\n applyServerProblem(error, {...formikHelpers, values});\n }\n };\n\n return (\n <SectionCard title={t('profile.data.title')}>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{mb: 2}}>{t('profile.data.text')}</Typography>\n <Formik initialValues={initialValues} validationSchema={schema} onSubmit={save}>\n <Form noValidate>\n <Stack spacing={2} sx={{maxWidth: FORM_MAX_WIDTH}}>\n <FormProblemAlert />\n <FormTextField name=\"name\" label={t('profile.data.name')} autoComplete=\"name\" />\n {/* el correo se ve con el mismo estilo de los demás campos, pero no se cambia aquí */}\n <TextInput name=\"email\" label={t('profile.data.email')} value={session.user.email} onChange={() => {}}\n helperText={t('profile.data.emailHelp')} disabled />\n <FormTextField name=\"phone\" label={t('profile.data.phone')} type=\"tel\" autoComplete=\"tel\" inputMode=\"tel\" />\n {extraFields?.fields}\n <SubmitButton sx={{alignSelf: 'flex-start'}}>{t('profile.data.save')}</SubmitButton>\n </Stack>\n </Form>\n </Formik>\n </SectionCard>\n );\n}\n\n/** @typedef {import('@/account/accountConnector.js').AccountUser} AccountUser */\n\n/**\n * @param {object} props\n * @param {boolean} [props.showColorScheme] el ajuste de claro u oscuro en Preferencias; sin modo oscuro, false\n * @param {{initialValues?: (user: AccountUser & Record<string, any>) => Record<string, any>, rules?: Record<string, any>, fields: import('react').ReactNode}} [props.extraFields]\n * campos propios en «Sus datos», debajo del teléfono: sus valores de inicio salen del usuario de la sesión (lo que su\n * conector ponga en session.user), sus reglas de yup y sus campos (FormTextField, FormSelectField… de /forms). Al guardar\n * viajan a connector.updateProfile junto con name y phone\n * @param {Array<{key: string, label: string, content: import('react').ReactNode}>} [props.extraTabs] pestañas propias,\n * después de las de la casa; label ya traducido\n * @param {Array<'data'|'security'|'preferences'>} [props.hiddenTabs] pestañas de la casa que no se ven (PROFILE_TAB)\n * @param {(tab: string) => void} [props.onTabChange] cada que se cambia de pestaña\n * @param {(saved: {values: Record<string, any>, response: unknown}) => void} [props.onProfileSaved] después de guardar «Sus datos»\n * @param {object} [props.texts] los textos que cambian, con la forma de `account` en los textos del paquete:\n * {profile: {tabs: {data: 'Mis datos'}}}\n */\nexport default function ProfileScreen({\n showColorScheme = true, extraFields, extraTabs = [], hiddenTabs = [], onTabChange, onProfileSaved, texts,\n}) {\n const {t} = useToolkitTranslation('account', texts);\n const {session} = useAccount();\n const {useNavigate, useLocation} = useToolkitNavigation();\n const navigate = useNavigate();\n const {pathname, search} = useLocation();\n // las pestañas que se ven: las de la casa que no se escondieron y luego las del proyecto\n const tabs = [\n ...Object.values(PROFILE_TAB).filter((tab) => !hiddenTabs.includes(tab)).map((tab) => ({key: tab, label: t(`profile.tabs.${tab}`)})),\n ...extraTabs.map(({key, label}) => ({key, label})),\n ];\n const requestedTab = new URLSearchParams(search).get(TAB_PARAM);\n const activeTab = tabs.some((tab) => tab.key === requestedTab) ? requestedTab : tabs[0]?.key;\n const activeExtraTab = extraTabs.find((tab) => tab.key === activeTab);\n const changeTab = (tab) => {\n navigate(`${pathname}?${TAB_PARAM}=${tab}`, {replace: true});\n onTabChange?.(tab);\n };\n\n // guard clause: sin sesión no hay perfil que enseñar (el marco de la app ya lleva a entrar)\n if (!session) {\n return null;\n }\n\n return (\n <Stack spacing={2}>\n <ProfileHeader name={session.user.name} email={session.user.email} subtitle={session.user.subtitle} avatarUrl={session.user.avatarUrl}\n chips={session.labels ?? []} />\n <Tabs value={activeTab} onChange={(event, tab) => changeTab(tab)} aria-label={t('profile.tabs.label')}\n variant=\"scrollable\" allowScrollButtonsMobile>\n {tabs.map((tab) => <Tab key={tab.key} value={tab.key} label={tab.label} />)}\n </Tabs>\n {activeTab === PROFILE_TAB.data && <ProfileDataSection extraFields={extraFields} onProfileSaved={onProfileSaved} texts={texts} />}\n {activeTab === PROFILE_TAB.security && <AccountSecuritySection />}\n {activeTab === PROFILE_TAB.preferences && (\n <SectionCard title={t('profile.preferences.title')}>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{mb: 1}}>{t('profile.preferences.text')}</Typography>\n <Stack sx={{maxWidth: FORM_MAX_WIDTH, mx: -1}}>\n <AppearanceSettings showColorScheme={showColorScheme} />\n </Stack>\n </SectionCard>\n )}\n {activeExtraTab?.content}\n </Stack>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ProfileScreen.js","sources":["../../src/account/ProfileScreen.jsx"],"sourcesContent":["// Mi perfil, completo: quién está dentro (ProfileHeader con sus perfiles como chips) y tres pestañas:\n// - Sus datos: nombre y teléfono (el correo se ve pero no se cambia aquí); al guardar se vuelve a leer la sesión, así la\n// barra y el menú de la cuenta ya dicen el nombre nuevo;\n// - Seguridad: AccountSecuritySection (contraseña, dos pasos, sesiones y bitácora);\n// - Preferencias: AppearanceSettings (tamaño de letra, idioma y modo de color).\n// La pestaña abierta va en la dirección (?tab=): se puede compartir o recargar sin perderla. Lo del servidor sale del\n// conector de la cuenta.\n//\n// Se extiende sin tocar el framework (cada proyecto tiene su perfil «especial»):\n// - extraFields: campos propios en «Sus datos» (el área, el número de empleado…), con su valor de inicio sacado del usuario\n// de la sesión, sus reglas de yup y sus campos. Al guardar viajan a connector.updateProfile junto con name y phone: un\n// backend que ya existe recibe lo suyo con sus nombres;\n// - extraTabs: pestañas propias después de las de la casa (facturación, notificaciones…); hiddenTabs esconde las de la casa;\n// - eventos: onTabChange y onProfileSaved (lo guardado y lo que contestó el servidor);\n// - texts: cualquier texto, con la forma de `account` en los textos del paquete.\n//\n// <ProfileScreen />\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaProfileScreen con defaultProps y\n// styleOverrides de sus partes: sectionTitle, form, preferences.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useMemo} from 'react';\nimport {Form, Formik} from 'formik';\nimport Stack from '@mui/material/Stack';\nimport Tab from '@mui/material/Tab';\nimport Tabs from '@mui/material/Tabs';\nimport Typography from '@mui/material/Typography';\n\nimport AccountSecuritySection from '@/account/AccountSecuritySection.jsx';\nimport {useAccount} from '@/account/AccountProvider.jsx';\nimport AppearanceSettings from '@/appShell/AppearanceSettings.jsx';\nimport {toastSuccess} from '@/feedback/toast.jsx';\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport TextInput from '@/inputs/TextInput.jsx';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport ProfileHeader from '@/ui/layout/ProfileHeader.jsx';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaProfileScreen)\nconst THEME_NAME = 'CalcitaProfileScreen';\nconst ProfileScreenSectionTitle = piecePart(THEME_NAME, 'SectionTitle', Typography);\nconst ProfileScreenForm = piecePart(THEME_NAME, 'Form', Stack);\nconst ProfileScreenPreferences = piecePart(THEME_NAME, 'Preferences', Stack);\n\n/** Las pestañas, en el orden en que se pintan. */\nexport const PROFILE_TAB = Object.freeze({data: 'data', security: 'security', preferences: 'preferences'});\nconst TAB_PARAM = 'tab';\n// el teléfono: dígitos, con + opcional al inicio (formato internacional); lo largo que permite el plan de numeración\nconst PHONE_PATTERN = /^\\+?\\d{7,15}$/;\n// lo ancho de los formularios: un renglón cómodo, no toda la pantalla\nconst FORM_MAX_WIDTH = 520;\n\n/**\n * Sus datos: nombre, teléfono y los campos propios del proyecto (extraFields).\n *\n * @param {object} props\n * @param {object} [props.extraFields]\n * @param {Function} [props.onProfileSaved]\n * @param {object} [props.texts]\n */\nfunction ProfileDataSection({extraFields, onProfileSaved, texts}) {\n const {t} = useToolkitTranslation('account', texts);\n const {connector, session, refreshSession} = useAccount();\n const extraRules = extraFields?.rules;\n // el esquema se arma al pintar: yup toma sus mensajes al armarlo. Las reglas propias se suman a las de la casa\n const schema = useMemo(() => Yup.object({\n name: Yup.string().trim().required(),\n phone: Yup.string().trim().matches(PHONE_PATTERN, {message: {key: 'account:profile.data.phoneInvalid'}, excludeEmptyString: true}),\n ...extraRules,\n }), [extraRules]);\n const initialValues = {name: session.user.name ?? '', phone: session.user.phone ?? '', ...extraFields?.initialValues?.(session.user)};\n\n const save = async (values, formikHelpers) => {\n // lo de la casa limpio de espacios; lo propio, tal como lo dejaron sus campos\n const profile = {...values, name: values.name.trim(), phone: values.phone.trim()};\n try {\n const response = await connector.updateProfile(profile);\n await refreshSession();\n toastSuccess(response?.message ?? t('profile.data.saved'));\n onProfileSaved?.({values: profile, response});\n } catch (error) {\n applyServerProblem(error, {...formikHelpers, values});\n }\n };\n\n return (\n <SectionCard title={t('profile.data.title')}>\n <ProfileScreenSectionTitle variant=\"body2\" color=\"text.secondary\" ownSx={{mb: 2}}>{t('profile.data.text')}</ProfileScreenSectionTitle>\n <Formik initialValues={initialValues} validationSchema={schema} onSubmit={save}>\n <Form noValidate>\n <ProfileScreenForm spacing={2} ownSx={{maxWidth: FORM_MAX_WIDTH}}>\n <FormProblemAlert />\n <FormTextField name=\"name\" label={t('profile.data.name')} autoComplete=\"name\" />\n {/* el correo se ve con el mismo estilo de los demás campos, pero no se cambia aquí */}\n <TextInput name=\"email\" label={t('profile.data.email')} value={session.user.email} onChange={() => {}}\n helperText={t('profile.data.emailHelp')} disabled />\n <FormTextField name=\"phone\" label={t('profile.data.phone')} type=\"tel\" autoComplete=\"tel\" inputMode=\"tel\" />\n {extraFields?.fields}\n <SubmitButton sx={{alignSelf: 'flex-start'}}>{t('profile.data.save')}</SubmitButton>\n </ProfileScreenForm>\n </Form>\n </Formik>\n </SectionCard>\n );\n}\n\n/** @typedef {import('@/account/accountConnector.js').AccountUser} AccountUser */\n\n/**\n * @param {object} props\n * @param {boolean} [props.showColorScheme] el ajuste de claro u oscuro en Preferencias; sin modo oscuro, false\n * @param {{initialValues?: (user: AccountUser & Record<string, any>) => Record<string, any>, rules?: Record<string, any>, fields: import('react').ReactNode}} [props.extraFields]\n * campos propios en «Sus datos», debajo del teléfono: sus valores de inicio salen del usuario de la sesión (lo que su\n * conector ponga en session.user), sus reglas de yup y sus campos (FormTextField, FormSelectField… de /forms). Al guardar\n * viajan a connector.updateProfile junto con name y phone\n * @param {Array<{key: string, label: string, content: import('react').ReactNode}>} [props.extraTabs] pestañas propias,\n * después de las de la casa; label ya traducido\n * @param {Array<'data'|'security'|'preferences'>} [props.hiddenTabs] pestañas de la casa que no se ven (PROFILE_TAB)\n * @param {(tab: string) => void} [props.onTabChange] cada que se cambia de pestaña\n * @param {(saved: {values: Record<string, any>, response: unknown}) => void} [props.onProfileSaved] después de guardar «Sus datos»\n * @param {object} [props.texts] los textos que cambian, con la forma de `account` en los textos del paquete:\n * {profile: {tabs: {data: 'Mis datos'}}}\n */\nexport default function ProfileScreen(props) {\n // lo que el proyecto puso en su tema (components.CalcitaProfileScreen.defaultProps) y encima lo que llega aquí\n const {\n showColorScheme = true, extraFields, extraTabs = [], hiddenTabs = [], onTabChange, onProfileSaved, texts,\n} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('account', texts);\n const {session} = useAccount();\n const {useNavigate, useLocation} = useToolkitNavigation();\n const navigate = useNavigate();\n const {pathname, search} = useLocation();\n // las pestañas que se ven: las de la casa que no se escondieron y luego las del proyecto\n const tabs = [\n ...Object.values(PROFILE_TAB).filter((tab) => !hiddenTabs.includes(tab)).map((tab) => ({key: tab, label: t(`profile.tabs.${tab}`)})),\n ...extraTabs.map(({key, label}) => ({key, label})),\n ];\n const requestedTab = new URLSearchParams(search).get(TAB_PARAM);\n const activeTab = tabs.some((tab) => tab.key === requestedTab) ? requestedTab : tabs[0]?.key;\n const activeExtraTab = extraTabs.find((tab) => tab.key === activeTab);\n const changeTab = (tab) => {\n navigate(`${pathname}?${TAB_PARAM}=${tab}`, {replace: true});\n onTabChange?.(tab);\n };\n\n // guard clause: sin sesión no hay perfil que enseñar (el marco de la app ya lleva a entrar)\n if (!session) {\n return null;\n }\n\n return (\n <Stack spacing={2}>\n <ProfileHeader name={session.user.name} email={session.user.email} subtitle={session.user.subtitle} avatarUrl={session.user.avatarUrl}\n chips={session.labels ?? []} />\n <Tabs value={activeTab} onChange={(event, tab) => changeTab(tab)} aria-label={t('profile.tabs.label')}\n variant=\"scrollable\" allowScrollButtonsMobile>\n {tabs.map((tab) => <Tab key={tab.key} value={tab.key} label={tab.label} />)}\n </Tabs>\n {activeTab === PROFILE_TAB.data && <ProfileDataSection extraFields={extraFields} onProfileSaved={onProfileSaved} texts={texts} />}\n {activeTab === PROFILE_TAB.security && <AccountSecuritySection />}\n {activeTab === PROFILE_TAB.preferences && (\n <SectionCard title={t('profile.preferences.title')}>\n <ProfileScreenSectionTitle variant=\"body2\" color=\"text.secondary\" ownSx={{mb: 1}}>{t('profile.preferences.text')}</ProfileScreenSectionTitle>\n <ProfileScreenPreferences ownSx={{maxWidth: FORM_MAX_WIDTH, mx: -1}}>\n <AppearanceSettings showColorScheme={showColorScheme} />\n </ProfileScreenPreferences>\n </SectionCard>\n )}\n {activeExtraTab?.content}\n </Stack>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -12,7 +12,11 @@ import { signWithPasskey, passkeysSupported } from "../security/passkeys.js";
12
12
  import { useToolkitNavigation } from "../toolkit/toolkitContext.js";
13
13
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
14
14
  import ComponentTexts from "../toolkit/ComponentTexts.js";
15
- function SignInScreen({ homePath = "/", forgotPasswordPath, logo, brandName, footer, showLanguageToggle = true, showColorSchemeToggle = true, texts }) {
15
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
16
+ const THEME_NAME = "CalcitaSignInScreen";
17
+ const SignInAlert = piecePart(THEME_NAME, "Alert", Alert);
18
+ function SignInScreen(props) {
19
+ const { homePath = "/", forgotPasswordPath, logo, brandName, footer, showLanguageToggle = true, showColorSchemeToggle = true, texts } = usePieceProps(props, THEME_NAME);
16
20
  const { t } = useToolkitTranslation("auth", texts);
17
21
  const { connector, session, refreshSession } = useAccount();
18
22
  const { useNavigate, useLocation } = useToolkitNavigation();
@@ -90,7 +94,7 @@ function SignInScreen({ homePath = "/", forgotPasswordPath, logo, brandName, foo
90
94
  showColorSchemeToggle,
91
95
  footer: typeof footer === "function" ? footer({ signIn: signInFromFooter }) : footer,
92
96
  children: [
93
- notice && /* @__PURE__ */ jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: notice }),
97
+ notice && /* @__PURE__ */ jsx(SignInAlert, { severity: "warning", ownSx: { mb: 2 }, children: notice }),
94
98
  /* @__PURE__ */ jsx(SignInForm, { onSubmit: signIn, forgotPasswordPath })
95
99
  ]
96
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SignInScreen.js","sources":["../../src/account/SignInScreen.jsx"],"sourcesContent":["// Entrar, completo: la contraseña (SignInForm) y, si la cuenta tiene verificación en dos pasos, el segundo paso\n// (SecondStepForm: la llave de acceso, el código de la app o uno de recuperación). Al terminar se vuelve a leer la sesión y\n// se va a la pantalla que se quería abrir (readRedirectParam: solo si es de esta app) o al inicio.\n//\n// Lo del servidor sale del conector de la cuenta (useAccount): en el proyecto la pantalla solo se pone en su ruta.\n//\n// <SignInScreen forgotPasswordPath=\"/forgot-password\" logo={<AppLogo />} />\n//\n// footer va abajo de la tarjeta. Si es una función, recibe signIn para entrar desde un botón propio (p. ej. «Entrar con la\n// cuenta de ejemplo»), con el mismo segundo paso; si falla, el motivo sale arriba del formulario.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useState} from 'react';\nimport Alert from '@mui/material/Alert';\n\nimport {useAccount} from '@/account/AccountProvider.jsx';\nimport DemoTotpCodeButton from '@/account/DemoTotpCodeButton.jsx';\nimport {AuthLayout} from '@/appShell/AuthLayout.jsx';\nimport SignInForm from '@/forms/auth/SignInForm.jsx';\nimport SecondStepForm, {SECOND_STEP_METHOD} from '@/forms/security/SecondStepForm.jsx';\nimport {readRedirectParam} from '@/navigation.js';\nimport {passkeysSupported, signWithPasskey} from '@/security/passkeys.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\n\n/**\n * @param {object} props\n * @param {string} [props.homePath] a dónde ir si no se quería abrir otra pantalla; por omisión '/'\n * @param {string} [props.forgotPasswordPath] sin ella no sale «¿Olvidó su contraseña?»\n * @param {import('react').ReactNode} [props.logo]\n * @param {string} [props.brandName] por omisión el appName de ToolkitProvider\n * @param {import('react').ReactNode | ((helpers: {signIn: (credentials: {email: string, password: string, remember?: boolean}) => Promise<void>}) => import('react').ReactNode)} [props.footer]\n * @param {boolean} [props.showLanguageToggle] por omisión sí\n * @param {boolean} [props.showColorSchemeToggle] por omisión sí\n * @param {object} [props.texts] los textos que cambian, con la forma de `auth` en los textos del paquete\n */\nexport default function SignInScreen({homePath = '/', forgotPasswordPath, logo, brandName, footer, showLanguageToggle = true, showColorSchemeToggle = true, texts}) {\n const {t} = useToolkitTranslation('auth', texts);\n const {connector, session, refreshSession} = useAccount();\n const {useNavigate, useLocation} = useToolkitNavigation();\n const navigate = useNavigate();\n const {search} = useLocation();\n // con verificación en dos pasos: los métodos del segundo paso (la contraseña ya fue correcta)\n const [secondStep, setSecondStep] = useState(null);\n const [notice, setNotice] = useState(null);\n const destination = readRedirectParam(search) ?? homePath;\n\n // ya hay sesión (acaba de entrar, o abrió esta pantalla estando dentro): a donde iba\n useEffect(() => {\n if (session) {\n navigate(destination, {replace: true});\n }\n }, [session, destination, navigate]);\n\n // después de la contraseña: el segundo paso si lo pide el servidor; si no, ya está dentro\n const signIn = async (credentials) => {\n setNotice(null);\n const result = await connector.signIn({remember: false, ...credentials});\n if (result?.secondStep) {\n setSecondStep(result.secondStep);\n return;\n }\n await refreshSession();\n };\n // desde un botón propio del footer: el error no tiene formulario donde salir, sale arriba\n const signInFromFooter = async (credentials) => {\n try {\n await signIn(credentials);\n } catch (error) {\n setNotice(error?.message ?? null);\n }\n };\n // se acabaron los intentos o se venció el tiempo: de regreso a la contraseña, con el motivo\n const backToPassword = (message) => {\n setSecondStep(null);\n setNotice(message);\n };\n\n if (session) {\n return null;\n }\n\n if (secondStep) {\n const showsDemoCode = typeof connector.demoTotpCode === 'function' && secondStep.methods.includes(SECOND_STEP_METHOD.totp);\n return (\n <ComponentTexts namespace=\"auth\" texts={texts}>\n <AuthLayout logo={logo} brandName={brandName} title={t('security:twoStep.title')}\n showLanguageToggle={showLanguageToggle} showColorSchemeToggle={showColorSchemeToggle}>\n <SecondStepForm methods={secondStep.methods} canUsePasskeys={passkeysSupported()}\n onUsePasskey={async () => {\n await connector.signInWithPasskey(await signWithPasskey(await connector.secondStepPasskeyOptions()));\n await refreshSession();\n }}\n onSubmitCode={async ({method, code}) => {\n await connector.signInSecondStep({method, code});\n await refreshSession();\n }}\n onCancel={backToPassword}\n footer={showsDemoCode && <DemoTotpCodeButton loadCode={connector.demoTotpCode} />} />\n </AuthLayout>\n </ComponentTexts>\n );\n }\n\n return (\n <ComponentTexts namespace=\"auth\" texts={texts}>\n <AuthLayout logo={logo} brandName={brandName} title={t('signIn.title')} description={t('signIn.subtitle')}\n showLanguageToggle={showLanguageToggle} showColorSchemeToggle={showColorSchemeToggle}\n footer={typeof footer === 'function' ? footer({signIn: signInFromFooter}) : footer}>\n {notice && <Alert severity=\"warning\" sx={{mb: 2}}>{notice}</Alert>}\n <SignInForm onSubmit={signIn} forgotPasswordPath={forgotPasswordPath} />\n </AuthLayout>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAqCA,SAAwB,aAAa,EAAC,WAAW,KAAK,oBAAoB,MAAM,WAAW,QAAQ,qBAAqB,MAAM,wBAAwB,MAAM,SAAQ;AAChK,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ,KAAK;AAC/C,QAAM,EAAC,WAAW,SAAS,eAAA,IAAkB,WAAA;AAC7C,QAAM,EAAC,aAAa,YAAA,IAAe,qBAAA;AACnC,QAAM,WAAW,YAAA;AACjB,QAAM,EAAC,OAAA,IAAU,YAAA;AAEjB,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,IAAI;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AACzC,QAAM,cAAc,kBAAkB,MAAM,KAAK;AAGjD,YAAU,MAAM;AACZ,QAAI,SAAS;AACT,eAAS,aAAa,EAAC,SAAS,KAAA,CAAK;AAAA,IACzC;AAAA,EACJ,GAAG,CAAC,SAAS,aAAa,QAAQ,CAAC;AAGnC,QAAM,SAAS,OAAO,gBAAgB;AAClC,cAAU,IAAI;AACd,UAAM,SAAS,MAAM,UAAU,OAAO,EAAC,UAAU,OAAO,GAAG,aAAY;AACvE,QAAI,QAAQ,YAAY;AACpB,oBAAc,OAAO,UAAU;AAC/B;AAAA,IACJ;AACA,UAAM,eAAA;AAAA,EACV;AAEA,QAAM,mBAAmB,OAAO,gBAAgB;AAC5C,QAAI;AACA,YAAM,OAAO,WAAW;AAAA,IAC5B,SAAS,OAAO;AACZ,gBAAU,OAAO,WAAW,IAAI;AAAA,IACpC;AAAA,EACJ;AAEA,QAAM,iBAAiB,CAAC,YAAY;AAChC,kBAAc,IAAI;AAClB,cAAU,OAAO;AAAA,EACrB;AAEA,MAAI,SAAS;AACT,WAAO;AAAA,EACX;AAEA,MAAI,YAAY;AACZ,UAAM,gBAAgB,OAAO,UAAU,iBAAiB,cAAc,WAAW,QAAQ,SAAS,mBAAmB,IAAI;AACzH,WACI,oBAAC,gBAAA,EAAe,WAAU,QAAO,OAC7B,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAW;AAAA,QAAY;AAAA,QAAsB,OAAO,EAAE,wBAAwB;AAAA,QACnE;AAAA,QAAwC;AAAA,QAChD,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAe,SAAS,WAAW;AAAA,YAAS,gBAAgB,kBAAA;AAAA,YAC7C,cAAc,YAAY;AACtB,oBAAM,UAAU,kBAAkB,MAAM,gBAAgB,MAAM,UAAU,yBAAA,CAA0B,CAAC;AACnG,oBAAM,eAAA;AAAA,YACV;AAAA,YACA,cAAc,OAAO,EAAC,QAAQ,WAAU;AACpC,oBAAM,UAAU,iBAAiB,EAAC,QAAQ,MAAK;AAC/C,oBAAM,eAAA;AAAA,YACV;AAAA,YACA,UAAU;AAAA,YACV,QAAQ,iBAAiB,oBAAC,oBAAA,EAAmB,UAAU,UAAU,aAAA,CAAc;AAAA,UAAA;AAAA,QAAA;AAAA,MAAI;AAAA,IAAA,GAE3G;AAAA,EAER;AAEA,SACI,oBAAC,gBAAA,EAAe,WAAU,QAAO,OAC7B,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MAAW;AAAA,MAAY;AAAA,MAAsB,OAAO,EAAE,cAAc;AAAA,MAAG,aAAa,EAAE,iBAAiB;AAAA,MAC5F;AAAA,MAAwC;AAAA,MACxC,QAAQ,OAAO,WAAW,aAAa,OAAO,EAAC,QAAQ,iBAAA,CAAiB,IAAI;AAAA,MACnF,UAAA;AAAA,QAAA,UAAU,oBAAC,SAAM,UAAS,WAAU,IAAI,EAAC,IAAI,EAAA,GAAK,UAAA,OAAA,CAAO;AAAA,QAC1D,oBAAC,YAAA,EAAW,UAAU,QAAQ,mBAAA,CAAwC;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA,GAE9E;AAER;"}
1
+ {"version":3,"file":"SignInScreen.js","sources":["../../src/account/SignInScreen.jsx"],"sourcesContent":["// Entrar, completo: la contraseña (SignInForm) y, si la cuenta tiene verificación en dos pasos, el segundo paso\n// (SecondStepForm: la llave de acceso, el código de la app o uno de recuperación). Al terminar se vuelve a leer la sesión y\n// se va a la pantalla que se quería abrir (readRedirectParam: solo si es de esta app) o al inicio.\n//\n// Lo del servidor sale del conector de la cuenta (useAccount): en el proyecto la pantalla solo se pone en su ruta.\n//\n// <SignInScreen forgotPasswordPath=\"/forgot-password\" logo={<AppLogo />} />\n//\n// footer va abajo de la tarjeta. Si es una función, recibe signIn para entrar desde un botón propio (p. ej. «Entrar con la\n// cuenta de ejemplo»), con el mismo segundo paso; si falla, el motivo sale arriba del formulario.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSignInScreen con defaultProps y\n// styleOverrides de sus partes: alert.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useState} from 'react';\nimport Alert from '@mui/material/Alert';\n\nimport {useAccount} from '@/account/AccountProvider.jsx';\nimport DemoTotpCodeButton from '@/account/DemoTotpCodeButton.jsx';\nimport {AuthLayout} from '@/appShell/AuthLayout.jsx';\nimport SignInForm from '@/forms/auth/SignInForm.jsx';\nimport SecondStepForm, {SECOND_STEP_METHOD} from '@/forms/security/SecondStepForm.jsx';\nimport {readRedirectParam} from '@/navigation.js';\nimport {passkeysSupported, signWithPasskey} from '@/security/passkeys.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSignInScreen)\nconst THEME_NAME = 'CalcitaSignInScreen';\nconst SignInAlert = piecePart(THEME_NAME, 'Alert', Alert);\n\n/**\n * @param {object} props\n * @param {string} [props.homePath] a dónde ir si no se quería abrir otra pantalla; por omisión '/'\n * @param {string} [props.forgotPasswordPath] sin ella no sale «¿Olvidó su contraseña?»\n * @param {import('react').ReactNode} [props.logo]\n * @param {string} [props.brandName] por omisión el appName de ToolkitProvider\n * @param {import('react').ReactNode | ((helpers: {signIn: (credentials: {email: string, password: string, remember?: boolean}) => Promise<void>}) => import('react').ReactNode)} [props.footer]\n * @param {boolean} [props.showLanguageToggle] por omisión sí\n * @param {boolean} [props.showColorSchemeToggle] por omisión sí\n * @param {object} [props.texts] los textos que cambian, con la forma de `auth` en los textos del paquete\n */\nexport default function SignInScreen(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSignInScreen.defaultProps) y encima lo que llega aquí\n const {homePath = '/', forgotPasswordPath, logo, brandName, footer, showLanguageToggle = true, showColorSchemeToggle = true, texts} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('auth', texts);\n const {connector, session, refreshSession} = useAccount();\n const {useNavigate, useLocation} = useToolkitNavigation();\n const navigate = useNavigate();\n const {search} = useLocation();\n // con verificación en dos pasos: los métodos del segundo paso (la contraseña ya fue correcta)\n const [secondStep, setSecondStep] = useState(null);\n const [notice, setNotice] = useState(null);\n const destination = readRedirectParam(search) ?? homePath;\n\n // ya hay sesión (acaba de entrar, o abrió esta pantalla estando dentro): a donde iba\n useEffect(() => {\n if (session) {\n navigate(destination, {replace: true});\n }\n }, [session, destination, navigate]);\n\n // después de la contraseña: el segundo paso si lo pide el servidor; si no, ya está dentro\n const signIn = async (credentials) => {\n setNotice(null);\n const result = await connector.signIn({remember: false, ...credentials});\n if (result?.secondStep) {\n setSecondStep(result.secondStep);\n return;\n }\n await refreshSession();\n };\n // desde un botón propio del footer: el error no tiene formulario donde salir, sale arriba\n const signInFromFooter = async (credentials) => {\n try {\n await signIn(credentials);\n } catch (error) {\n setNotice(error?.message ?? null);\n }\n };\n // se acabaron los intentos o se venció el tiempo: de regreso a la contraseña, con el motivo\n const backToPassword = (message) => {\n setSecondStep(null);\n setNotice(message);\n };\n\n if (session) {\n return null;\n }\n\n if (secondStep) {\n const showsDemoCode = typeof connector.demoTotpCode === 'function' && secondStep.methods.includes(SECOND_STEP_METHOD.totp);\n return (\n <ComponentTexts namespace=\"auth\" texts={texts}>\n <AuthLayout logo={logo} brandName={brandName} title={t('security:twoStep.title')}\n showLanguageToggle={showLanguageToggle} showColorSchemeToggle={showColorSchemeToggle}>\n <SecondStepForm methods={secondStep.methods} canUsePasskeys={passkeysSupported()}\n onUsePasskey={async () => {\n await connector.signInWithPasskey(await signWithPasskey(await connector.secondStepPasskeyOptions()));\n await refreshSession();\n }}\n onSubmitCode={async ({method, code}) => {\n await connector.signInSecondStep({method, code});\n await refreshSession();\n }}\n onCancel={backToPassword}\n footer={showsDemoCode && <DemoTotpCodeButton loadCode={connector.demoTotpCode} />} />\n </AuthLayout>\n </ComponentTexts>\n );\n }\n\n return (\n <ComponentTexts namespace=\"auth\" texts={texts}>\n <AuthLayout logo={logo} brandName={brandName} title={t('signIn.title')} description={t('signIn.subtitle')}\n showLanguageToggle={showLanguageToggle} showColorSchemeToggle={showColorSchemeToggle}\n footer={typeof footer === 'function' ? footer({signIn: signInFromFooter}) : footer}>\n {notice && <SignInAlert severity=\"warning\" ownSx={{mb: 2}}>{notice}</SignInAlert>}\n <SignInForm onSubmit={signIn} forgotPasswordPath={forgotPasswordPath} />\n </AuthLayout>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -19,7 +19,15 @@ import { barMenuPlacementOf } from "./barMenuPlacement.js";
19
19
  import MenuLinks from "./MenuLinks.js";
20
20
  import { WHITE_LABEL, MONOGRAM_GRAY } from "../theme/components/glassComponents.js";
21
21
  import { isGlassTheme } from "../theme/glassSurface.js";
22
- function AccountMenu({ user, profilePath, accountMenuItems = [], onSignOut, isSigningOut = false, settings }) {
22
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
23
+ const THEME_NAME = "CalcitaAccountMenu";
24
+ const AccountButton = piecePart(THEME_NAME, "Button", IconButton);
25
+ const AccountAvatar = piecePart(THEME_NAME, "Avatar", Avatar);
26
+ const AccountHeader = piecePart(THEME_NAME, "Header", Box);
27
+ const AccountName = piecePart(THEME_NAME, "Name", Typography);
28
+ const AccountSettings = piecePart(THEME_NAME, "Settings", Box);
29
+ function AccountMenu(props) {
30
+ const { user, profilePath, accountMenuItems = [], onSignOut, isSigningOut = false, settings } = usePieceProps(props, THEME_NAME);
23
31
  const { t } = useToolkitTranslation("layout");
24
32
  const [anchorElement, setAnchorElement] = useState(null);
25
33
  const closeMenu = () => setAnchorElement(null);
@@ -33,7 +41,7 @@ function AccountMenu({ user, profilePath, accountMenuItems = [], onSignOut, isSi
33
41
  onSignOut();
34
42
  };
35
43
  return /* @__PURE__ */ jsxs(Fragment, { children: [
36
- /* @__PURE__ */ jsx(Tooltip, { title: t("account.open"), children: /* @__PURE__ */ jsx(IconButton, { size: "small", "aria-label": t("account.open"), "aria-haspopup": "menu", onClick: (event) => setAnchorElement(event.currentTarget), sx: { p: 0.5 }, children: /* @__PURE__ */ jsx(Avatar, { src: user.avatarUrl, alt: "", sx: (theme) => ({
44
+ /* @__PURE__ */ jsx(Tooltip, { title: t("account.open"), children: /* @__PURE__ */ jsx(AccountButton, { size: "small", "aria-label": t("account.open"), "aria-haspopup": "menu", onClick: (event) => setAnchorElement(event.currentTarget), ownSx: { p: 0.5 }, children: /* @__PURE__ */ jsx(AccountAvatar, { src: user.avatarUrl, alt: "", ownSx: (theme) => ({
37
45
  width: 32,
38
46
  height: 32,
39
47
  fontSize: "0.875rem",
@@ -53,13 +61,13 @@ function AccountMenu({ user, profilePath, accountMenuItems = [], onSignOut, isSi
53
61
  transformOrigin: placement.transformOrigin,
54
62
  slotProps: { paper: { sx: { minWidth: settings ? 300 : 240, maxWidth: 360, ...placement.gapSx } } },
55
63
  children: [
56
- /* @__PURE__ */ jsxs(Box, { sx: { px: 2, py: 1.25 }, children: [
57
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", noWrap: true, sx: { fontWeight: 600 }, children: user.name }),
64
+ /* @__PURE__ */ jsxs(AccountHeader, { ownSx: { px: 2, py: 1.25 }, children: [
65
+ /* @__PURE__ */ jsx(AccountName, { variant: "subtitle2", noWrap: true, ownSx: { fontWeight: 600 }, children: user.name }),
58
66
  user.email && /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", noWrap: true, children: user.email }),
59
67
  user.subtitle && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", component: "p", noWrap: true, children: user.subtitle })
60
68
  ] }),
61
69
  settings && /* @__PURE__ */ jsx(Divider, {}),
62
- settings && /* @__PURE__ */ jsx(Box, { sx: { py: 0.5 }, children: settings }),
70
+ settings && /* @__PURE__ */ jsx(AccountSettings, { ownSx: { py: 0.5 }, children: settings }),
63
71
  menuLinks.length > 0 && /* @__PURE__ */ jsx(Divider, {}),
64
72
  /* @__PURE__ */ jsx(MenuLinks, { links: menuLinks, onClose: closeMenu }),
65
73
  onSignOut && /* @__PURE__ */ jsx(Divider, {}),
@@ -1 +1 @@
1
- {"version":3,"file":"AccountMenu.js","sources":["../../src/appShell/AccountMenu.jsx"],"sourcesContent":["// La cuenta de quien está dentro, arriba a la derecha (como en las plantillas de MUI): un avatar con sus iniciales (o su\n// foto) que abre un menú con su nombre y correo, Mi perfil, lo que el proyecto agregue (accountMenuItems: Facturación,\n// Mis llaves…) y Salir.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Avatar from '@mui/material/Avatar';\nimport Box from '@mui/material/Box';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport LogoutIcon from '@mui/icons-material/Logout';\nimport PersonOutlineIcon from '@mui/icons-material/PersonOutline';\n\nimport {initialsOf} from '@/appShell/menuItemText.js';\nimport {barMenuPlacementOf} from '@/appShell/barMenuPlacement.js';\nimport MenuLinks from '@/appShell/MenuLinks.jsx';\nimport {MONOGRAM_GRAY, WHITE_LABEL} from '@/theme/components/glassComponents.js';\nimport {isGlassTheme} from '@/theme/glassSurface.js';\n\n/**\n * @param {object} props\n * @param {{name: string, email?: string, subtitle?: string, avatarUrl?: string}} props.user\n * @param {string} [props.profilePath] sin ella no sale «Mi perfil»\n * @param {Array<{to?: string, onClick?: Function, label?: string, labelKey?: string, Icon?: import('react').ElementType}>} [props.accountMenuItems]\n * @param {() => void} [props.onSignOut] sin él no sale «Salir»\n * @param {boolean} [props.isSigningOut]\n * @param {import('react').ReactNode} [props.settings] los ajustes de la app, entre los datos de la persona y sus ligas\n */\nexport default function AccountMenu({user, profilePath, accountMenuItems = [], onSignOut, isSigningOut = false, settings}) {\n const {t} = useToolkitTranslation('layout');\n const [anchorElement, setAnchorElement] = useState(null);\n const closeMenu = () => setAnchorElement(null);\n const menuLinks = [\n ...(profilePath ? [{to: profilePath, labelKey: 'layout:account.myProfile', Icon: PersonOutlineIcon}] : []),\n ...accountMenuItems,\n ];\n\n // hacia arriba si la barra está abajo (teléfono con vidrio)\n const placement = barMenuPlacementOf(anchorElement);\n\n const signOut = () => {\n closeMenu();\n onSignOut();\n };\n\n return (\n <>\n <Tooltip title={t('account.open')}>\n <IconButton size=\"small\" aria-label={t('account.open')} aria-haspopup=\"menu\" onClick={(event) => setAnchorElement(event.currentTarget)} sx={{p: 0.5}}>\n <Avatar src={user.avatarUrl} alt=\"\" sx={(theme) => ({\n width: 32, height: 32, fontSize: '0.875rem', bgcolor: 'secondary.main', color: 'secondary.contrastText',\n // con vidrio, en oscuro el monograma de Apple (gris oscuro con letra blanca), el que se aprobó. Antes lo\n // ponía el tema porque su regla de oscuro le ganaba a este sx; ahora el sx gana en los dos modos\n ...(isGlassTheme(theme) && theme.applyStyles('dark', {bgcolor: MONOGRAM_GRAY.dark, color: WHITE_LABEL})),\n })}>\n {initialsOf(user.name)}\n </Avatar>\n </IconButton>\n </Tooltip>\n <Menu anchorEl={anchorElement} open={Boolean(anchorElement)} onClose={closeMenu}\n anchorOrigin={placement.anchorOrigin} transformOrigin={placement.transformOrigin}\n slotProps={{paper: {sx: {minWidth: settings ? 300 : 240, maxWidth: 360, ...placement.gapSx}}}}>\n <Box sx={{px: 2, py: 1.25}}>\n <Typography variant=\"subtitle2\" noWrap sx={{fontWeight: 600}}>{user.name}</Typography>\n {user.email && <Typography variant=\"body2\" color=\"text.secondary\" noWrap>{user.email}</Typography>}\n {user.subtitle && <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\" noWrap>{user.subtitle}</Typography>}\n </Box>\n {/* los ajustes de la app (letra, idioma, claro u oscuro y los del proyecto): en la cuenta y no sueltos en la barra */}\n {settings && <Divider />}\n {settings && <Box sx={{py: 0.5}}>{settings}</Box>}\n {menuLinks.length > 0 && <Divider />}\n <MenuLinks links={menuLinks} onClose={closeMenu} />\n {onSignOut && <Divider />}\n {onSignOut && (\n <MenuItem onClick={signOut} disabled={isSigningOut}>\n <ListItemIcon><LogoutIcon fontSize=\"small\" /></ListItemIcon>\n <ListItemText primary={t('signOut')} />\n </MenuItem>\n )}\n </Menu>\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAmCA,SAAwB,YAAY,EAAC,MAAM,aAAa,mBAAmB,IAAI,WAAW,eAAe,OAAO,YAAW;AACvH,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAC1C,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,IAAI;AACvD,QAAM,YAAY,MAAM,iBAAiB,IAAI;AAC7C,QAAM,YAAY;AAAA,IACd,GAAI,cAAc,CAAC,EAAC,IAAI,aAAa,UAAU,4BAA4B,MAAM,kBAAA,CAAkB,IAAI,CAAA;AAAA,IACvG,GAAG;AAAA,EAAA;AAIP,QAAM,YAAY,mBAAmB,aAAa;AAElD,QAAM,UAAU,MAAM;AAClB,cAAA;AACA,cAAA;AAAA,EACJ;AAEA,SACI,qBAAA,UAAA,EACI,UAAA;AAAA,IAAA,oBAAC,WAAQ,OAAO,EAAE,cAAc,GAC5B,8BAAC,YAAA,EAAW,MAAK,SAAQ,cAAY,EAAE,cAAc,GAAG,iBAAc,QAAO,SAAS,CAAC,UAAU,iBAAiB,MAAM,aAAa,GAAG,IAAI,EAAC,GAAG,OAC5I,UAAA,oBAAC,QAAA,EAAO,KAAK,KAAK,WAAW,KAAI,IAAG,IAAI,CAAC,WAAW;AAAA,MAChD,OAAO;AAAA,MAAI,QAAQ;AAAA,MAAI,UAAU;AAAA,MAAY,SAAS;AAAA,MAAkB,OAAO;AAAA;AAAA;AAAA,MAG/E,GAAI,aAAa,KAAK,KAAK,MAAM,YAAY,QAAQ,EAAC,SAAS,cAAc,MAAM,OAAO,aAAY;AAAA,IAAA,IAErG,UAAA,WAAW,KAAK,IAAI,EAAA,CACzB,GACJ,GACJ;AAAA,IACA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAK,UAAU;AAAA,QAAe,MAAM,QAAQ,aAAa;AAAA,QAAG,SAAS;AAAA,QAChE,cAAc,UAAU;AAAA,QAAc,iBAAiB,UAAU;AAAA,QACjE,WAAW,EAAC,OAAO,EAAC,IAAI,EAAC,UAAU,WAAW,MAAM,KAAK,UAAU,KAAK,GAAG,UAAU,MAAA,IAAM;AAAA,QAC7F,UAAA;AAAA,UAAA,qBAAC,OAAI,IAAI,EAAC,IAAI,GAAG,IAAI,QACjB,UAAA;AAAA,YAAA,oBAAC,YAAA,EAAW,SAAQ,aAAY,QAAM,MAAC,IAAI,EAAC,YAAY,IAAA,GAAO,UAAA,KAAK,KAAA,CAAK;AAAA,YACxE,KAAK,SAAS,oBAAC,YAAA,EAAW,SAAQ,SAAQ,OAAM,kBAAiB,QAAM,MAAE,UAAA,KAAK,MAAA,CAAM;AAAA,YACpF,KAAK,YAAY,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAiB,WAAU,KAAI,QAAM,MAAE,eAAK,SAAA,CAAS;AAAA,UAAA,GAC/G;AAAA,UAEC,gCAAa,SAAA,EAAQ;AAAA,UACrB,gCAAa,KAAA,EAAI,IAAI,EAAC,IAAI,IAAA,GAAO,UAAA,UAAS;AAAA,UAC1C,UAAU,SAAS,KAAK,oBAAC,SAAA,CAAA,CAAQ;AAAA,UAClC,oBAAC,WAAA,EAAU,OAAO,WAAW,SAAS,WAAW;AAAA,UAChD,iCAAc,SAAA,EAAQ;AAAA,UACtB,aACG,qBAAC,UAAA,EAAS,SAAS,SAAS,UAAU,cAClC,UAAA;AAAA,YAAA,oBAAC,cAAA,EAAa,UAAA,oBAAC,YAAA,EAAW,UAAS,SAAQ,GAAE;AAAA,YAC7C,oBAAC,cAAA,EAAa,SAAS,EAAE,SAAS,EAAA,CAAG;AAAA,UAAA,EAAA,CACzC;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAER,GACJ;AAER;"}
1
+ {"version":3,"file":"AccountMenu.js","sources":["../../src/appShell/AccountMenu.jsx"],"sourcesContent":["// La cuenta de quien está dentro, arriba a la derecha (como en las plantillas de MUI): un avatar con sus iniciales (o su\n// foto) que abre un menú con su nombre y correo, Mi perfil, lo que el proyecto agregue (accountMenuItems: Facturación,\n// Mis llaves…) y Salir.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaAccountMenu con defaultProps y\n// styleOverrides de sus partes: button, avatar, header, name, settings.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Avatar from '@mui/material/Avatar';\nimport Box from '@mui/material/Box';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport LogoutIcon from '@mui/icons-material/Logout';\nimport PersonOutlineIcon from '@mui/icons-material/PersonOutline';\n\nimport {initialsOf} from '@/appShell/menuItemText.js';\nimport {barMenuPlacementOf} from '@/appShell/barMenuPlacement.js';\nimport MenuLinks from '@/appShell/MenuLinks.jsx';\nimport {MONOGRAM_GRAY, WHITE_LABEL} from '@/theme/components/glassComponents.js';\nimport {isGlassTheme} from '@/theme/glassSurface.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaAccountMenu)\nconst THEME_NAME = 'CalcitaAccountMenu';\nconst AccountButton = piecePart(THEME_NAME, 'Button', IconButton);\nconst AccountAvatar = piecePart(THEME_NAME, 'Avatar', Avatar);\nconst AccountHeader = piecePart(THEME_NAME, 'Header', Box);\nconst AccountName = piecePart(THEME_NAME, 'Name', Typography);\nconst AccountSettings = piecePart(THEME_NAME, 'Settings', Box);\n\n/**\n * @param {object} props\n * @param {{name: string, email?: string, subtitle?: string, avatarUrl?: string}} props.user\n * @param {string} [props.profilePath] sin ella no sale «Mi perfil»\n * @param {Array<{to?: string, onClick?: Function, label?: string, labelKey?: string, Icon?: import('react').ElementType}>} [props.accountMenuItems]\n * @param {() => void} [props.onSignOut] sin él no sale «Salir»\n * @param {boolean} [props.isSigningOut]\n * @param {import('react').ReactNode} [props.settings] los ajustes de la app, entre los datos de la persona y sus ligas\n */\nexport default function AccountMenu(props) {\n // lo que el proyecto puso en su tema (components.CalcitaAccountMenu.defaultProps) y encima lo que llega aquí\n const {user, profilePath, accountMenuItems = [], onSignOut, isSigningOut = false, settings} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('layout');\n const [anchorElement, setAnchorElement] = useState(null);\n const closeMenu = () => setAnchorElement(null);\n const menuLinks = [\n ...(profilePath ? [{to: profilePath, labelKey: 'layout:account.myProfile', Icon: PersonOutlineIcon}] : []),\n ...accountMenuItems,\n ];\n\n // hacia arriba si la barra está abajo (teléfono con vidrio)\n const placement = barMenuPlacementOf(anchorElement);\n\n const signOut = () => {\n closeMenu();\n onSignOut();\n };\n\n return (\n <>\n <Tooltip title={t('account.open')}>\n <AccountButton size=\"small\" aria-label={t('account.open')} aria-haspopup=\"menu\" onClick={(event) => setAnchorElement(event.currentTarget)} ownSx={{p: 0.5}}>\n <AccountAvatar src={user.avatarUrl} alt=\"\" ownSx={(theme) => ({\n width: 32, height: 32, fontSize: '0.875rem', bgcolor: 'secondary.main', color: 'secondary.contrastText',\n // con vidrio, en oscuro el monograma de Apple (gris oscuro con letra blanca), el que se aprobó. Antes lo\n // ponía el tema porque su regla de oscuro le ganaba a este sx; ahora el sx gana en los dos modos\n ...(isGlassTheme(theme) && theme.applyStyles('dark', {bgcolor: MONOGRAM_GRAY.dark, color: WHITE_LABEL})),\n })}>\n {initialsOf(user.name)}\n </AccountAvatar>\n </AccountButton>\n </Tooltip>\n <Menu anchorEl={anchorElement} open={Boolean(anchorElement)} onClose={closeMenu}\n anchorOrigin={placement.anchorOrigin} transformOrigin={placement.transformOrigin}\n slotProps={{paper: {sx: {minWidth: settings ? 300 : 240, maxWidth: 360, ...placement.gapSx}}}}>\n <AccountHeader ownSx={{px: 2, py: 1.25}}>\n <AccountName variant=\"subtitle2\" noWrap ownSx={{fontWeight: 600}}>{user.name}</AccountName>\n {user.email && <Typography variant=\"body2\" color=\"text.secondary\" noWrap>{user.email}</Typography>}\n {user.subtitle && <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\" noWrap>{user.subtitle}</Typography>}\n </AccountHeader>\n {/* los ajustes de la app (letra, idioma, claro u oscuro y los del proyecto): en la cuenta y no sueltos en la barra */}\n {settings && <Divider />}\n {settings && <AccountSettings ownSx={{py: 0.5}}>{settings}</AccountSettings>}\n {menuLinks.length > 0 && <Divider />}\n <MenuLinks links={menuLinks} onClose={closeMenu} />\n {onSignOut && <Divider />}\n {onSignOut && (\n <MenuItem onClick={signOut} disabled={isSigningOut}>\n <ListItemIcon><LogoutIcon fontSize=\"small\" /></ListItemIcon>\n <ListItemText primary={t('signOut')} />\n </MenuItem>\n )}\n </Menu>\n </>\n );\n}\n"],"names":[],"mappings":"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"}