@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
@@ -13,7 +13,6 @@ import {
13
13
  Heading1,
14
14
  Heading2,
15
15
  Heading3,
16
- Highlighter,
17
16
  ImageIcon,
18
17
  Italic,
19
18
  Link,
@@ -37,6 +36,7 @@ import { Toggle } from '@tuturuuu/ui/toggle';
37
36
  import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip';
38
37
  import { cn } from '@tuturuuu/utils/format';
39
38
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
39
+ import { TextEditorColorControls } from './color-controls';
40
40
  import {
41
41
  MAX_IMAGE_SIZE,
42
42
  MAX_VIDEO_SIZE,
@@ -65,7 +65,6 @@ const HOTKEYS = {
65
65
  'ordered-list': 'Mod+Shift+7',
66
66
  'task-list': 'Mod+Shift+9',
67
67
  table: '',
68
- highlight: 'Mod+Shift+H',
69
68
  link: 'Mod+K',
70
69
  image: '',
71
70
  video: '',
@@ -90,7 +89,6 @@ const LABELS: Record<string, string> = {
90
89
  'ordered-list': 'Ordered List',
91
90
  'task-list': 'Task List',
92
91
  table: 'Insert Table',
93
- highlight: 'Highlight',
94
92
  link: 'Link',
95
93
  image: 'Upload Image',
96
94
  video: 'Upload Video',
@@ -104,7 +102,7 @@ const TOOLBAR_GROUPS = [
104
102
  ['bold', 'italic', 'strike', 'subscript', 'superscript'],
105
103
  ['align-left', 'align-center', 'align-right'],
106
104
  ['bullet-list', 'ordered-list', 'task-list'],
107
- ['table', 'highlight', 'link'],
105
+ ['table', 'link'],
108
106
  ] as const;
109
107
 
110
108
  /** Format a hotkey combo string for display (platform-aware). */
@@ -371,12 +369,6 @@ export function ToolBar({
371
369
  .run(),
372
370
  pressed: editor?.isActive('table'),
373
371
  },
374
- {
375
- key: 'highlight',
376
- icon: <Highlighter className="size-4" />,
377
- onClick: () => editor?.chain().focus().toggleHighlight().run(),
378
- pressed: editor?.isActive('highlight'),
379
- },
380
372
  ] as const,
381
373
  [editor]
382
374
  );
@@ -601,6 +593,10 @@ export function ToolBar({
601
593
  />
602
594
  ))}
603
595
 
596
+ {/* Color controls */}
597
+ <ToolbarSeparator />
598
+ <TextEditorColorControls editor={editor} />
599
+
604
600
  {/* Link */}
605
601
  <ToolbarSeparator />
606
602
  <ToolbarButton
@@ -1098,12 +1094,6 @@ export function FixedToolbar({
1098
1094
  .run(),
1099
1095
  pressed: editor.isActive('table'),
1100
1096
  },
1101
- {
1102
- key: 'highlight',
1103
- icon: <Highlighter className="size-4" />,
1104
- onClick: () => editor.chain().focus().toggleHighlight().run(),
1105
- pressed: editor.isActive('highlight'),
1106
- },
1107
1097
  {
1108
1098
  key: 'link',
1109
1099
  icon: <Link className="size-4" />,
@@ -1244,6 +1234,9 @@ export function FixedToolbar({
1244
1234
  </div>
1245
1235
  ))}
1246
1236
 
1237
+ <ToolbarSeparator />
1238
+ <TextEditorColorControls editor={editor} />
1239
+
1247
1240
  {/* Media group */}
1248
1241
  {workspaceId && onImageUpload && (
1249
1242
  <>
@@ -29,6 +29,15 @@ const VIDEO_INPUT_REGEX = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/;
29
29
 
30
30
  interface VideoOptions {
31
31
  onVideoUpload?: (file: File) => Promise<string>;
32
+ getOnVideoUpload?: () => ((file: File) => Promise<string>) | undefined;
33
+ }
34
+
35
+ function resolveVideoUploadHandler(options: VideoOptions) {
36
+ if (options.getOnVideoUpload) {
37
+ return options.getOnVideoUpload();
38
+ }
39
+
40
+ return options.onVideoUpload;
32
41
  }
33
42
 
34
43
  /**
@@ -114,8 +123,6 @@ export const Video = (options: VideoOptions = {}) =>
114
123
  },
115
124
 
116
125
  addProseMirrorPlugins() {
117
- const { onVideoUpload } = options;
118
-
119
126
  return [
120
127
  // Video upload placeholder plugin - manages loading state decorations
121
128
  new Plugin({
@@ -166,6 +173,7 @@ export const Video = (options: VideoOptions = {}) =>
166
173
  props: {
167
174
  handleDOMEvents: {
168
175
  paste: (view, event: ClipboardEvent) => {
176
+ const onVideoUpload = resolveVideoUploadHandler(options);
169
177
  if (!onVideoUpload) return false;
170
178
 
171
179
  const items = event.clipboardData?.items;
@@ -303,6 +311,7 @@ export const Video = (options: VideoOptions = {}) =>
303
311
  props: {
304
312
  handleDOMEvents: {
305
313
  drop(view, event) {
314
+ const onVideoUpload = resolveVideoUploadHandler(options);
306
315
  if (!onVideoUpload) return false;
307
316
 
308
317
  const { schema } = view.state;
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import * as ToastPrimitives from '@radix-ui/react-toast';
4
- import { X } from '@tuturuuu/icons';
4
+ import { X } from '@tuturuuu/icons/lucide-static';
5
5
  import { cn } from '@tuturuuu/utils/format';
6
6
  import { cva, type VariantProps } from 'class-variance-authority';
7
7
  import * as React from 'react';
@@ -0,0 +1,270 @@
1
+ import '@testing-library/jest-dom';
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
4
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { BoardShareDialog } from '../board-share-dialog';
6
+
7
+ const createWorkspaceTaskBoardShareMock = vi.fn();
8
+ const deleteWorkspaceTaskBoardShareMock = vi.fn();
9
+ const disableWorkspaceTaskBoardPublicLinkMock = vi.fn();
10
+ const enableWorkspaceTaskBoardPublicLinkMock = vi.fn();
11
+ const getWorkspaceTaskBoardPublicLinkMock = vi.fn();
12
+ const listWorkspaceTaskBoardSharesMock = vi.fn();
13
+ const listWorkspaceTaskBoardViewableMembersMock = vi.fn();
14
+ const updateWorkspaceTaskBoardShareMock = vi.fn();
15
+
16
+ vi.mock('next-intl', () => ({
17
+ useLocale: () => 'en',
18
+ useTranslations: () => (key: string) => key,
19
+ }));
20
+
21
+ vi.mock('@tuturuuu/ui/custom/combobox', () => ({
22
+ Combobox: ({
23
+ disabled,
24
+ onChange,
25
+ options,
26
+ placeholder,
27
+ selected,
28
+ }: {
29
+ disabled?: boolean;
30
+ onChange?: (value: string) => void;
31
+ options: { label: string; value: string }[];
32
+ placeholder?: string;
33
+ selected: string;
34
+ }) => (
35
+ <select
36
+ aria-label={placeholder}
37
+ disabled={disabled}
38
+ value={selected}
39
+ onChange={(event) => onChange?.(event.target.value)}
40
+ >
41
+ {options.map((option) => (
42
+ <option key={option.value} value={option.value}>
43
+ {option.label}
44
+ </option>
45
+ ))}
46
+ </select>
47
+ ),
48
+ }));
49
+
50
+ vi.mock('@tuturuuu/internal-api/tasks', () => ({
51
+ createWorkspaceTaskBoardShare: (
52
+ ...args: Parameters<typeof createWorkspaceTaskBoardShareMock>
53
+ ) => createWorkspaceTaskBoardShareMock(...args),
54
+ deleteWorkspaceTaskBoardShare: (
55
+ ...args: Parameters<typeof deleteWorkspaceTaskBoardShareMock>
56
+ ) => deleteWorkspaceTaskBoardShareMock(...args),
57
+ disableWorkspaceTaskBoardPublicLink: (
58
+ ...args: Parameters<typeof disableWorkspaceTaskBoardPublicLinkMock>
59
+ ) => disableWorkspaceTaskBoardPublicLinkMock(...args),
60
+ enableWorkspaceTaskBoardPublicLink: (
61
+ ...args: Parameters<typeof enableWorkspaceTaskBoardPublicLinkMock>
62
+ ) => enableWorkspaceTaskBoardPublicLinkMock(...args),
63
+ getWorkspaceTaskBoardPublicLink: (
64
+ ...args: Parameters<typeof getWorkspaceTaskBoardPublicLinkMock>
65
+ ) => getWorkspaceTaskBoardPublicLinkMock(...args),
66
+ listWorkspaceTaskBoardShares: (
67
+ ...args: Parameters<typeof listWorkspaceTaskBoardSharesMock>
68
+ ) => listWorkspaceTaskBoardSharesMock(...args),
69
+ listWorkspaceTaskBoardViewableMembers: (
70
+ ...args: Parameters<typeof listWorkspaceTaskBoardViewableMembersMock>
71
+ ) => listWorkspaceTaskBoardViewableMembersMock(...args),
72
+ updateWorkspaceTaskBoardShare: (
73
+ ...args: Parameters<typeof updateWorkspaceTaskBoardShareMock>
74
+ ) => updateWorkspaceTaskBoardShareMock(...args),
75
+ }));
76
+
77
+ vi.mock('sonner', () => ({
78
+ toast: {
79
+ error: vi.fn(),
80
+ success: vi.fn(),
81
+ },
82
+ }));
83
+
84
+ function renderBoardShareDialog() {
85
+ const queryClient = new QueryClient({
86
+ defaultOptions: {
87
+ queries: {
88
+ retry: false,
89
+ },
90
+ },
91
+ });
92
+
93
+ return render(
94
+ <QueryClientProvider client={queryClient}>
95
+ <BoardShareDialog
96
+ board={{ id: 'board-1', name: 'Tasks' }}
97
+ onOpenChange={vi.fn()}
98
+ open
99
+ wsId="ws-1"
100
+ />
101
+ </QueryClientProvider>
102
+ );
103
+ }
104
+
105
+ describe('BoardShareDialog', () => {
106
+ beforeEach(() => {
107
+ vi.clearAllMocks();
108
+ getWorkspaceTaskBoardPublicLinkMock.mockResolvedValue({ publicLink: null });
109
+ enableWorkspaceTaskBoardPublicLinkMock.mockResolvedValue({
110
+ publicLink: { code: 'abc123' },
111
+ });
112
+ disableWorkspaceTaskBoardPublicLinkMock.mockResolvedValue({
113
+ publicLink: null,
114
+ });
115
+ createWorkspaceTaskBoardShareMock.mockResolvedValue({ share: null });
116
+ updateWorkspaceTaskBoardShareMock.mockResolvedValue({ share: null });
117
+ deleteWorkspaceTaskBoardShareMock.mockResolvedValue({ ok: true });
118
+ listWorkspaceTaskBoardSharesMock.mockResolvedValue({ shares: [] });
119
+ listWorkspaceTaskBoardViewableMembersMock.mockResolvedValue({
120
+ members: [
121
+ {
122
+ avatar_url: null,
123
+ display_name: 'Project Manager',
124
+ email: 'pm@example.com',
125
+ handle: null,
126
+ id: 'user-1',
127
+ is_creator: false,
128
+ roles: [{ id: 'role-1', name: 'Project manager' }],
129
+ user_id: 'user-1',
130
+ workspace_member_type: 'MEMBER',
131
+ },
132
+ ],
133
+ });
134
+ });
135
+
136
+ it('starts compact with all sections collapsed and tooltip copy hidden', async () => {
137
+ renderBoardShareDialog();
138
+
139
+ for (const title of [
140
+ 'ws-task-boards.share.public.title',
141
+ 'ws-task-boards.share.workspace_members.title',
142
+ 'ws-task-boards.share.guests.title',
143
+ ]) {
144
+ expect(
145
+ screen.getByRole('button', { name: new RegExp(title) })
146
+ ).toHaveAttribute('aria-expanded', 'false');
147
+ }
148
+
149
+ expect(
150
+ screen.queryByText('ws-task-boards.share.public.description')
151
+ ).not.toBeInTheDocument();
152
+ expect(
153
+ screen.queryByText('ws-task-boards.share.public.tooltip')
154
+ ).not.toBeInTheDocument();
155
+ expect(
156
+ screen.queryByText('ws-task-boards.share.workspace_members.description')
157
+ ).not.toBeInTheDocument();
158
+ expect(
159
+ screen.queryByText('ws-task-boards.share.guests.description')
160
+ ).not.toBeInTheDocument();
161
+ await waitFor(() => {
162
+ expect(getWorkspaceTaskBoardPublicLinkMock).toHaveBeenCalledWith(
163
+ 'ws-1',
164
+ 'board-1'
165
+ );
166
+ });
167
+ expect(listWorkspaceTaskBoardViewableMembersMock).not.toHaveBeenCalled();
168
+
169
+ expect(await screen.findByText('common.disabled')).toBeInTheDocument();
170
+ expect(screen.getByText('common.workspace')).toBeInTheDocument();
171
+ expect(screen.getByText('common.none')).toBeInTheDocument();
172
+ expect(
173
+ screen.queryByRole('button', {
174
+ name: /ws-task-boards.share.shared_with/,
175
+ })
176
+ ).not.toBeInTheDocument();
177
+ });
178
+
179
+ it('fetches viewable members only when the workspace section opens', async () => {
180
+ renderBoardShareDialog();
181
+
182
+ fireEvent.click(
183
+ screen.getByRole('button', {
184
+ name: /ws-task-boards.share.workspace_members.title/,
185
+ })
186
+ );
187
+
188
+ await waitFor(() => {
189
+ expect(listWorkspaceTaskBoardViewableMembersMock).toHaveBeenCalledWith(
190
+ 'ws-1',
191
+ 'board-1'
192
+ );
193
+ });
194
+ expect(await screen.findByText('Project Manager')).toBeInTheDocument();
195
+ expect(screen.getByText('pm@example.com')).toBeInTheDocument();
196
+ });
197
+
198
+ it('keeps direct board guests first-class for invite, update, and remove', async () => {
199
+ listWorkspaceTaskBoardSharesMock.mockResolvedValue({
200
+ shares: [
201
+ {
202
+ id: 'share-1',
203
+ email: 'guest@example.com',
204
+ permission: 'view',
205
+ user: null,
206
+ user_id: null,
207
+ },
208
+ ],
209
+ });
210
+
211
+ renderBoardShareDialog();
212
+ await waitFor(() => {
213
+ expect(listWorkspaceTaskBoardSharesMock).toHaveBeenCalledWith(
214
+ 'ws-1',
215
+ 'board-1'
216
+ );
217
+ });
218
+ await waitFor(() => {
219
+ expect(
220
+ screen.getByRole('button', {
221
+ name: /ws-task-boards.share.guests.title/,
222
+ })
223
+ ).toHaveTextContent('1');
224
+ });
225
+
226
+ fireEvent.click(
227
+ screen.getByRole('button', {
228
+ name: /ws-task-boards.share.guests.title/,
229
+ })
230
+ );
231
+ fireEvent.change(
232
+ screen.getByPlaceholderText('ws-task-boards.share.email_placeholder'),
233
+ {
234
+ target: { value: 'new@example.com' },
235
+ }
236
+ );
237
+ fireEvent.click(screen.getByText('common.share'));
238
+
239
+ await waitFor(() => {
240
+ expect(createWorkspaceTaskBoardShareMock).toHaveBeenCalledWith(
241
+ 'ws-1',
242
+ 'board-1',
243
+ { email: 'new@example.com', permission: 'view' }
244
+ );
245
+ });
246
+
247
+ expect(await screen.findAllByText('guest@example.com')).toHaveLength(2);
248
+
249
+ fireEvent.change(
250
+ screen.getAllByLabelText('ws-task-boards.share.permission.view').at(-1)!,
251
+ { target: { value: 'edit' } }
252
+ );
253
+ await waitFor(() => {
254
+ expect(updateWorkspaceTaskBoardShareMock).toHaveBeenCalledWith(
255
+ 'ws-1',
256
+ 'board-1',
257
+ { shareId: 'share-1', permission: 'edit' }
258
+ );
259
+ });
260
+
261
+ fireEvent.click(screen.getByLabelText('common.remove'));
262
+ await waitFor(() => {
263
+ expect(deleteWorkspaceTaskBoardShareMock).toHaveBeenCalledWith(
264
+ 'ws-1',
265
+ 'board-1',
266
+ 'share-1'
267
+ );
268
+ });
269
+ });
270
+ });
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
6
- import { render, screen } from '@testing-library/react';
6
+ import { render, screen, waitFor } from '@testing-library/react';
7
7
  import type { ReactNode } from 'react';
8
8
  import { beforeEach, describe, expect, it, vi } from 'vitest';
9
9
  import WorkspaceProjectsClientPage from '../workspace-projects-client-page';
@@ -97,4 +97,73 @@ describe('WorkspaceProjectsClientPage', () => {
97
97
  expect(await screen.findByTestId('boards-view')).toBeInTheDocument();
98
98
  expect(mocks.replace).not.toHaveBeenCalled();
99
99
  });
100
+
101
+ it('does not fetch board data for members without manage_projects', async () => {
102
+ mocks.checkWorkspacePermission.mockResolvedValue({
103
+ hasPermission: false,
104
+ });
105
+
106
+ const queryClient = new QueryClient({
107
+ defaultOptions: {
108
+ queries: { retry: false },
109
+ mutations: { retry: false },
110
+ },
111
+ });
112
+
113
+ render(
114
+ <QueryClientProvider client={queryClient}>
115
+ <WorkspaceProjectsClientPage />
116
+ </QueryClientProvider>
117
+ );
118
+
119
+ await waitFor(() => {
120
+ expect(mocks.replace).toHaveBeenCalledWith('/personal');
121
+ });
122
+ expect(mocks.getWorkspaceBoardsData).not.toHaveBeenCalled();
123
+ });
124
+
125
+ it('allows task-board guests to fetch their shared boards', async () => {
126
+ mocks.getWorkspace.mockResolvedValue({
127
+ access_type: 'guest',
128
+ guest_products: ['tasks'],
129
+ id: 'guest-ws',
130
+ personal: false,
131
+ });
132
+ mocks.checkWorkspacePermission.mockResolvedValue({
133
+ hasPermission: false,
134
+ });
135
+ mocks.getWorkspaceBoardsData.mockResolvedValue({
136
+ access_type: 'guest',
137
+ count: 1,
138
+ data: [
139
+ {
140
+ archived_at: null,
141
+ deleted_at: null,
142
+ id: 'board-1',
143
+ name: 'Shared Tasks',
144
+ },
145
+ ],
146
+ });
147
+
148
+ const queryClient = new QueryClient({
149
+ defaultOptions: {
150
+ queries: { retry: false },
151
+ mutations: { retry: false },
152
+ },
153
+ });
154
+
155
+ render(
156
+ <QueryClientProvider client={queryClient}>
157
+ <WorkspaceProjectsClientPage />
158
+ </QueryClientProvider>
159
+ );
160
+
161
+ expect(await screen.findByTestId('boards-view')).toBeInTheDocument();
162
+ expect(mocks.getWorkspaceBoardsData).toHaveBeenCalledWith('guest-ws', {
163
+ page: 1,
164
+ pageSize: 10,
165
+ q: '',
166
+ });
167
+ expect(mocks.replace).not.toHaveBeenCalled();
168
+ });
100
169
  });