@donotdev/ui 0.0.13 → 0.0.14

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 (147) hide show
  1. package/dist/components/auth/AuthMenu.d.ts.map +1 -1
  2. package/dist/components/auth/AuthMenu.js +19 -20
  3. package/dist/components/common/FeatureCard.d.ts +3 -1
  4. package/dist/components/common/FeatureCard.d.ts.map +1 -1
  5. package/dist/components/common/FeatureCard.js +2 -2
  6. package/dist/components/common/ProgressBar.js +2 -2
  7. package/dist/components/common/TechBento.d.ts +14 -2
  8. package/dist/components/common/TechBento.d.ts.map +1 -1
  9. package/dist/components/common/TechBento.js +8 -9
  10. package/dist/components/cookie-consent/CookieConsent.d.ts.map +1 -1
  11. package/dist/components/cookie-consent/CookieConsent.js +3 -4
  12. package/dist/components/layout/components/DropdownNavigation.d.ts.map +1 -1
  13. package/dist/components/layout/components/DropdownNavigation.js +3 -12
  14. package/dist/components/layout/components/FloatingLanguageSwitcher.js +1 -1
  15. package/dist/components/layout/components/Notifications.d.ts +1 -3
  16. package/dist/components/layout/components/Notifications.d.ts.map +1 -1
  17. package/dist/components/layout/components/Notifications.js +4 -2
  18. package/dist/components/layout/components/header/AppBranding.d.ts.map +1 -1
  19. package/dist/components/layout/components/header/AppBranding.js +2 -1
  20. package/dist/components/layout/components/header/AppIcon.d.ts.map +1 -1
  21. package/dist/components/layout/components/header/AppIcon.js +5 -2
  22. package/dist/components/layout/components/header/CacheSettings.d.ts.map +1 -1
  23. package/dist/components/layout/components/header/CacheSettings.js +3 -1
  24. package/dist/components/layout/components/header/HeaderNavigation.d.ts +6 -0
  25. package/dist/components/layout/components/header/HeaderNavigation.d.ts.map +1 -1
  26. package/dist/components/layout/components/header/HeaderNavigation.js +12 -2
  27. package/dist/components/license/LicenseWatermark.d.ts.map +1 -1
  28. package/dist/components/license/LicenseWatermark.js +3 -1
  29. package/dist/crud/components/CrudCardLink.d.ts +17 -0
  30. package/dist/crud/components/CrudCardLink.d.ts.map +1 -0
  31. package/dist/crud/components/CrudCardLink.js +17 -0
  32. package/dist/crud/components/EntityCardList.d.ts.map +1 -1
  33. package/dist/crud/components/EntityCardList.js +24 -79
  34. package/dist/crud/components/EntityDisplayRenderer.d.ts +1 -1
  35. package/dist/crud/components/EntityDisplayRenderer.d.ts.map +1 -1
  36. package/dist/crud/components/EntityDisplayRenderer.js +6 -2
  37. package/dist/crud/components/EntityFormRenderer.d.ts +1 -1
  38. package/dist/crud/components/EntityFormRenderer.d.ts.map +1 -1
  39. package/dist/crud/components/EntityFormRenderer.js +29 -18
  40. package/dist/crud/components/EntityList.d.ts +1 -1
  41. package/dist/crud/components/EntityList.d.ts.map +1 -1
  42. package/dist/crud/components/EntityList.js +1 -1
  43. package/dist/crud/components/EntityRecommendations.d.ts +28 -0
  44. package/dist/crud/components/EntityRecommendations.d.ts.map +1 -0
  45. package/dist/crud/components/EntityRecommendations.js +31 -0
  46. package/dist/crud/components/index.d.ts +2 -1
  47. package/dist/crud/components/index.d.ts.map +1 -1
  48. package/dist/crud/components/index.js +1 -0
  49. package/dist/index.js +4 -4
  50. package/dist/internal/common/RouteErrorFallback.d.ts.map +1 -1
  51. package/dist/internal/devtools/components/AuthDebugButton.js +1 -1
  52. package/dist/internal/devtools/components/DesignTab.d.ts.map +1 -1
  53. package/dist/internal/devtools/components/DesignTab.js +3 -2
  54. package/dist/internal/devtools/components/LayoutReset.d.ts.map +1 -1
  55. package/dist/internal/devtools/components/LayoutReset.js +2 -0
  56. package/dist/internal/devtools/components/StoresTab.d.ts.map +1 -1
  57. package/dist/internal/devtools/components/StoresTab.js +3 -0
  58. package/dist/internal/devtools/utils/envVarDiscovery.d.ts +1 -0
  59. package/dist/internal/devtools/utils/envVarDiscovery.d.ts.map +1 -1
  60. package/dist/internal/devtools/utils/envVarDiscovery.js +5 -0
  61. package/dist/internal/devtools/utils/virtualModuleInspector.d.ts.map +1 -1
  62. package/dist/internal/devtools/utils/virtualModuleInspector.js +27 -21
  63. package/dist/internal/initializers/BaseStoresInitializer.d.ts.map +1 -1
  64. package/dist/internal/initializers/BaseStoresInitializer.js +30 -6
  65. package/dist/internal/layout/components/AutoMetaTags.d.ts.map +1 -1
  66. package/dist/internal/layout/components/AutoMetaTags.js +10 -8
  67. package/dist/internal/layout/components/FontPreloadLinks.d.ts +16 -0
  68. package/dist/internal/layout/components/FontPreloadLinks.d.ts.map +1 -0
  69. package/dist/internal/layout/components/FontPreloadLinks.js +32 -0
  70. package/dist/internal/layout/components/PerformanceHints.d.ts +7 -12
  71. package/dist/internal/layout/components/PerformanceHints.d.ts.map +1 -1
  72. package/dist/internal/layout/components/PerformanceHints.js +8 -12
  73. package/dist/internal/layout/components/footer/useLegalLinks.d.ts +6 -5
  74. package/dist/internal/layout/components/footer/useLegalLinks.d.ts.map +1 -1
  75. package/dist/internal/layout/components/footer/useLegalLinks.js +6 -2
  76. package/dist/internal/layout/zones/DnDevFooter.d.ts +6 -0
  77. package/dist/internal/layout/zones/DnDevFooter.d.ts.map +1 -1
  78. package/dist/internal/layout/zones/DnDevFooter.js +10 -4
  79. package/dist/internal/layout/zones/DnDevHeader.d.ts +7 -0
  80. package/dist/internal/layout/zones/DnDevHeader.d.ts.map +1 -1
  81. package/dist/internal/layout/zones/DnDevHeader.js +7 -0
  82. package/dist/internal/layout/zones/DnDevMergedBar.d.ts +7 -0
  83. package/dist/internal/layout/zones/DnDevMergedBar.d.ts.map +1 -1
  84. package/dist/internal/layout/zones/DnDevMergedBar.js +9 -0
  85. package/dist/internal/layout/zones/DnDevSidebar.d.ts +4 -0
  86. package/dist/internal/layout/zones/DnDevSidebar.d.ts.map +1 -1
  87. package/dist/internal/layout/zones/DnDevSidebar.js +13 -1
  88. package/dist/next.d.ts +1 -0
  89. package/dist/next.d.ts.map +1 -1
  90. package/dist/next.js +1 -0
  91. package/dist/routing/AuthGuard.d.ts +1 -1
  92. package/dist/routing/AuthGuard.d.ts.map +1 -1
  93. package/dist/routing/AuthGuard.js +3 -1
  94. package/dist/routing/GoTo.d.ts.map +1 -1
  95. package/dist/routing/GoTo.js +3 -1
  96. package/dist/routing/GoToDialog.d.ts.map +1 -1
  97. package/dist/routing/GoToDialog.js +2 -7
  98. package/dist/routing/GoToInput.d.ts +0 -3
  99. package/dist/routing/GoToInput.d.ts.map +1 -1
  100. package/dist/routing/GoToInput.js +4 -2
  101. package/dist/routing/Link.js +1 -1
  102. package/dist/routing/NavigationItem.d.ts +29 -7
  103. package/dist/routing/NavigationItem.d.ts.map +1 -1
  104. package/dist/routing/NavigationItem.js +22 -6
  105. package/dist/routing/hooks/hooks.next.js +1 -1
  106. package/dist/routing/hooks/hooks.vite.js +1 -1
  107. package/dist/routing/hooks/useRedirectGuard.next.d.ts.map +1 -1
  108. package/dist/routing/hooks/useRedirectGuard.next.js +9 -8
  109. package/dist/routing/hooks/useRedirectGuard.vite.d.ts.map +1 -1
  110. package/dist/routing/hooks/useRedirectGuard.vite.js +9 -8
  111. package/dist/routing/hooks/useSearchParams.next.d.ts +18 -1
  112. package/dist/routing/hooks/useSearchParams.next.d.ts.map +1 -1
  113. package/dist/routing/hooks/useSearchParams.next.js +16 -0
  114. package/dist/routing/hooks/useSearchParams.vite.d.ts +16 -0
  115. package/dist/routing/hooks/useSearchParams.vite.d.ts.map +1 -1
  116. package/dist/routing/hooks/useSearchParams.vite.js +17 -1
  117. package/dist/routing/index.d.ts.map +1 -1
  118. package/dist/routing/index.js +2 -0
  119. package/dist/routing/useNavigation.d.ts +30 -0
  120. package/dist/routing/useNavigation.d.ts.map +1 -1
  121. package/dist/routing/useNavigation.js +40 -3
  122. package/dist/routing/useRouteDiscovery.d.ts +2 -2
  123. package/dist/routing/useRouteDiscovery.d.ts.map +1 -1
  124. package/dist/routing/useRouteDiscovery.js +10 -4
  125. package/dist/styles/index.css +268 -82
  126. package/dist/utils/index.d.ts +1 -0
  127. package/dist/utils/index.d.ts.map +1 -1
  128. package/dist/utils/index.js +1 -0
  129. package/dist/utils/sanitizeSvg.d.ts +13 -0
  130. package/dist/utils/sanitizeSvg.d.ts.map +1 -0
  131. package/dist/utils/sanitizeSvg.js +47 -0
  132. package/dist/utils/useBillingVisibility.d.ts.map +1 -1
  133. package/dist/utils/useBillingVisibility.js +0 -7
  134. package/dist/utils/useCrudSafe.d.ts +0 -2
  135. package/dist/utils/useCrudSafe.d.ts.map +1 -1
  136. package/dist/utils/useFormStoreSafe.d.ts +3 -1
  137. package/dist/utils/useFormStoreSafe.d.ts.map +1 -1
  138. package/dist/utils/useFormStoreSafe.js +4 -5
  139. package/dist/vite-routing/AppRoutes.d.ts +19 -8
  140. package/dist/vite-routing/AppRoutes.d.ts.map +1 -1
  141. package/dist/vite-routing/AppRoutes.js +0 -3
  142. package/package.json +15 -11
  143. package/assets/fonts/fonts.css +0 -206
  144. package/dist/dndev.css +0 -10733
  145. package/dist/routing/Navigate.d.ts +0 -10
  146. package/dist/routing/Navigate.d.ts.map +0 -1
  147. package/dist/routing/Navigate.js +0 -10
@@ -15,10 +15,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
15
15
  */
16
16
  import { useMemo, useCallback } from 'react';
17
17
  import { Heart } from 'lucide-react';
18
- import { Grid, Card, Stack, Text, Spinner, Section, Button, } from '@donotdev/components';
19
- import { useTranslation } from '@donotdev/core';
18
+ import { Grid, Stack, Text, Spinner, Section, Button, } from '@donotdev/components';
19
+ import { useTranslation, getListCardFieldNames } from '@donotdev/core';
20
20
  import { useNavigate } from '../../routing';
21
- import { translateFieldLabel, useCrudCardList, EntityFilters, useEntityFavorites, matchesFilter, formatValue, useCrudFilters, } from '@donotdev/crud';
21
+ import { useCrudCardList, EntityFilters, useEntityFavorites, matchesFilter, useCrudFilters, } from '@donotdev/crud';
22
+ import { CrudCard } from './CrudCardLink';
22
23
  /**
23
24
  * Entity Card List Component - Card grid view for public/user-facing browsing
24
25
  *
@@ -43,11 +44,7 @@ filter, hideFilters = false, }) {
43
44
  });
44
45
  // Favorites toggle from CrudStore (persists across navigation)
45
46
  // Note: EntityFilters now manages its own filters internally via useCrudFilters
46
- const { showFavoritesOnly, setShowFavoritesOnly } = useCrudFilters({
47
- collection: entity.collection,
48
- });
49
- // Get filters for applying to data (EntityFilters manages its own state)
50
- const { filters } = useCrudFilters({
47
+ const { showFavoritesOnly, setShowFavoritesOnly, filters } = useCrudFilters({
51
48
  collection: entity.collection,
52
49
  });
53
50
  // Apply filters from EntityFilters component
@@ -86,32 +83,8 @@ filter, hideFilters = false, }) {
86
83
  navigate(`${base}/${id}`);
87
84
  }
88
85
  }, [base, navigate, onClick]);
89
- // Determine which fields to show in cards
90
- const fieldsToShow = useMemo(() => {
91
- const cardFields = entity.listCardFields ?? entity.listFields;
92
- if (cardFields && cardFields.length > 0)
93
- return cardFields;
94
- return Object.keys(entity.fields).slice(0, 4);
95
- }, [entity.listCardFields, entity.listFields, entity.fields]);
96
- // Find image field
97
- const imageField = useMemo(() => {
98
- const imageFieldsInList = fieldsToShow.filter((fieldName) => {
99
- const fieldConfig = entity.fields[fieldName];
100
- return fieldConfig?.type === 'image' || fieldConfig?.type === 'images';
101
- });
102
- if (imageFieldsInList.length > 0)
103
- return imageFieldsInList[0];
104
- // Fallback: search all entity fields
105
- const allImageFields = Object.keys(entity.fields).filter((fieldName) => {
106
- const fieldConfig = entity.fields[fieldName];
107
- return fieldConfig?.type === 'image' || fieldConfig?.type === 'images';
108
- });
109
- return allImageFields[0] || null;
110
- }, [fieldsToShow, entity.fields]);
111
- // Get other fields (non-image)
112
- const otherFields = useMemo(() => {
113
- return fieldsToShow.filter((fieldName) => fieldName !== imageField);
114
- }, [fieldsToShow, imageField]);
86
+ // Flat field names for filters (works with both string[] and ListCardLayout)
87
+ const fieldsToFilter = useMemo(() => getListCardFieldNames(entity), [entity]);
115
88
  const entityName = t('name', { defaultValue: entity.name });
116
89
  return (_jsxs(_Fragment, { children: [!hideFilters && (_jsx(Section, { title: tCrud('filters.title', {
117
90
  entity: entityName,
@@ -120,7 +93,7 @@ filter, hideFilters = false, }) {
120
93
  ? tCrud('favorites.showAll', { defaultValue: 'Show All' })
121
94
  : tCrud('favorites.showFavorites', {
122
95
  defaultValue: 'Show Favorites',
123
- }) }), _jsx(EntityFilters, { entity: entity, data: rawData, fieldsToFilter: entity.listCardFields })] }) })), _jsx(Section, { title: loading
96
+ }) }), _jsx(EntityFilters, { entity: entity, data: rawData, fieldsToFilter: fieldsToFilter })] }) })), _jsx(Section, { title: loading
124
97
  ? tCrud('results.title.fetching', { defaultValue: 'Fetching...' })
125
98
  : tCrud('results.title.count', {
126
99
  count: data.length,
@@ -132,50 +105,22 @@ filter, hideFilters = false, }) {
132
105
  }) }), _jsx(Text, { style: { color: 'var(--muted-foreground)' }, children: tCrud('emptyState.description', {
133
106
  defaultValue: `No ${entity.name.toLowerCase()} available at this time.`,
134
107
  }) })] })) : (_jsx(Grid, { cols: cols, children: data.map((item) => {
135
- const imageValue = imageField ? item[imageField] : null;
136
- // Backend optimizes picture fields for listCard: returns thumbUrl string directly
137
- // (or fullUrl if thumbUrl missing, or null if no picture)
138
- const imageUrl = typeof imageValue === 'string' ? imageValue : null;
139
- // Title from first non-image field
140
- const titleField = otherFields[0];
141
- const titleValue = titleField ? item[titleField] : item.id;
142
108
  const itemIsFavorite = isFavorite(item.id);
143
- return (_jsxs(Card, { title: String(titleValue || ''), clickable: true, onClick: () => handleView(item.id), elevated: true, children: [_jsx(Heart, { fill: itemIsFavorite ? '#ef4444' : '#ffffff', stroke: itemIsFavorite ? '#ef4444' : 'var(--muted-foreground)', onClick: (e) => {
144
- e.stopPropagation();
145
- toggleFavorite(item.id);
146
- }, style: {
147
- position: 'absolute',
148
- top: 'var(--gap-sm)',
149
- right: 'var(--gap-sm)',
150
- zIndex: 10,
151
- cursor: 'pointer',
152
- width: 'var(--icon-md)',
153
- height: 'var(--icon-md)',
154
- transition: 'fill 0.2s, stroke 0.2s',
155
- }, "aria-label": itemIsFavorite
156
- ? tCrud('favorites.remove', {
157
- defaultValue: 'Remove from favorites',
158
- })
159
- : tCrud('favorites.add', {
160
- defaultValue: 'Add to favorites',
161
- }) }), _jsxs(Stack, { direction: "column", children: [imageUrl && (_jsx("div", { style: {
162
- width: '100%',
163
- aspectRatio: '16/9',
164
- borderRadius: 'var(--radius-md)',
165
- overflow: 'hidden',
166
- backgroundColor: 'var(--muted)',
167
- position: 'relative',
168
- }, children: _jsx("img", { src: imageUrl, alt: String(titleValue || ''), style: {
169
- width: '100%',
170
- height: '100%',
171
- objectFit: 'cover',
172
- } }) })), _jsx(Stack, { direction: "column", gap: "tight", children: otherFields.slice(1, 4).map((fieldName) => {
173
- const fieldConfig = entity.fields[fieldName];
174
- if (!fieldConfig)
175
- return null;
176
- return (_jsxs("div", { children: [_jsx(Text, { level: "small", variant: "muted", children: translateFieldLabel(fieldName, fieldConfig, t) }), _jsx(Text, { children: formatValue(item[fieldName], fieldConfig, t, {
177
- compact: true,
178
- }) })] }, fieldName));
179
- }) })] })] }, item.id));
109
+ const detailHref = onClick ? undefined : `${base}/${item.id}`;
110
+ return (_jsx(CrudCard, { item: item, entity: entity, detailHref: detailHref, onClick: onClick ? () => handleView(item.id) : undefined, renderActions: _jsx(Heart, { fill: itemIsFavorite ? '#ef4444' : '#ffffff', stroke: itemIsFavorite ? '#ef4444' : 'var(--muted-foreground)', onClick: (e) => {
111
+ e.stopPropagation();
112
+ toggleFavorite(item.id);
113
+ }, style: {
114
+ cursor: 'pointer',
115
+ width: 'var(--icon-md)',
116
+ height: 'var(--icon-md)',
117
+ transition: 'fill 0.2s, stroke 0.2s',
118
+ }, "aria-label": itemIsFavorite
119
+ ? tCrud('favorites.remove', {
120
+ defaultValue: 'Remove from favorites',
121
+ })
122
+ : tCrud('favorites.add', {
123
+ defaultValue: 'Add to favorites',
124
+ }) }) }, item.id));
180
125
  }) })) })] }));
181
126
  }
@@ -16,6 +16,6 @@ export type { EntityDisplayRendererProps };
16
16
  * <EntityDisplayRenderer entity={carEntity} id={carId} />
17
17
  * ```
18
18
  */
19
- export declare function EntityDisplayRenderer<T extends EntityRecord = EntityRecord>({ entity, id, t, className, backend, loadingMessage, notFoundMessage, viewerRole, }: EntityDisplayRendererProps<T>): import("react/jsx-runtime").JSX.Element;
19
+ export declare function EntityDisplayRenderer<T extends EntityRecord = EntityRecord>({ entity, id, t, className, loadingMessage, notFoundMessage, viewerRole: viewerRoleProp, }: EntityDisplayRendererProps<T>): import("react/jsx-runtime").JSX.Element;
20
20
  export default EntityDisplayRenderer;
21
21
  //# sourceMappingURL=EntityDisplayRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntityDisplayRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityDisplayRenderer.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,0BAA0B,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG/E,YAAY,EAAE,0BAA0B,EAAE,CAAC;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC3E,MAAM,EACN,EAAE,EACF,CAAC,EACD,SAAc,EACd,OAAqB,EACrB,cAAc,EACd,eAAe,EACf,UAAoB,GACrB,EAAE,0BAA0B,CAAC,CAAC,CAAC,2CAgM/B;AAED,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"EntityDisplayRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityDisplayRenderer.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,0BAA0B,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAI/E,YAAY,EAAE,0BAA0B,EAAE,CAAC;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC3E,MAAM,EACN,EAAE,EACF,CAAC,EACD,SAAc,EACd,cAAc,EACd,eAAe,EACf,UAAU,EAAE,cAAc,GAC3B,EAAE,0BAA0B,CAAC,CAAC,CAAC,2CAkM/B;AAED,eAAe,qBAAqB,CAAC"}
@@ -14,6 +14,7 @@ import { useEffect, useState, useMemo } from 'react';
14
14
  import { Stack, Spinner } from '@donotdev/components';
15
15
  import { useTranslation, isFieldVisible } from '@donotdev/core';
16
16
  import { useCrud, DisplayFieldRenderer } from '@donotdev/crud';
17
+ import { useAuthSafe } from '../../utils/useAuthSafe';
17
18
  /**
18
19
  * EntityDisplayRenderer - Automatically fetches and displays entity data
19
20
  *
@@ -30,8 +31,11 @@ import { useCrud, DisplayFieldRenderer } from '@donotdev/crud';
30
31
  * <EntityDisplayRenderer entity={carEntity} id={carId} />
31
32
  * ```
32
33
  */
33
- export function EntityDisplayRenderer({ entity, id, t, className = '', backend = 'functions', loadingMessage, notFoundMessage, viewerRole = 'guest', }) {
34
- const { get, loading, data: storeData, error: storeError, isAvailable, } = useCrud(entity, { backend });
34
+ export function EntityDisplayRenderer({ entity, id, t, className = '', loadingMessage, notFoundMessage, viewerRole: viewerRoleProp, }) {
35
+ // Auto-detect role from auth; prop overrides
36
+ const authRole = useAuthSafe('userRole');
37
+ const viewerRole = viewerRoleProp ?? authRole;
38
+ const { get, loading, data: storeData, error: storeError, isAvailable, } = useCrud(entity);
35
39
  const [isFetching, setIsFetching] = useState(false);
36
40
  const [fetchError, setFetchError] = useState(null);
37
41
  const [data, setData] = useState(null);
@@ -13,6 +13,6 @@ export type { EntityFormRendererProps };
13
13
  * @since 0.0.1
14
14
  * @author AMBROISE PARK Consulting
15
15
  */
16
- export declare function EntityFormRenderer<T extends EntityRecord = EntityRecord>({ entity, onSubmit, t, className, submitText, loading, defaultValues, submitVariant, secondaryButtonText, secondaryButtonVariant, onSecondarySubmit, viewerRole, operation, autoSave, formId: externalFormId, cancelText, cancelPath, successPath, onCancel, warnOnUnsavedChanges, unsavedChangesMessage, hideVisibilityInfo, }: EntityFormRendererProps<T>): import("react/jsx-runtime").JSX.Element;
16
+ export declare function EntityFormRenderer<T extends EntityRecord = EntityRecord>({ entity, onSubmit, t, className, submitText, loading, defaultValues, submitVariant, secondaryButtonText, secondaryButtonVariant, onSecondarySubmit, viewerRole: viewerRoleProp, operation, autoSave, formId: externalFormId, cancelText, cancelPath, successPath, onCancel, warnOnUnsavedChanges, unsavedChangesMessage, hideVisibilityInfo, }: EntityFormRendererProps<T>): import("react/jsx-runtime").JSX.Element;
17
17
  export default EntityFormRenderer;
18
18
  //# sourceMappingURL=EntityFormRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntityFormRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityFormRenderer.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EACV,uBAAuB,EACvB,YAAY,EAEb,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,uBAAuB,EAAE,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACxE,MAAM,EACN,QAAQ,EACR,CAAC,EACD,SAAc,EACd,UAAU,EACV,OAAe,EACf,aAAa,EACb,aAAyB,EACzB,mBAAmB,EACnB,sBAAkC,EAClC,iBAAiB,EACjB,UAAU,EACV,SAA6C,EAC7C,QAAiC,EACjC,MAAM,EAAE,cAAc,EACtB,UAAU,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,oBAA2B,EAC3B,qBAAqB,EACrB,kBAA0B,GAC3B,EAAE,uBAAuB,CAAC,CAAC,CAAC,2CAic5B;AAED,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"EntityFormRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityFormRenderer.tsx"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EACV,uBAAuB,EACvB,YAAY,EAEb,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,uBAAuB,EAAE,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACxE,MAAM,EACN,QAAQ,EACR,CAAC,EACD,SAAc,EACd,UAAU,EACV,OAAe,EACf,aAAa,EACb,aAAyB,EACzB,mBAAmB,EACnB,sBAAkC,EAClC,iBAAiB,EACjB,UAAU,EAAE,cAAc,EAC1B,SAA6C,EAC7C,QAAiC,EACjC,MAAM,EAAE,cAAc,EACtB,UAAU,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,oBAA2B,EAC3B,qBAAqB,EACrB,kBAA0B,GAC3B,EAAE,uBAAuB,CAAC,CAAC,CAAC,2CA4c5B;AAED,eAAe,kBAAkB,CAAC"}
@@ -16,6 +16,7 @@ import { Badge, Button, DropdownMenu, Grid, Stack, Spinner, } from '@donotdev/co
16
16
  import { hasRoleAccess, useTranslation } from '@donotdev/core';
17
17
  import { useNavigate } from '../../routing';
18
18
  import { DisplayFieldRenderer, FormFieldRenderer, UploadProvider, useEntityForm, useUnsavedChangesWarning, useConfirmNavigation, useFormStore, } from '@donotdev/crud';
19
+ import { useAuthSafe } from '../../utils/useAuthSafe';
19
20
  /**
20
21
  * EntityFormRenderer - Dumb component that renders a form from entity definition.
21
22
  *
@@ -29,8 +30,11 @@ import { DisplayFieldRenderer, FormFieldRenderer, UploadProvider, useEntityForm,
29
30
  * @since 0.0.1
30
31
  * @author AMBROISE PARK Consulting
31
32
  */
32
- export function EntityFormRenderer({ entity, onSubmit, t, className = '', submitText, loading = false, defaultValues, submitVariant = 'primary', secondaryButtonText, secondaryButtonVariant = 'outline', onSecondarySubmit, viewerRole, operation = defaultValues ? 'edit' : 'create', autoSave = operation === 'create', formId: externalFormId, cancelText, cancelPath, successPath, onCancel, warnOnUnsavedChanges = true, unsavedChangesMessage, hideVisibilityInfo = false, }) {
33
+ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submitText, loading = false, defaultValues, submitVariant = 'primary', secondaryButtonText, secondaryButtonVariant = 'outline', onSecondarySubmit, viewerRole: viewerRoleProp, operation = defaultValues ? 'edit' : 'create', autoSave = operation === 'create', formId: externalFormId, cancelText, cancelPath, successPath, onCancel, warnOnUnsavedChanges = true, unsavedChangesMessage, hideVisibilityInfo = false, }) {
33
34
  const navigate = useNavigate();
35
+ // Auto-detect role from auth; prop overrides (e.g. View-As preview)
36
+ const authRole = useAuthSafe('userRole');
37
+ const viewerRole = viewerRoleProp ?? authRole;
34
38
  // Generate stable form ID
35
39
  const generatedFormId = useId();
36
40
  const formId = externalFormId ?? `entity-form-${entity.name}-${generatedFormId}`;
@@ -101,12 +105,14 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
101
105
  return fieldRoleIndex <= previewRoleIndex;
102
106
  });
103
107
  }, [rawFields, canPreviewRole, previewRole]);
108
+ // Subscribe to setIsDirty action via React-compatible selector
109
+ const setFormIsDirty = useFormStore((state) => state.setIsDirty);
104
110
  // Sync isDirty to FormStore (single source of truth)
105
111
  useEffect(() => {
106
112
  if (formId) {
107
- useFormStore.getState().setIsDirty(formId, isDirtyForBlocking);
113
+ setFormIsDirty(formId, isDirtyForBlocking);
108
114
  }
109
- }, [formId, isDirtyForBlocking]);
115
+ }, [formId, isDirtyForBlocking, setFormIsDirty]);
110
116
  // Cleanup on unmount
111
117
  useEffect(() => {
112
118
  return cleanup;
@@ -151,22 +157,27 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
151
157
  }
152
158
  }
153
159
  };
154
- // Wrap onSubmit to navigate optimistically (fire and forget)
160
+ // Wrap onSubmit to await the result before navigating
155
161
  // handleSubmit from react-hook-form expects: (data: EntityData) => void | Promise<void>
156
162
  // Our onSubmit prop is (data: T) => void | Promise<void>, where T extends EntityRecord
157
163
  // EntityData should be compatible with T, so we can safely pass it
158
- const handleFormSubmit = (data) => {
159
- // Call onSubmit (don't await - optimistic update pattern)
160
- // Type assertion is safe: EntityData is the validated form data, T is EntityRecord
161
- onSubmit(data);
162
- // Navigate immediately (optimistic navigation - feels fast)
163
- // If onSubmit is async, navigation happens while it's processing in background
164
- if (successPath) {
165
- navigate(successPath);
164
+ const handleFormSubmit = async (data) => {
165
+ try {
166
+ // Await onSubmit so navigation only happens after successful submission
167
+ // Type assertion is safe: EntityData is the validated form data, T is EntityRecord
168
+ await onSubmit(data);
169
+ // Navigate only after successful submission
170
+ if (successPath) {
171
+ navigate(successPath);
172
+ }
173
+ else {
174
+ // Default: navigate back (user came from list, go back there)
175
+ navigate('back');
176
+ }
166
177
  }
167
- else {
168
- // Default: navigate back (optimistic - user came from list, go back there)
169
- navigate('back');
178
+ catch {
179
+ // onSubmit rejected stay on form so user can correct errors
180
+ // Error display is handled by the caller's onSubmit implementation
170
181
  }
171
182
  };
172
183
  // Determine if cancel button should show
@@ -274,13 +285,13 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
274
285
  if (buttonCount === 1) {
275
286
  return (_jsx(Stack, { direction: "column", gap: "tight", style: buttonContainerStyle, children: buttons[0] }));
276
287
  }
277
- // Multiple buttons: use Grid with proportional columns (Submit gets 2fr)
278
- const templateColumns = buttonCount === 2
288
+ // Multiple buttons: responsive stacked on mobile, proportional on tablet+
289
+ const colTemplate = buttonCount === 2
279
290
  ? '1fr 2fr' // Cancel/Preview + Submit
280
291
  : buttonCount === 3
281
292
  ? '1fr 1fr 2fr' // Cancel + Preview + Submit
282
293
  : '1fr 1fr 1fr 2fr'; // Cancel + Preview + Secondary + Submit
283
- return (_jsx(Grid, { cols: [1, buttonCount, buttonCount, buttonCount], templateColumns: templateColumns, gap: "tight", style: buttonContainerStyle, children: buttons }));
294
+ return (_jsx(Grid, { cols: ['1fr', colTemplate, colTemplate, colTemplate], gap: "tight", style: buttonContainerStyle, children: buttons }));
284
295
  })()] })] }) }) }));
285
296
  }
286
297
  export default EntityFormRenderer;
@@ -10,5 +10,5 @@ export type { EntityListProps };
10
10
  * - Edit and Delete actions (admin only)
11
11
  * - Auto-routing when handlers not provided
12
12
  */
13
- export declare function EntityList({ entity, userRole, basePath, onClick, hideFilters, pagination, pageSize: pageSizeProp, queryOptions, exportable, }: EntityListProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function EntityList({ entity, basePath, onClick, hideFilters, pagination, pageSize: pageSizeProp, queryOptions, exportable, }: EntityListProps): import("react/jsx-runtime").JSX.Element;
14
14
  //# sourceMappingURL=EntityList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntityList.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityList.tsx"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,QAAkB,EAClB,QAAQ,EACR,OAAO,EACP,WAAmB,EACnB,UAAqB,EACrB,QAAQ,EAAE,YAAY,EACtB,YAAY,EACZ,UAAiB,GAClB,EAAE,eAAe,2CAkTjB"}
1
+ {"version":3,"file":"EntityList.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityList.tsx"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,QAAQ,EACR,OAAO,EACP,WAAmB,EACnB,UAAqB,EACrB,QAAQ,EAAE,YAAY,EACtB,YAAY,EACZ,UAAiB,GAClB,EAAE,eAAe,2CAiTjB"}
@@ -29,7 +29,7 @@ import { translateFieldLabel, useCrud, useCrudList, EntityFilters, matchesFilter
29
29
  * - Edit and Delete actions (admin only)
30
30
  * - Auto-routing when handlers not provided
31
31
  */
32
- export function EntityList({ entity, userRole = 'guest', basePath, onClick, hideFilters = false, pagination = 'client', pageSize: pageSizeProp, queryOptions, exportable = true, }) {
32
+ export function EntityList({ entity, basePath, onClick, hideFilters = false, pagination = 'client', pageSize: pageSizeProp, queryOptions, exportable = true, }) {
33
33
  const navigate = useNavigate();
34
34
  const base = basePath ?? `/${entity.collection}`;
35
35
  // Server-side pagination state (only used when pagination='server')
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @fileoverview Reusable recommendations section for related entities
3
+ * @description Fetches and displays related entity cards in a grid.
4
+ * Caller defines "related by what" via queryOptions.
5
+ *
6
+ * @version 0.0.1
7
+ * @since 0.0.15
8
+ * @author AMBROISE PARK Consulting
9
+ */
10
+ import type { EntityRecommendationsProps } from '@donotdev/core';
11
+ /**
12
+ * Displays a grid of related entity cards.
13
+ *
14
+ * @example
15
+ * <EntityRecommendations
16
+ * entity={apartmentEntity}
17
+ * title={t('apartment.recommendations')}
18
+ * queryOptions={{
19
+ * where: [
20
+ * { field: 'district_code', operator: 'eq', value: current.district_code },
21
+ * { field: 'id', operator: 'neq', value: current.id },
22
+ * ],
23
+ * limit: 3,
24
+ * }}
25
+ * />
26
+ */
27
+ export declare function EntityRecommendations({ entity, queryOptions, title, basePath, cols, className, }: EntityRecommendationsProps): import("react/jsx-runtime").JSX.Element | null;
28
+ //# sourceMappingURL=EntityRecommendations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityRecommendations.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityRecommendations.tsx"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAMjE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,IAAQ,EACR,SAAS,GACV,EAAE,0BAA0B,kDAuB5B"}
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Section } from '@donotdev/components';
4
+ import { useCrudList } from '@donotdev/crud';
5
+ import { CrudCard } from './CrudCardLink';
6
+ /**
7
+ * Displays a grid of related entity cards.
8
+ *
9
+ * @example
10
+ * <EntityRecommendations
11
+ * entity={apartmentEntity}
12
+ * title={t('apartment.recommendations')}
13
+ * queryOptions={{
14
+ * where: [
15
+ * { field: 'district_code', operator: 'eq', value: current.district_code },
16
+ * { field: 'id', operator: 'neq', value: current.id },
17
+ * ],
18
+ * limit: 3,
19
+ * }}
20
+ * />
21
+ */
22
+ export function EntityRecommendations({ entity, queryOptions, title, basePath, cols = 3, className, }) {
23
+ const { items, loading } = useCrudList(entity, {
24
+ queryOptions,
25
+ });
26
+ if (!loading && items.length === 0) {
27
+ return null;
28
+ }
29
+ const resolvedBasePath = basePath ?? `/${entity.collection}`;
30
+ return (_jsx(Section, { title: title, gridCols: cols, className: className, children: items.map((item) => (_jsx(CrudCard, { item: item, entity: entity, detailHref: `${resolvedBasePath}/${item.id}` }, item.id))) }));
31
+ }
@@ -10,6 +10,7 @@ export { EntityDisplayRenderer } from './EntityDisplayRenderer';
10
10
  export { EntityList } from './EntityList';
11
11
  export { EntityCardList } from './EntityCardList';
12
12
  export { EntityFormRenderer } from './EntityFormRenderer';
13
+ export { EntityRecommendations } from './EntityRecommendations';
13
14
  export * from './Form';
14
- export type { EntityListProps, EntityCardListProps, EntityFormRendererProps, EntityDisplayRendererProps, } from '@donotdev/core';
15
+ export type { EntityListProps, EntityCardListProps, EntityRecommendationsProps, EntityFormRendererProps, EntityDisplayRendererProps, } from '@donotdev/core';
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/crud/components/index.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,cAAc,QAAQ,CAAC;AAEvB,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/crud/components/index.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,cAAc,QAAQ,CAAC;AAEvB,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,gBAAgB,CAAC"}
@@ -11,4 +11,5 @@ export { EntityDisplayRenderer } from './EntityDisplayRenderer';
11
11
  export { EntityList } from './EntityList';
12
12
  export { EntityCardList } from './EntityCardList';
13
13
  export { EntityFormRenderer } from './EntityFormRenderer';
14
+ export { EntityRecommendations } from './EntityRecommendations';
14
15
  export * from './Form';