@tuturuuu/ui 0.7.0 → 0.9.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 (226) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/biome.json +1 -1
  3. package/package.json +75 -73
  4. package/src/components/ui/accordion.tsx +1 -1
  5. package/src/components/ui/breadcrumb.tsx +1 -1
  6. package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
  7. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
  8. package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
  9. package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
  10. package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
  11. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
  12. package/src/components/ui/calendar.tsx +1 -1
  13. package/src/components/ui/carousel.tsx +1 -1
  14. package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
  15. package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
  16. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
  17. package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
  18. package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
  19. package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
  20. package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
  21. package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
  22. package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
  23. package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
  24. package/src/components/ui/checkbox.tsx +1 -1
  25. package/src/components/ui/color-picker.tsx +1 -1
  26. package/src/components/ui/command.tsx +1 -1
  27. package/src/components/ui/context-menu.tsx +5 -1
  28. package/src/components/ui/currency-input.test.tsx +43 -0
  29. package/src/components/ui/currency-input.tsx +1 -1
  30. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
  31. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +19 -0
  32. package/src/components/ui/custom/combobox.test.tsx +195 -0
  33. package/src/components/ui/custom/combobox.tsx +273 -156
  34. package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
  35. package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
  36. package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
  37. package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
  38. package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
  39. package/src/components/ui/custom/settings-dialog-shell.tsx +2 -1
  40. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  41. package/src/components/ui/custom/workspace-access/workspace-access-default-role-card.tsx +60 -35
  42. package/src/components/ui/custom/workspace-access/workspace-access-member-row.tsx +176 -167
  43. package/src/components/ui/custom/workspace-access/workspace-access-members.tsx +16 -10
  44. package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +75 -36
  45. package/src/components/ui/custom/workspace-access/workspace-access-page.tsx +39 -42
  46. package/src/components/ui/custom/workspace-access/workspace-access-people-filters.tsx +1 -1
  47. package/src/components/ui/custom/workspace-access/workspace-access-roles.tsx +113 -91
  48. package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +73 -32
  49. package/src/components/ui/custom/workspace-select.tsx +8 -3
  50. package/src/components/ui/dialog.test.tsx +52 -0
  51. package/src/components/ui/dialog.tsx +6 -2
  52. package/src/components/ui/dropdown-menu.tsx +5 -1
  53. package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
  54. package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
  55. package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
  56. package/src/components/ui/finance/debts/debts-page.tsx +15 -2
  57. package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
  58. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
  59. package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
  60. package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
  61. package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
  62. package/src/components/ui/finance/invoices/utils.ts +3 -1
  63. package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
  64. package/src/components/ui/finance/transactions/form-types.ts +3 -0
  65. package/src/components/ui/finance/transactions/form.tsx +2 -0
  66. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
  67. package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
  68. package/src/components/ui/finance/transactions/transaction-card.tsx +21 -9
  69. package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
  70. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  71. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  72. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  73. package/src/components/ui/finance/wallets/form.tsx +15 -4
  74. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  75. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  76. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  77. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  78. package/src/components/ui/input-otp.tsx +1 -1
  79. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  80. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  81. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  82. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  83. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  84. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  85. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  86. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  87. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  88. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  89. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  90. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  91. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  92. package/src/components/ui/money-input.test.tsx +64 -0
  93. package/src/components/ui/money-input.tsx +63 -0
  94. package/src/components/ui/navigation-menu.tsx +1 -1
  95. package/src/components/ui/pagination.tsx +1 -1
  96. package/src/components/ui/radio-group.tsx +1 -1
  97. package/src/components/ui/select.tsx +5 -1
  98. package/src/components/ui/sheet.tsx +1 -1
  99. package/src/components/ui/sidebar.tsx +1 -1
  100. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  101. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  102. package/src/components/ui/storefront/cart-summary.tsx +104 -80
  103. package/src/components/ui/storefront/checkout-overlay.tsx +26 -0
  104. package/src/components/ui/storefront/hero-panel.tsx +2 -8
  105. package/src/components/ui/storefront/image-panel.tsx +6 -0
  106. package/src/components/ui/storefront/index.ts +11 -0
  107. package/src/components/ui/storefront/listing-card.tsx +84 -22
  108. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  109. package/src/components/ui/storefront/product-detail.tsx +289 -0
  110. package/src/components/ui/storefront/product-dialog.tsx +72 -0
  111. package/src/components/ui/storefront/storefront-surface.test.tsx +221 -3
  112. package/src/components/ui/storefront/storefront-surface.tsx +288 -153
  113. package/src/components/ui/storefront/types.ts +27 -1
  114. package/src/components/ui/storefront/utils.ts +117 -27
  115. package/src/components/ui/text-editor/__tests__/content-migration.test.ts +32 -0
  116. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  117. package/src/components/ui/text-editor/__tests__/image-extension.test.ts +69 -1
  118. package/src/components/ui/text-editor/__tests__/video-extension.test.ts +47 -0
  119. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  120. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  121. package/src/components/ui/text-editor/content-migration.ts +41 -18
  122. package/src/components/ui/text-editor/editor.tsx +69 -14
  123. package/src/components/ui/text-editor/extensions.ts +9 -3
  124. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  125. package/src/components/ui/text-editor/image-extension.ts +40 -18
  126. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  127. package/src/components/ui/text-editor/video-extension.ts +11 -2
  128. package/src/components/ui/toast.tsx +1 -1
  129. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
  130. package/src/components/ui/tu-do/boards/__tests__/workspace-projects-client-page.test.tsx +70 -1
  131. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  132. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +222 -109
  133. package/src/components/ui/tu-do/boards/boardId/board-column-external-retry.test.tsx +127 -0
  134. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +113 -46
  135. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  136. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  137. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  138. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  139. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  140. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  141. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +51 -9
  142. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  143. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  144. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -0
  145. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +127 -38
  146. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  147. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  148. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  149. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  150. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  151. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  152. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  153. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  154. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  155. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  156. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  157. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +410 -4
  158. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +106 -14
  159. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
  160. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
  161. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
  162. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +186 -0
  163. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +59 -2
  164. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
  165. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  166. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +191 -28
  167. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  168. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  169. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +7 -0
  170. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-display.ts +9 -0
  171. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +8 -16
  172. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +5 -25
  173. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.test.ts +36 -1
  174. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.ts +51 -2
  175. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  176. package/src/components/ui/tu-do/boards/workspace-projects-client-page.tsx +13 -3
  177. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  178. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  179. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  180. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  181. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  182. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  183. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +25 -0
  184. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
  185. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
  186. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +237 -3
  187. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
  188. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  189. package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
  190. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  191. package/src/components/ui/tu-do/shared/board-header.tsx +465 -937
  192. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  193. package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
  194. package/src/components/ui/tu-do/shared/board-views.tsx +596 -82
  195. package/src/components/ui/tu-do/shared/cursor-overlay-multi-wrapper.tsx +53 -12
  196. package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
  197. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  198. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  199. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  200. package/src/components/ui/tu-do/shared/task-dialog-presentation.test.ts +53 -0
  201. package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +19 -0
  202. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +57 -0
  203. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +136 -111
  204. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -1
  205. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  206. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  207. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  208. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  209. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  210. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +44 -15
  211. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  212. package/src/declarations.d.ts +1 -0
  213. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  214. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  215. package/src/hooks/__tests__/useBoardRealtime.test.tsx +2 -2
  216. package/src/hooks/__tests__/useCursorTracking.test.tsx +212 -0
  217. package/src/hooks/use-calendar-sync.tsx +247 -243
  218. package/src/hooks/use-calendar.tsx +323 -138
  219. package/src/hooks/use-task-actions.ts +24 -0
  220. package/src/hooks/use-user-workspace-config.ts +75 -0
  221. package/src/hooks/use-workspace-currency.ts +8 -3
  222. package/src/hooks/useBoardRealtime.ts +6 -3
  223. package/src/hooks/useBoardRealtime.types.ts +11 -0
  224. package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
  225. package/src/hooks/useCursorTracking.ts +91 -27
  226. package/src/hooks/useTaskUserRealtime.ts +5 -3
@@ -3,8 +3,11 @@ import {
3
3
  type CalendarSourceOption,
4
4
  getGoogleCalendarAuthUrl,
5
5
  getWorkspaceCalendarDefaultSource,
6
+ getWorkspaceCalendarSyncPreferences,
7
+ updateCalendarConnection as updateCalendarConnectionRequest,
6
8
  updateWorkspaceCalendarDefaultSource,
7
- } from '@tuturuuu/internal-api';
9
+ updateWorkspaceCalendarSyncPreferences,
10
+ } from '@tuturuuu/internal-api/calendar';
8
11
  import { createClient } from '@tuturuuu/supabase/next/client';
9
12
  import { useRouter } from 'next/navigation';
10
13
  import { useTranslations } from 'next-intl';
@@ -184,6 +187,12 @@ export function useCalendarConnectionsManager(wsId: string) {
184
187
  staleTime: 30_000,
185
188
  });
186
189
 
190
+ const { data: syncPreferencesData } = useQuery({
191
+ queryKey: ['calendar-sync-preferences', wsId],
192
+ queryFn: () => getWorkspaceCalendarSyncPreferences(wsId),
193
+ staleTime: 30_000,
194
+ });
195
+
187
196
  const defaultSourceMutation = useMutation({
188
197
  mutationFn: async (sourceId: string) => {
189
198
  const option = defaultSourceData?.options.find(
@@ -209,6 +218,46 @@ export function useCalendarConnectionsManager(wsId: string) {
209
218
  },
210
219
  });
211
220
 
221
+ const syncPreferencesMutation = useMutation({
222
+ mutationFn: (
223
+ payload: Parameters<typeof updateWorkspaceCalendarSyncPreferences>[1]
224
+ ) => updateWorkspaceCalendarSyncPreferences(wsId, payload),
225
+ onSuccess: () => {
226
+ queryClient.invalidateQueries({
227
+ queryKey: ['calendar-sync-preferences', wsId],
228
+ });
229
+ toast.success(
230
+ t('calendar_sync_settings_updated') || 'Calendar sync settings updated'
231
+ );
232
+ },
233
+ onError: (error: Error) => {
234
+ toast.error(error.message || 'Failed to update calendar sync settings');
235
+ },
236
+ });
237
+
238
+ const updateConnectionSyncSettingsMutation = useMutation({
239
+ mutationFn: (
240
+ payload: Parameters<typeof updateCalendarConnectionRequest>[0]
241
+ ) => updateCalendarConnectionRequest(payload),
242
+ onSuccess: () => {
243
+ queryClient.invalidateQueries({
244
+ queryKey: ['calendar-connections', wsId],
245
+ });
246
+ queryClient.invalidateQueries({
247
+ queryKey: ['provider-calendar-list', wsId],
248
+ });
249
+ queryClient.invalidateQueries({
250
+ queryKey: ['calendar-sync-preferences', wsId],
251
+ });
252
+ toast.success(
253
+ t('calendar_sync_settings_updated') || 'Calendar sync settings updated'
254
+ );
255
+ },
256
+ onError: (error: Error) => {
257
+ toast.error(error.message || 'Failed to update calendar sync settings');
258
+ },
259
+ });
260
+
212
261
  // Fetch current user's email
213
262
  const { data: userEmail } = useQuery({
214
263
  queryKey: ['current-user-email'],
@@ -621,7 +670,13 @@ export function useCalendarConnectionsManager(wsId: string) {
621
670
  const existingConnection = calendarConnections.find(
622
671
  (conn) =>
623
672
  conn.calendar_id === apiCal.id && conn.auth_token_id === account.id
624
- );
673
+ ) as
674
+ | ((typeof calendarConnections)[number] & {
675
+ sync_delete_enabled?: boolean | null;
676
+ sync_inbound_enabled?: boolean | null;
677
+ sync_outbound_enabled?: boolean | null;
678
+ })
679
+ | undefined;
625
680
 
626
681
  return {
627
682
  id: existingConnection?.id || apiCal.id,
@@ -634,6 +689,10 @@ export function useCalendarConnectionsManager(wsId: string) {
634
689
  connectionExists: !!existingConnection,
635
690
  accountId: account.id,
636
691
  accessRole: apiCal.accessRole,
692
+ syncDeleteEnabled: existingConnection?.sync_delete_enabled ?? true,
693
+ syncInboundEnabled: existingConnection?.sync_inbound_enabled ?? true,
694
+ syncOutboundEnabled:
695
+ existingConnection?.sync_outbound_enabled ?? false,
637
696
  };
638
697
  });
639
698
 
@@ -652,6 +711,9 @@ export function useCalendarConnectionsManager(wsId: string) {
652
711
  connectionExists: boolean;
653
712
  accountId: string;
654
713
  accessRole: string;
714
+ syncDeleteEnabled: boolean;
715
+ syncInboundEnabled: boolean;
716
+ syncOutboundEnabled: boolean;
655
717
  }>
656
718
  >
657
719
  );
@@ -688,6 +750,8 @@ export function useCalendarConnectionsManager(wsId: string) {
688
750
  showCreateCalendarDialog,
689
751
  syncHealth,
690
752
  syncMutation,
753
+ syncPreferencesData,
754
+ syncPreferencesMutation,
691
755
  syncStatusStyles,
692
756
  syncToTuturuuu,
693
757
  systemCalendars,
@@ -696,6 +760,7 @@ export function useCalendarConnectionsManager(wsId: string) {
696
760
  togglingTuturuuuIds,
697
761
  toggleAccountExpanded,
698
762
  toggleWorkspaceCalendarMutation,
763
+ updateConnectionSyncSettingsMutation,
699
764
  tuturuuuEnabledCount,
700
765
  userEmail,
701
766
  workspaceCalendars,
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { ChevronLeft, ChevronRight } from '@tuturuuu/icons';
3
+ import { ChevronLeft, ChevronRight } from '@tuturuuu/icons/lucide-static';
4
4
  import { cn } from '@tuturuuu/utils/format';
5
5
  import { format } from 'date-fns';
6
6
  import dayjs from 'dayjs';
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { ArrowLeft, ArrowRight } from '@tuturuuu/icons';
3
+ import { ArrowLeft, ArrowRight } from '@tuturuuu/icons/lucide-static';
4
4
  import { cn } from '@tuturuuu/utils/format';
5
5
  import useEmblaCarousel, {
6
6
  type UseEmblaCarouselType,
@@ -18,7 +18,7 @@ vi.mock('next-intl', () => ({
18
18
  values?.count === undefined ? key : `${key}:${values.count}`,
19
19
  }));
20
20
 
21
- vi.mock('@tuturuuu/internal-api/infrastructure', () => ({
21
+ vi.mock('@tuturuuu/internal-api/infrastructure/ai', () => ({
22
22
  draftAiAgentExternalResponse: (...args: unknown[]) =>
23
23
  mocks.draftAiAgentExternalResponse(...args),
24
24
  listAiAgentExternalThreads: (...args: unknown[]) =>
@@ -13,7 +13,7 @@ import {
13
13
  listAiAgentExternalThreads,
14
14
  sendAiAgentExternalResponse,
15
15
  syncAiAgentExternalThread,
16
- } from '@tuturuuu/internal-api/infrastructure';
16
+ } from '@tuturuuu/internal-api/infrastructure/ai';
17
17
  import { useTranslations } from 'next-intl';
18
18
  import { useState } from 'react';
19
19
  import { Button } from '../button';
@@ -16,7 +16,7 @@ vi.mock('next-intl', () => ({
16
16
  useTranslations: () => (key: string) => key,
17
17
  }));
18
18
 
19
- vi.mock('@tuturuuu/internal-api/infrastructure', () => ({
19
+ vi.mock('@tuturuuu/internal-api/infrastructure/ai', () => ({
20
20
  abortAiAgentZaloPersonalQrLogin: (
21
21
  ...args: Parameters<typeof mocks.abortAiAgentZaloPersonalQrLogin>
22
22
  ) => mocks.abortAiAgentZaloPersonalQrLogin(...args),
@@ -14,7 +14,7 @@ import {
14
14
  import type {
15
15
  AiAgentChannelConfig,
16
16
  AiAgentTestResponse,
17
- } from '@tuturuuu/internal-api/infrastructure';
17
+ } from '@tuturuuu/internal-api/infrastructure/ai';
18
18
  import { useTranslations } from 'next-intl';
19
19
  import { useState } from 'react';
20
20
  import { Button } from '../button';
@@ -14,7 +14,7 @@ import type {
14
14
  AiAgentChannelConfig,
15
15
  AiAgentDefinition,
16
16
  SaveAiAgentPayload,
17
- } from '@tuturuuu/internal-api/infrastructure';
17
+ } from '@tuturuuu/internal-api/infrastructure/ai';
18
18
  import { useTranslations } from 'next-intl';
19
19
  import { useState } from 'react';
20
20
  import { Badge } from '../badge';
@@ -20,7 +20,7 @@ vi.mock('next-intl', () => ({
20
20
  useTranslations: () => (key: string) => key,
21
21
  }));
22
22
 
23
- vi.mock('@tuturuuu/internal-api/infrastructure', () => ({
23
+ vi.mock('@tuturuuu/internal-api/infrastructure/ai', () => ({
24
24
  abortAiAgentZaloPersonalQrLogin: vi.fn(),
25
25
  deployAiAgentChannel: vi.fn(),
26
26
  getAiAgentZaloPersonalQrLoginStatus: vi.fn(),
@@ -6,7 +6,7 @@ import type { ChatConversation } from '@tuturuuu/internal-api';
6
6
  import type {
7
7
  AiAgentTestResponse,
8
8
  SaveAiAgentPayload,
9
- } from '@tuturuuu/internal-api/infrastructure';
9
+ } from '@tuturuuu/internal-api/infrastructure/ai';
10
10
  import {
11
11
  deployAiAgentChannel,
12
12
  listAiAgents,
@@ -14,7 +14,7 @@ import {
14
14
  rotateAiAgentChannelSecret,
15
15
  saveAiAgent,
16
16
  testAiAgentChannel,
17
- } from '@tuturuuu/internal-api/infrastructure';
17
+ } from '@tuturuuu/internal-api/infrastructure/ai';
18
18
  import { useTranslations } from 'next-intl';
19
19
  import { useMemo, useState } from 'react';
20
20
  import { Badge } from '../badge';
@@ -1,5 +1,5 @@
1
1
  import type { ChatConversation } from '@tuturuuu/internal-api';
2
- import type { AiAgentDefinition } from '@tuturuuu/internal-api/infrastructure';
2
+ import type { AiAgentDefinition } from '@tuturuuu/internal-api/infrastructure/ai';
3
3
  import { ROOT_WORKSPACE_ID } from '@tuturuuu/utils/constants';
4
4
  import { describe, expect, it } from 'vitest';
5
5
  import {
@@ -6,7 +6,7 @@ import type {
6
6
  AiAgentChannelConfig,
7
7
  AiAgentDefinition,
8
8
  SaveAiAgentPayload,
9
- } from '@tuturuuu/internal-api/infrastructure';
9
+ } from '@tuturuuu/internal-api/infrastructure/ai';
10
10
  import { cn } from '@tuturuuu/utils/format';
11
11
  import { useTranslations } from 'next-intl';
12
12
  import type { ReactNode } from 'react';
@@ -19,14 +19,14 @@ import type {
19
19
  AiAgentZaloPersonalPhoneSyncResult,
20
20
  AiAgentZaloPersonalQrLoginSession,
21
21
  AiAgentZaloPersonalQrLoginStatus,
22
- } from '@tuturuuu/internal-api/infrastructure';
22
+ } from '@tuturuuu/internal-api/infrastructure/ai';
23
23
  import {
24
24
  abortAiAgentZaloPersonalQrLogin,
25
25
  getAiAgentZaloPersonalQrLoginStatus,
26
26
  getAiAgentZaloPersonalStatus,
27
27
  runAiAgentZaloPersonalAction,
28
28
  startAiAgentZaloPersonalQrLogin,
29
- } from '@tuturuuu/internal-api/infrastructure';
29
+ } from '@tuturuuu/internal-api/infrastructure/ai';
30
30
  import Image from 'next/image';
31
31
  import { useTranslations } from 'next-intl';
32
32
  import type { ReactNode } from 'react';
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
4
- import { CheckIcon, MinusIcon } from '@tuturuuu/icons';
4
+ import { CheckIcon, MinusIcon } from '@tuturuuu/icons/lucide-static';
5
5
  import { cn } from '@tuturuuu/utils/format';
6
6
  import type * as React from 'react';
7
7
 
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { TextIcon } from '@tuturuuu/icons';
3
+ import { TextIcon } from '@tuturuuu/icons/lucide-static';
4
4
  import { forwardRef, useMemo, useState } from 'react';
5
5
  import { HexColorPicker } from 'react-colorful';
6
6
  import { useDomResolvedTheme } from '../../hooks/use-dom-resolved-theme';
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { SearchIcon } from '@tuturuuu/icons';
3
+ import { SearchIcon } from '@tuturuuu/icons/lucide-static';
4
4
  import { cn } from '@tuturuuu/utils/format';
5
5
  import { Command as CommandPrimitive } from 'cmdk';
6
6
  import type * as React from 'react';
@@ -1,7 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
4
- import { CheckIcon, ChevronRightIcon, CircleIcon } from '@tuturuuu/icons';
4
+ import {
5
+ CheckIcon,
6
+ ChevronRightIcon,
7
+ CircleIcon,
8
+ } from '@tuturuuu/icons/lucide-static';
5
9
  import { cn } from '@tuturuuu/utils/format';
6
10
  import type * as React from 'react';
7
11
 
@@ -0,0 +1,43 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { CurrencyInput } from './currency-input';
4
+
5
+ describe('CurrencyInput', () => {
6
+ it('keeps locale decimal separators while editing', () => {
7
+ const onChange = vi.fn();
8
+
9
+ render(
10
+ <CurrencyInput
11
+ aria-label="Amount"
12
+ hideHelpers
13
+ locale="vi-VN"
14
+ maximumFractionDigits={2}
15
+ onChange={onChange}
16
+ value={undefined}
17
+ />
18
+ );
19
+
20
+ const input = screen.getByLabelText('Amount') as HTMLInputElement;
21
+
22
+ fireEvent.focus(input);
23
+ fireEvent.change(input, {
24
+ target: {
25
+ selectionStart: 3,
26
+ value: '1,2',
27
+ },
28
+ });
29
+
30
+ expect(input).toHaveValue('1,2');
31
+ expect(onChange).toHaveBeenLastCalledWith(1.2);
32
+
33
+ fireEvent.change(input, {
34
+ target: {
35
+ selectionStart: input.value.length + 1,
36
+ value: `${input.value}3`,
37
+ },
38
+ });
39
+
40
+ expect(input).toHaveValue('1,23');
41
+ expect(onChange).toHaveBeenLastCalledWith(1.23);
42
+ });
43
+ });
@@ -197,7 +197,7 @@ export const CurrencyInput = forwardRef<HTMLInputElement, CurrencyInputProps>(
197
197
  // Build final formatted string
198
198
  let formatted = formattedInteger;
199
199
  if (hasDecimal) {
200
- formatted += `.${decimalPart}`;
200
+ formatted += `${localeSeparators.decimal}${decimalPart}`;
201
201
  }
202
202
 
203
203
  // Calculate new cursor position
@@ -92,10 +92,13 @@ describe('SettingsDialogShell keyboard navigation', () => {
92
92
  renderShell();
93
93
 
94
94
  const dialog = screen.getByRole('dialog');
95
+ const className = dialog.getAttribute('class') ?? '';
95
96
 
96
97
  expect(dialog).toHaveClass('h-dvh');
97
98
  expect(dialog).toHaveClass('w-screen');
99
+ expect(dialog).toHaveClass('bg-background');
98
100
  expect(dialog).toHaveClass('rounded-none');
101
+ expect(className).not.toMatch(/animate-in|fade-in|zoom-in|slide-in/);
99
102
  expect(
100
103
  screen.getAllByRole('button', { name: 'settings.back_to_app' }).length
101
104
  ).toBeGreaterThan(0);
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
1
3
  import type { InternalApiWorkspaceSummary } from '@tuturuuu/types';
2
4
  import { describe, expect, it } from 'vitest';
3
5
  import { mergeWorkspaceSelectWorkspaces } from '../workspace-select-helpers';
@@ -36,4 +38,21 @@ describe('mergeWorkspaceSelectWorkspaces', () => {
36
38
  workspace,
37
39
  ]);
38
40
  });
41
+
42
+ it('keeps workspace fallback images outside Radix AvatarImage context', () => {
43
+ const workspaceSelectSource = readFileSync(
44
+ join(process.cwd(), 'src/components/ui/custom/workspace-select.tsx'),
45
+ 'utf8'
46
+ );
47
+ const workspaceIconSource = workspaceSelectSource.slice(
48
+ workspaceSelectSource.indexOf('function WorkspaceIcon'),
49
+ workspaceSelectSource.indexOf('export function WorkspaceSelect')
50
+ );
51
+
52
+ expect(workspaceIconSource).toContain('<AvatarFallback');
53
+ expect(workspaceIconSource).toContain('<Image');
54
+ expect(workspaceIconSource).not.toMatch(
55
+ /<AvatarFallback[\s\S]*<AvatarImage/u
56
+ );
57
+ });
39
58
  });
@@ -138,4 +138,199 @@ describe('Combobox', () => {
138
138
 
139
139
  expect(onSelect).toHaveBeenCalledTimes(1);
140
140
  });
141
+
142
+ it('can hide the selected trigger icon while keeping option icons visible', () => {
143
+ render(
144
+ <Combobox
145
+ options={[
146
+ {
147
+ label: 'Alpha',
148
+ value: 'alpha',
149
+ icon: <span data-testid="alpha-option-icon">A</span>,
150
+ },
151
+ { label: 'Beta', value: 'beta' },
152
+ ]}
153
+ placeholder="Pick item"
154
+ selected="alpha"
155
+ showSelectedIcon={false}
156
+ />
157
+ );
158
+
159
+ expect(screen.queryByTestId('alpha-option-icon')).not.toBeInTheDocument();
160
+
161
+ openCombobox();
162
+
163
+ expect(screen.getByTestId('alpha-option-icon')).toBeInTheDocument();
164
+ });
165
+
166
+ it('can use a stable trigger icon while preserving option row icons', () => {
167
+ render(
168
+ <Combobox
169
+ ariaLabel="View picker"
170
+ hideTriggerLabel
171
+ options={[
172
+ {
173
+ label: 'Kanban',
174
+ value: 'kanban',
175
+ icon: <span data-testid="kanban-option-icon">K</span>,
176
+ },
177
+ ]}
178
+ placeholder="Pick view"
179
+ selected="kanban"
180
+ showChevron={false}
181
+ triggerIcon={<span data-testid="stable-view-icon">V</span>}
182
+ triggerMode="compact"
183
+ />
184
+ );
185
+
186
+ expect(screen.getByTestId('stable-view-icon')).toBeInTheDocument();
187
+ expect(screen.queryByTestId('kanban-option-icon')).not.toBeInTheDocument();
188
+
189
+ openCombobox();
190
+
191
+ expect(screen.getByTestId('kanban-option-icon')).toBeInTheDocument();
192
+ });
193
+
194
+ it('supports compact accessible icon-only triggers with wider popover content', () => {
195
+ render(
196
+ <Combobox
197
+ ariaLabel="Compact picker"
198
+ contentWidth="md"
199
+ hideTriggerLabel
200
+ label={<span>Alpha compact</span>}
201
+ options={[
202
+ {
203
+ label: 'Alpha',
204
+ value: 'alpha',
205
+ icon: <span data-testid="alpha-compact-icon">A</span>,
206
+ },
207
+ ]}
208
+ placeholder="Pick item"
209
+ selected="alpha"
210
+ showChevron={false}
211
+ triggerMode="compact"
212
+ />
213
+ );
214
+
215
+ const trigger = screen.getByRole('combobox', { name: 'Compact picker' });
216
+ expect(trigger).toHaveClass('aspect-square', 'px-0');
217
+ expect(screen.queryByText('Alpha compact')).not.toBeInTheDocument();
218
+ expect(screen.getByTestId('alpha-compact-icon')).toBeInTheDocument();
219
+
220
+ fireEvent.click(trigger);
221
+
222
+ expect(document.querySelector('[data-slot="popover-content"]')).toHaveClass(
223
+ 'w-80'
224
+ );
225
+ });
226
+
227
+ it('shows trigger tooltips instantly for compact controls', async () => {
228
+ render(
229
+ <Combobox
230
+ ariaLabel="Compact picker"
231
+ hideTriggerLabel
232
+ options={options}
233
+ placeholder="Pick item"
234
+ selected="alpha"
235
+ showChevron={false}
236
+ triggerMode="compact"
237
+ triggerTooltip="Current: Alpha"
238
+ />
239
+ );
240
+
241
+ const trigger = screen.getByRole('combobox', { name: 'Compact picker' });
242
+ fireEvent.focus(trigger);
243
+
244
+ await waitFor(() => {
245
+ expect(trigger).toHaveAttribute('data-state', 'instant-open');
246
+ });
247
+ expect(screen.getAllByText('Current: Alpha').length).toBeGreaterThan(0);
248
+ });
249
+
250
+ it('can keep compact trigger icons monochrome while option rows keep semantic colors', () => {
251
+ render(
252
+ <Combobox
253
+ ariaLabel="Priority picker"
254
+ colorizeTriggerIcon={false}
255
+ hideTriggerLabel
256
+ options={[
257
+ {
258
+ color: '#f97316',
259
+ icon: <span data-testid="priority-icon">P</span>,
260
+ label: 'High priority',
261
+ value: 'high',
262
+ },
263
+ ]}
264
+ placeholder="Pick item"
265
+ selected="high"
266
+ showChevron={false}
267
+ triggerMode="compact"
268
+ />
269
+ );
270
+
271
+ const triggerIcon = screen.getByTestId('priority-icon');
272
+ expect(triggerIcon).not.toHaveAttribute('style');
273
+
274
+ openCombobox();
275
+
276
+ const [, optionIcon] = screen.getAllByTestId('priority-icon');
277
+ expect(optionIcon).toHaveStyle({ color: '#f97316' });
278
+ });
279
+
280
+ it('reports popover open changes when the trigger opens and a single option closes it', async () => {
281
+ const onChange = vi.fn();
282
+ const onOpenChange = vi.fn();
283
+
284
+ render(
285
+ <Combobox
286
+ onChange={onChange}
287
+ onOpenChange={onOpenChange}
288
+ options={options}
289
+ placeholder="Pick item"
290
+ selected=""
291
+ />
292
+ );
293
+
294
+ openCombobox();
295
+
296
+ expect(onOpenChange).toHaveBeenCalledWith(true);
297
+
298
+ fireEvent.click(screen.getByText('Beta'));
299
+
300
+ await waitFor(() => {
301
+ expect(onChange).toHaveBeenCalledWith('beta');
302
+ expect(onOpenChange).toHaveBeenCalledWith(false);
303
+ });
304
+ });
305
+
306
+ it('renders grouped options and non-clipped descriptions', () => {
307
+ render(
308
+ <Combobox
309
+ options={[
310
+ {
311
+ description: 'Detailed board view with enough room to wrap.',
312
+ group: 'Views',
313
+ label: 'Kanban board',
314
+ value: 'kanban',
315
+ },
316
+ {
317
+ description: 'Sort by due date from earliest to latest.',
318
+ group: 'Sorting',
319
+ label: 'Soonest first',
320
+ value: 'due-date-asc',
321
+ },
322
+ ]}
323
+ placeholder="Pick item"
324
+ selected="kanban"
325
+ />
326
+ );
327
+
328
+ openCombobox();
329
+
330
+ expect(screen.getByText('Views')).toBeInTheDocument();
331
+ expect(screen.getByText('Sorting')).toBeInTheDocument();
332
+ expect(
333
+ screen.getByText('Detailed board view with enough room to wrap.')
334
+ ).toHaveClass('whitespace-normal', 'break-words');
335
+ });
141
336
  });