@tuturuuu/ui 0.8.0 → 0.10.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 (245) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/biome.json +1 -1
  3. package/package.json +74 -71
  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/custom/__tests__/settings-dialog-search.test.ts +78 -0
  29. package/src/components/ui/custom/__tests__/settings-dialog-shell-compile-graph.test.ts +76 -0
  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 +46 -1
  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/nav-link.test.tsx +165 -0
  40. package/src/components/ui/custom/nav-link.tsx +69 -11
  41. package/src/components/ui/custom/navigation.tsx +1 -0
  42. package/src/components/ui/custom/settings/task-settings.tsx +104 -0
  43. package/src/components/ui/custom/settings-dialog-search-loader.d.ts +5 -0
  44. package/src/components/ui/custom/settings-dialog-search-loader.js +3 -0
  45. package/src/components/ui/custom/settings-dialog-search.ts +75 -0
  46. package/src/components/ui/custom/settings-dialog-shell.tsx +65 -28
  47. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  48. package/src/components/ui/custom/workspace-select-helpers.ts +23 -0
  49. package/src/components/ui/custom/workspace-select.tsx +25 -19
  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 +1 -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-edit-dialog.tsx +1 -4
  69. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  70. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  71. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  72. package/src/components/ui/finance/wallets/form.tsx +15 -4
  73. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  74. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  75. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  76. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  77. package/src/components/ui/input-otp.tsx +1 -1
  78. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  79. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  80. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  81. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  82. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  83. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  84. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  85. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  86. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  87. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  88. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  89. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  90. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  91. package/src/components/ui/navigation-menu.tsx +1 -1
  92. package/src/components/ui/pagination.tsx +1 -1
  93. package/src/components/ui/radio-group.tsx +1 -1
  94. package/src/components/ui/select.tsx +5 -1
  95. package/src/components/ui/sheet.tsx +1 -1
  96. package/src/components/ui/sidebar.tsx +1 -1
  97. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  98. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  99. package/src/components/ui/storefront/cart-summary.tsx +93 -154
  100. package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
  101. package/src/components/ui/storefront/listing-card.tsx +1 -1
  102. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  103. package/src/components/ui/storefront/product-detail.tsx +1 -1
  104. package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
  105. package/src/components/ui/storefront/storefront-surface.tsx +101 -166
  106. package/src/components/ui/storefront/types.ts +4 -0
  107. package/src/components/ui/storefront/utils.ts +6 -0
  108. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  109. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  110. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  111. package/src/components/ui/text-editor/editor.tsx +69 -14
  112. package/src/components/ui/text-editor/extensions.ts +8 -2
  113. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  114. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  115. package/src/components/ui/toast.tsx +1 -1
  116. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +286 -0
  117. package/src/components/ui/tu-do/boards/__tests__/task-board-form.test.tsx +12 -0
  118. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  119. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +15 -226
  120. package/src/components/ui/tu-do/boards/board-share-settings-panel.tsx +351 -0
  121. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +121 -39
  122. package/src/components/ui/tu-do/boards/boardId/enhanced-task-list.tsx +7 -0
  123. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  124. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  125. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  126. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-types.ts +3 -3
  127. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  128. package/src/components/ui/tu-do/boards/boardId/kanban/data/use-bulk-resources.ts +59 -5
  129. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  130. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  131. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/drag-preview.tsx +20 -1
  132. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
  133. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  134. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  135. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
  136. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  137. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  138. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  139. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  140. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  141. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  142. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  143. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  144. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  145. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  146. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  147. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +642 -5
  148. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +224 -15
  149. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +535 -53
  150. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +101 -33
  151. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +235 -113
  152. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +50 -5
  153. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +12 -2
  154. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +10 -1
  155. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  156. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +20 -0
  157. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +10 -0
  158. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +271 -36
  159. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  160. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  161. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +22 -0
  162. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +9 -0
  163. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +9 -0
  164. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-toolbar.tsx +9 -0
  165. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +35 -3
  166. package/src/components/ui/tu-do/boards/form.tsx +1 -1
  167. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  168. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  169. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  170. package/src/components/ui/tu-do/hooks/__tests__/useTaskLabelManagement.test.tsx +48 -0
  171. package/src/components/ui/tu-do/hooks/__tests__/useTaskProjectManagement.test.tsx +144 -0
  172. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +7 -0
  173. package/src/components/ui/tu-do/hooks/useTaskLabelManagement.ts +115 -106
  174. package/src/components/ui/tu-do/hooks/useTaskProjectManagement.ts +115 -122
  175. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  176. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  177. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  178. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  179. package/src/components/ui/tu-do/progress/task-progress-import-panel.tsx +60 -0
  180. package/src/components/ui/tu-do/progress/task-progress-leaderboards-panel.tsx +156 -0
  181. package/src/components/ui/tu-do/progress/task-progress-page.tsx +348 -0
  182. package/src/components/ui/tu-do/progress/task-progress-panels.tsx +301 -0
  183. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +26 -0
  184. package/src/components/ui/tu-do/shared/__tests__/assignee-select.test.tsx +81 -10
  185. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +141 -1
  186. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +377 -36
  187. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +374 -0
  188. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +419 -5
  189. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +38 -0
  190. package/src/components/ui/tu-do/shared/__tests__/task-cache-patches.test.ts +147 -0
  191. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  192. package/src/components/ui/tu-do/shared/__tests__/use-progressive-board-loader.test.tsx +3 -0
  193. package/src/components/ui/tu-do/shared/assignee-select.tsx +77 -26
  194. package/src/components/ui/tu-do/shared/board-client.tsx +15 -10
  195. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  196. package/src/components/ui/tu-do/shared/board-header.tsx +471 -975
  197. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  198. package/src/components/ui/tu-do/shared/board-switcher.tsx +244 -220
  199. package/src/components/ui/tu-do/shared/board-user-presence-avatars.tsx +18 -12
  200. package/src/components/ui/tu-do/shared/board-views.tsx +577 -85
  201. package/src/components/ui/tu-do/shared/list-view.tsx +246 -2
  202. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  203. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  204. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  205. package/src/components/ui/tu-do/shared/task-cache-patches.ts +394 -0
  206. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +21 -1
  207. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +5 -1
  208. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +25 -2
  209. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +7 -1
  210. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  211. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-data.ts +79 -10
  212. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +76 -77
  213. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.test.tsx +63 -0
  214. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.ts +78 -69
  215. package/src/components/ui/tu-do/shared/task-edit-dialog/personal-overrides-section.tsx +28 -8
  216. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/dependencies-section.tsx +14 -3
  217. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/parent-section.tsx +6 -1
  218. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/related-section.tsx +6 -1
  219. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/subtasks-section.tsx +6 -1
  220. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/types/task-relationships.types.ts +8 -0
  221. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  222. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  223. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  224. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +8 -1
  225. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.test.tsx +150 -0
  226. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +61 -35
  227. package/src/components/ui/tu-do/shared/task-edit-dialog/task-relationships-properties.tsx +44 -2
  228. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  229. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +11 -1
  230. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  231. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +11 -0
  232. package/src/components/ui/tu-do/shared/use-progressive-board-loader.ts +2 -0
  233. package/src/declarations.d.ts +1 -0
  234. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  235. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  236. package/src/hooks/__tests__/useBoardPresence.test.tsx +191 -0
  237. package/src/hooks/__tests__/useBoardRealtime.test.tsx +24 -144
  238. package/src/hooks/use-calendar-sync.tsx +247 -243
  239. package/src/hooks/use-calendar.tsx +323 -138
  240. package/src/hooks/use-task-actions.ts +24 -0
  241. package/src/hooks/use-user-workspace-config.ts +75 -0
  242. package/src/hooks/use-workspace-currency.ts +8 -3
  243. package/src/hooks/useBoardPresence.ts +364 -0
  244. package/src/hooks/useBoardRealtimeEventHandler.ts +45 -90
  245. package/src/lib/workspace-actions.ts +2 -6
@@ -1,18 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4
- import { Loader2, Share2, Trash2 } from '@tuturuuu/icons';
5
- import {
6
- createWorkspaceTaskBoardShare,
7
- deleteWorkspaceTaskBoardShare,
8
- listWorkspaceTaskBoardShares,
9
- updateWorkspaceTaskBoardShare,
10
- type WorkspaceTaskBoardShare,
11
- type WorkspaceTaskBoardSharePermission,
12
- } from '@tuturuuu/internal-api/tasks';
3
+ import { Share2 } from '@tuturuuu/icons';
13
4
  import type { WorkspaceTaskBoard } from '@tuturuuu/types';
14
- import { Avatar, AvatarFallback, AvatarImage } from '@tuturuuu/ui/avatar';
15
- import { Badge } from '@tuturuuu/ui/badge';
16
5
  import { Button } from '@tuturuuu/ui/button';
17
6
  import {
18
7
  Dialog,
@@ -22,18 +11,9 @@ import {
22
11
  DialogHeader,
23
12
  DialogTitle,
24
13
  } from '@tuturuuu/ui/dialog';
25
- import { Input } from '@tuturuuu/ui/input';
26
- import {
27
- Select,
28
- SelectContent,
29
- SelectItem,
30
- SelectTrigger,
31
- SelectValue,
32
- } from '@tuturuuu/ui/select';
33
- import { getInitials } from '@tuturuuu/utils/name-helper';
34
14
  import { useTranslations } from 'next-intl';
35
- import { useState } from 'react';
36
- import { toast } from 'sonner';
15
+ import { useRef } from 'react';
16
+ import { BoardShareSettingsPanel } from './board-share-settings-panel';
37
17
 
38
18
  interface BoardShareDialogProps {
39
19
  board: Pick<WorkspaceTaskBoard, 'id' | 'name'>;
@@ -42,16 +22,6 @@ interface BoardShareDialogProps {
42
22
  wsId: string;
43
23
  }
44
24
 
45
- function shareDisplayName(share: WorkspaceTaskBoardShare) {
46
- return (
47
- share.user?.display_name ||
48
- (share.user?.handle ? `@${share.user.handle}` : null) ||
49
- share.email ||
50
- share.user_id ||
51
- 'Guest'
52
- );
53
- }
54
-
55
25
  export function BoardShareDialog({
56
26
  board,
57
27
  onOpenChange,
@@ -59,78 +29,17 @@ export function BoardShareDialog({
59
29
  wsId,
60
30
  }: BoardShareDialogProps) {
61
31
  const t = useTranslations();
62
- const queryClient = useQueryClient();
63
- const [email, setEmail] = useState('');
64
- const [permission, setPermission] =
65
- useState<WorkspaceTaskBoardSharePermission>('view');
66
-
67
- const queryKey = ['task-board-shares', wsId, board.id] as const;
68
- const sharesQuery = useQuery({
69
- queryKey,
70
- queryFn: () => listWorkspaceTaskBoardShares(wsId, board.id),
71
- enabled: open,
72
- });
73
-
74
- const createMutation = useMutation({
75
- mutationFn: () =>
76
- createWorkspaceTaskBoardShare(wsId, board.id, {
77
- email,
78
- permission,
79
- }),
80
- onSuccess: () => {
81
- setEmail('');
82
- setPermission('view');
83
- void queryClient.invalidateQueries({ queryKey });
84
- toast.success(t('ws-task-boards.share.saved'));
85
- },
86
- onError: () => {
87
- toast.error(t('common.error'));
88
- },
89
- });
90
-
91
- const updateMutation = useMutation({
92
- mutationFn: ({
93
- nextPermission,
94
- shareId,
95
- }: {
96
- nextPermission: WorkspaceTaskBoardSharePermission;
97
- shareId: string;
98
- }) =>
99
- updateWorkspaceTaskBoardShare(wsId, board.id, {
100
- shareId,
101
- permission: nextPermission,
102
- }),
103
- onSuccess: () => {
104
- void queryClient.invalidateQueries({ queryKey });
105
- },
106
- onError: () => {
107
- toast.error(t('common.error'));
108
- },
109
- });
110
-
111
- const deleteMutation = useMutation({
112
- mutationFn: (shareId: string) =>
113
- deleteWorkspaceTaskBoardShare(wsId, board.id, shareId),
114
- onSuccess: () => {
115
- void queryClient.invalidateQueries({ queryKey });
116
- toast.success(t('ws-task-boards.share.removed'));
117
- },
118
- onError: () => {
119
- toast.error(t('common.error'));
120
- },
121
- });
122
-
123
- const shares = sharesQuery.data?.shares ?? [];
124
- const canSubmit =
125
- email.trim().length > 0 &&
126
- !createMutation.isPending &&
127
- !sharesQuery.isLoading;
32
+ const initialFocusRef = useRef<HTMLDivElement>(null);
128
33
 
129
34
  return (
130
35
  <Dialog open={open} onOpenChange={onOpenChange}>
131
36
  <DialogContent
132
- className="sm:max-w-xl"
37
+ className="max-h-[min(88dvh,720px)] overflow-y-auto sm:max-w-xl"
133
38
  onClick={(e) => e.stopPropagation()}
39
+ onOpenAutoFocus={(event) => {
40
+ event.preventDefault();
41
+ initialFocusRef.current?.focus();
42
+ }}
134
43
  >
135
44
  <DialogHeader>
136
45
  <DialogTitle className="flex items-center gap-2">
@@ -139,135 +48,15 @@ export function BoardShareDialog({
139
48
  name: board.name || t('common.untitled'),
140
49
  })}
141
50
  </DialogTitle>
142
- <DialogDescription>
143
- {t('ws-task-boards.share.description')}
51
+ <DialogDescription className="sr-only">
52
+ {t('ws-task-boards.share.title', {
53
+ name: board.name || t('common.untitled'),
54
+ })}
144
55
  </DialogDescription>
145
56
  </DialogHeader>
57
+ <div ref={initialFocusRef} tabIndex={-1} className="sr-only" />
146
58
 
147
- <div className="space-y-4">
148
- <div className="rounded-md border bg-muted/30 p-3 text-muted-foreground text-sm">
149
- {t('ws-task-boards.share.guest_scope')}
150
- </div>
151
-
152
- <div className="grid gap-2 sm:grid-cols-[1fr_8rem_auto]">
153
- <Input
154
- type="email"
155
- value={email}
156
- onChange={(event) => setEmail(event.target.value)}
157
- placeholder={t('ws-task-boards.share.email_placeholder')}
158
- />
159
- <Select
160
- value={permission}
161
- onValueChange={(value) =>
162
- setPermission(value as WorkspaceTaskBoardSharePermission)
163
- }
164
- >
165
- <SelectTrigger>
166
- <SelectValue />
167
- </SelectTrigger>
168
- <SelectContent>
169
- <SelectItem value="view">
170
- {t('ws-task-boards.share.permission.view')}
171
- </SelectItem>
172
- <SelectItem value="edit">
173
- {t('ws-task-boards.share.permission.edit')}
174
- </SelectItem>
175
- </SelectContent>
176
- </Select>
177
- <Button
178
- type="button"
179
- onClick={() => createMutation.mutate()}
180
- disabled={!canSubmit}
181
- >
182
- {createMutation.isPending ? (
183
- <Loader2 className="h-4 w-4 animate-spin" />
184
- ) : (
185
- t('common.share')
186
- )}
187
- </Button>
188
- </div>
189
-
190
- <div className="space-y-2">
191
- <div className="font-medium text-sm">
192
- {t('ws-task-boards.share.shared_with')}
193
- </div>
194
- {sharesQuery.isLoading ? (
195
- <div className="rounded-md border border-dashed p-4 text-muted-foreground text-sm">
196
- {t('common.loading')}
197
- </div>
198
- ) : shares.length === 0 ? (
199
- <div className="rounded-md border border-dashed p-4 text-muted-foreground text-sm">
200
- {t('ws-task-boards.share.empty')}
201
- </div>
202
- ) : (
203
- <div className="divide-y rounded-md border">
204
- {shares.map((share) => (
205
- <div
206
- key={share.id}
207
- className="flex flex-col gap-3 p-3 sm:flex-row sm:items-center"
208
- >
209
- <div className="flex min-w-0 flex-1 items-center gap-3">
210
- <Avatar className="h-8 w-8">
211
- <AvatarImage
212
- src={share.user?.avatar_url ?? undefined}
213
- />
214
- <AvatarFallback>
215
- {getInitials(shareDisplayName(share))}
216
- </AvatarFallback>
217
- </Avatar>
218
- <div className="min-w-0">
219
- <div className="truncate font-medium text-sm">
220
- {shareDisplayName(share)}
221
- </div>
222
- <div className="truncate text-muted-foreground text-xs">
223
- {share.email || share.user_id}
224
- </div>
225
- </div>
226
- </div>
227
-
228
- <Badge variant="outline" className="w-fit">
229
- {t('common.guest_access')}
230
- </Badge>
231
-
232
- <Select
233
- value={share.permission}
234
- onValueChange={(value) =>
235
- updateMutation.mutate({
236
- shareId: share.id,
237
- nextPermission:
238
- value as WorkspaceTaskBoardSharePermission,
239
- })
240
- }
241
- >
242
- <SelectTrigger className="w-28">
243
- <SelectValue />
244
- </SelectTrigger>
245
- <SelectContent>
246
- <SelectItem value="view">
247
- {t('ws-task-boards.share.permission.view')}
248
- </SelectItem>
249
- <SelectItem value="edit">
250
- {t('ws-task-boards.share.permission.edit')}
251
- </SelectItem>
252
- </SelectContent>
253
- </Select>
254
-
255
- <Button
256
- type="button"
257
- variant="ghost"
258
- size="icon"
259
- onClick={() => deleteMutation.mutate(share.id)}
260
- disabled={deleteMutation.isPending}
261
- aria-label={t('common.remove')}
262
- >
263
- <Trash2 className="h-4 w-4" />
264
- </Button>
265
- </div>
266
- ))}
267
- </div>
268
- )}
269
- </div>
270
- </div>
59
+ <BoardShareSettingsPanel board={board} enabled={open} wsId={wsId} />
271
60
 
272
61
  <DialogFooter>
273
62
  <Button variant="outline" onClick={() => onOpenChange(false)}>
@@ -0,0 +1,351 @@
1
+ 'use client';
2
+
3
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4
+ import { Loader2, Trash2, Users } from '@tuturuuu/icons';
5
+ import {
6
+ createWorkspaceTaskBoardShare,
7
+ deleteWorkspaceTaskBoardShare,
8
+ listWorkspaceTaskBoardShares,
9
+ listWorkspaceTaskBoardViewableMembers,
10
+ updateWorkspaceTaskBoardShare,
11
+ type WorkspaceTaskBoardShare,
12
+ type WorkspaceTaskBoardSharePermission,
13
+ type WorkspaceTaskBoardViewableMember,
14
+ } from '@tuturuuu/internal-api/tasks';
15
+ import type { WorkspaceTaskBoard } from '@tuturuuu/types';
16
+ import { Avatar, AvatarFallback, AvatarImage } from '@tuturuuu/ui/avatar';
17
+ import { Badge } from '@tuturuuu/ui/badge';
18
+ import { Button } from '@tuturuuu/ui/button';
19
+ import { Combobox } from '@tuturuuu/ui/custom/combobox';
20
+ import { Input } from '@tuturuuu/ui/input';
21
+ import { getInitials } from '@tuturuuu/utils/name-helper';
22
+ import { useTranslations } from 'next-intl';
23
+ import { useState } from 'react';
24
+ import { toast } from 'sonner';
25
+ import { BoardPublicLinkSection } from './board-public-link-section';
26
+ import { ShareSection } from './share-section';
27
+
28
+ interface BoardShareSettingsPanelProps {
29
+ board: Pick<WorkspaceTaskBoard, 'id' | 'name'>;
30
+ enabled?: boolean;
31
+ wsId: string;
32
+ }
33
+
34
+ function shareDisplayName(share: WorkspaceTaskBoardShare) {
35
+ return (
36
+ share.user?.display_name ||
37
+ (share.user?.handle ? `@${share.user.handle}` : null) ||
38
+ share.email ||
39
+ share.user_id ||
40
+ 'Guest'
41
+ );
42
+ }
43
+
44
+ function viewableMemberDisplayName(member: WorkspaceTaskBoardViewableMember) {
45
+ return (
46
+ member.display_name ||
47
+ (member.handle ? `@${member.handle}` : null) ||
48
+ member.email ||
49
+ member.user_id
50
+ );
51
+ }
52
+
53
+ export function BoardShareSettingsPanel({
54
+ board,
55
+ enabled = true,
56
+ wsId,
57
+ }: BoardShareSettingsPanelProps) {
58
+ const t = useTranslations();
59
+ const queryClient = useQueryClient();
60
+ const [email, setEmail] = useState('');
61
+ const [permission, setPermission] =
62
+ useState<WorkspaceTaskBoardSharePermission>('view');
63
+ const [membersOpen, setMembersOpen] = useState(false);
64
+ const [guestsOpen, setGuestsOpen] = useState(false);
65
+
66
+ const queryKey = ['task-board-shares', wsId, board.id] as const;
67
+ const sharesQuery = useQuery({
68
+ queryKey,
69
+ queryFn: () => listWorkspaceTaskBoardShares(wsId, board.id),
70
+ enabled,
71
+ });
72
+ const viewableMembersQuery = useQuery({
73
+ queryKey: ['task-board-viewable-members', wsId, board.id] as const,
74
+ queryFn: () => listWorkspaceTaskBoardViewableMembers(wsId, board.id),
75
+ enabled: enabled && membersOpen,
76
+ staleTime: 60_000,
77
+ });
78
+
79
+ const createMutation = useMutation({
80
+ mutationFn: () =>
81
+ createWorkspaceTaskBoardShare(wsId, board.id, {
82
+ email,
83
+ permission,
84
+ }),
85
+ onSuccess: () => {
86
+ setEmail('');
87
+ setPermission('view');
88
+ void queryClient.invalidateQueries({ queryKey });
89
+ toast.success(t('ws-task-boards.share.saved'));
90
+ },
91
+ onError: () => {
92
+ toast.error(t('common.error'));
93
+ },
94
+ });
95
+
96
+ const updateMutation = useMutation({
97
+ mutationFn: ({
98
+ nextPermission,
99
+ shareId,
100
+ }: {
101
+ nextPermission: WorkspaceTaskBoardSharePermission;
102
+ shareId: string;
103
+ }) =>
104
+ updateWorkspaceTaskBoardShare(wsId, board.id, {
105
+ shareId,
106
+ permission: nextPermission,
107
+ }),
108
+ onSuccess: () => {
109
+ void queryClient.invalidateQueries({ queryKey });
110
+ },
111
+ onError: () => {
112
+ toast.error(t('common.error'));
113
+ },
114
+ });
115
+
116
+ const deleteMutation = useMutation({
117
+ mutationFn: (shareId: string) =>
118
+ deleteWorkspaceTaskBoardShare(wsId, board.id, shareId),
119
+ onSuccess: () => {
120
+ void queryClient.invalidateQueries({ queryKey });
121
+ toast.success(t('ws-task-boards.share.removed'));
122
+ },
123
+ onError: () => {
124
+ toast.error(t('common.error'));
125
+ },
126
+ });
127
+
128
+ const shares = sharesQuery.data?.shares ?? [];
129
+ const members = Array.isArray(viewableMembersQuery.data?.members)
130
+ ? viewableMembersQuery.data.members
131
+ : undefined;
132
+ const membersCount = members?.length;
133
+ const membersStatusBadge = viewableMembersQuery.isLoading ? (
134
+ <Badge variant="secondary" className="gap-1 px-2 py-0.5 text-[10px]">
135
+ <Loader2 className="h-3 w-3 animate-spin" />
136
+ {t('common.loading')}
137
+ </Badge>
138
+ ) : typeof membersCount === 'number' ? (
139
+ <Badge variant="secondary" className="px-2 py-0.5 text-[10px]">
140
+ {membersCount}
141
+ </Badge>
142
+ ) : (
143
+ <Badge variant="outline" className="px-2 py-0.5 text-[10px]">
144
+ {t('common.workspace')}
145
+ </Badge>
146
+ );
147
+ const guestsStatusBadge = sharesQuery.isLoading ? (
148
+ <Badge variant="secondary" className="gap-1 px-2 py-0.5 text-[10px]">
149
+ <Loader2 className="h-3 w-3 animate-spin" />
150
+ {t('common.loading')}
151
+ </Badge>
152
+ ) : shares.length ? (
153
+ <Badge variant="secondary" className="px-2 py-0.5 text-[10px]">
154
+ {shares.length}
155
+ </Badge>
156
+ ) : (
157
+ <Badge variant="outline" className="px-2 py-0.5 text-[10px]">
158
+ {t('common.none')}
159
+ </Badge>
160
+ );
161
+ const canSubmit =
162
+ email.trim().length > 0 &&
163
+ !createMutation.isPending &&
164
+ !sharesQuery.isLoading;
165
+ const permissionOptions = [
166
+ {
167
+ value: 'view',
168
+ label: t('ws-task-boards.share.permission.view'),
169
+ },
170
+ {
171
+ value: 'edit',
172
+ label: t('ws-task-boards.share.permission.edit'),
173
+ },
174
+ ];
175
+
176
+ return (
177
+ <div className="space-y-2">
178
+ <BoardPublicLinkSection boardId={board.id} open={enabled} wsId={wsId} />
179
+
180
+ <ShareSection
181
+ open={membersOpen}
182
+ onOpenChange={setMembersOpen}
183
+ title={t('ws-task-boards.share.workspace_members.title')}
184
+ tooltip={t('ws-task-boards.share.workspace_members.tooltip')}
185
+ icon={<Users className="h-4 w-4 text-muted-foreground" />}
186
+ statusBadge={membersStatusBadge}
187
+ >
188
+ {viewableMembersQuery.isLoading ? (
189
+ <div className="flex items-center gap-2 text-muted-foreground text-sm">
190
+ <Loader2 className="h-4 w-4 animate-spin" />
191
+ {t('common.loading')}
192
+ </div>
193
+ ) : (members ?? []).length === 0 ? (
194
+ <div className="text-muted-foreground text-sm">
195
+ {t('ws-task-boards.share.workspace_members.empty')}
196
+ </div>
197
+ ) : (
198
+ <div className="divide-y rounded-md border">
199
+ {members?.map((member) => (
200
+ <div
201
+ key={member.user_id}
202
+ className="flex flex-col gap-3 p-3 sm:flex-row sm:items-center"
203
+ >
204
+ <div className="flex min-w-0 flex-1 items-center gap-3">
205
+ <Avatar className="h-8 w-8">
206
+ <AvatarImage src={member.avatar_url ?? undefined} />
207
+ <AvatarFallback>
208
+ {getInitials(viewableMemberDisplayName(member))}
209
+ </AvatarFallback>
210
+ </Avatar>
211
+ <div className="min-w-0">
212
+ <div className="truncate font-medium text-sm">
213
+ {viewableMemberDisplayName(member)}
214
+ </div>
215
+ <div className="truncate text-muted-foreground text-xs">
216
+ {member.email || member.user_id}
217
+ </div>
218
+ </div>
219
+ </div>
220
+ <div className="flex flex-wrap items-center gap-1.5">
221
+ {member.is_creator && (
222
+ <Badge variant="secondary">
223
+ {t('ws-task-boards.share.workspace_members.creator')}
224
+ </Badge>
225
+ )}
226
+ {member.roles.slice(0, 2).map((role) => (
227
+ <Badge key={role.id} variant="outline">
228
+ {role.name}
229
+ </Badge>
230
+ ))}
231
+ <Badge variant="outline">
232
+ {t('ws-task-boards.share.workspace_members.badge')}
233
+ </Badge>
234
+ </div>
235
+ </div>
236
+ ))}
237
+ </div>
238
+ )}
239
+ </ShareSection>
240
+
241
+ <ShareSection
242
+ open={guestsOpen}
243
+ onOpenChange={setGuestsOpen}
244
+ title={t('ws-task-boards.share.guests.title')}
245
+ tooltip={t('ws-task-boards.share.guests.tooltip')}
246
+ icon={<Users className="h-4 w-4 text-muted-foreground" />}
247
+ statusBadge={guestsStatusBadge}
248
+ >
249
+ <div className="space-y-3">
250
+ <div className="grid gap-2 sm:grid-cols-[1fr_8rem_auto]">
251
+ <Input
252
+ type="email"
253
+ value={email}
254
+ onChange={(event) => setEmail(event.target.value)}
255
+ placeholder={t('ws-task-boards.share.email_placeholder')}
256
+ />
257
+ <Combobox
258
+ mode="single"
259
+ options={permissionOptions}
260
+ selected={permission}
261
+ onChange={(value) =>
262
+ setPermission(value as WorkspaceTaskBoardSharePermission)
263
+ }
264
+ placeholder={t('ws-task-boards.share.permission.view')}
265
+ searchPlaceholder={t('common.search_members')}
266
+ className="[&_button]:h-9"
267
+ />
268
+ <Button
269
+ type="button"
270
+ onClick={() => createMutation.mutate()}
271
+ disabled={!canSubmit}
272
+ >
273
+ {createMutation.isPending ? (
274
+ <Loader2 className="h-4 w-4 animate-spin" />
275
+ ) : (
276
+ t('common.share')
277
+ )}
278
+ </Button>
279
+ </div>
280
+
281
+ {sharesQuery.isLoading ? (
282
+ <div className="rounded-md border border-dashed p-4 text-muted-foreground text-sm">
283
+ {t('common.loading')}
284
+ </div>
285
+ ) : shares.length === 0 ? (
286
+ <div className="rounded-md border border-dashed p-4 text-muted-foreground text-sm">
287
+ {t('ws-task-boards.share.empty')}
288
+ </div>
289
+ ) : (
290
+ <div className="divide-y rounded-md border">
291
+ {shares.map((share) => (
292
+ <div
293
+ key={share.id}
294
+ className="flex flex-col gap-3 p-3 sm:flex-row sm:items-center"
295
+ >
296
+ <div className="flex min-w-0 flex-1 items-center gap-3">
297
+ <Avatar className="h-8 w-8">
298
+ <AvatarImage src={share.user?.avatar_url ?? undefined} />
299
+ <AvatarFallback>
300
+ {getInitials(shareDisplayName(share))}
301
+ </AvatarFallback>
302
+ </Avatar>
303
+ <div className="min-w-0">
304
+ <div className="truncate font-medium text-sm">
305
+ {shareDisplayName(share)}
306
+ </div>
307
+ <div className="truncate text-muted-foreground text-xs">
308
+ {share.email || share.user_id}
309
+ </div>
310
+ </div>
311
+ </div>
312
+
313
+ <Badge variant="outline" className="w-fit">
314
+ {t('common.guest_access')}
315
+ </Badge>
316
+
317
+ <Combobox
318
+ mode="single"
319
+ options={permissionOptions}
320
+ selected={share.permission}
321
+ onChange={(value) =>
322
+ updateMutation.mutate({
323
+ shareId: share.id,
324
+ nextPermission:
325
+ value as WorkspaceTaskBoardSharePermission,
326
+ })
327
+ }
328
+ placeholder={t('ws-task-boards.share.permission.view')}
329
+ searchPlaceholder={t('common.search_members')}
330
+ className="w-28 [&_button]:h-9"
331
+ />
332
+
333
+ <Button
334
+ type="button"
335
+ variant="ghost"
336
+ size="icon"
337
+ onClick={() => deleteMutation.mutate(share.id)}
338
+ disabled={deleteMutation.isPending}
339
+ aria-label={t('common.remove')}
340
+ >
341
+ <Trash2 className="h-4 w-4" />
342
+ </Button>
343
+ </div>
344
+ ))}
345
+ </div>
346
+ )}
347
+ </div>
348
+ </ShareSection>
349
+ </div>
350
+ );
351
+ }