@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
@@ -1,15 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { Pencil } from '@tuturuuu/icons';
3
+ import { KeyRound, Pencil, ShieldUser } from '@tuturuuu/icons';
4
4
  import type { WorkspaceDefaultPermissionMemberType } from '@tuturuuu/types';
5
5
  import { Button } from '@tuturuuu/ui/button';
6
- import {
7
- Card,
8
- CardContent,
9
- CardDescription,
10
- CardHeader,
11
- CardTitle,
12
- } from '@tuturuuu/ui/card';
13
6
  import { Skeleton } from '@tuturuuu/ui/skeleton';
14
7
  import { useTranslations } from 'next-intl';
15
8
  import type { WorkspaceAccessRole } from './types';
@@ -37,45 +30,77 @@ export function WorkspaceAccessDefaultRoleCard({
37
30
  }) {
38
31
  const t = useTranslations() as (key: string) => string;
39
32
  const isGuest = memberType === 'GUEST';
33
+ const enabled = enabledPermissionCount(role);
34
+ const pct =
35
+ permissionCount > 0 ? Math.round((enabled / permissionCount) * 100) : 0;
36
+ const accent = isGuest
37
+ ? 'border-dynamic-blue/30 bg-dynamic-blue/10 text-dynamic-blue'
38
+ : 'border-dynamic-green/30 bg-dynamic-green/10 text-dynamic-green';
39
+ const barColor = isGuest ? 'bg-dynamic-blue' : 'bg-dynamic-green';
40
40
 
41
41
  return (
42
- <Card className="shadow-none">
43
- <CardHeader>
44
- <div className="flex flex-wrap items-start justify-between gap-3">
45
- <div className="space-y-1">
46
- <CardTitle className="text-base">
42
+ <div className="rounded-xl border border-border bg-background p-5">
43
+ <div className="flex flex-wrap items-start justify-between gap-3">
44
+ <div className="flex min-w-0 gap-3">
45
+ <div
46
+ className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg border ${accent}`}
47
+ >
48
+ {isGuest ? (
49
+ <KeyRound className="h-5 w-5" />
50
+ ) : (
51
+ <ShieldUser className="h-5 w-5" />
52
+ )}
53
+ </div>
54
+ <div className="min-w-0">
55
+ <div className="font-semibold text-base">
47
56
  {isGuest
48
57
  ? t('ws-roles.guest_defaults')
49
58
  : t('ws-roles.member_defaults')}
50
- </CardTitle>
51
- <CardDescription>
59
+ </div>
60
+ <p className="mt-0.5 max-w-md text-muted-foreground text-sm">
52
61
  {isGuest
53
62
  ? t('ws-roles.guest_defaults_description')
54
63
  : t('ws-roles.member_defaults_description')}
55
- </CardDescription>
64
+ </p>
56
65
  </div>
57
- {canManageRoles ? (
58
- <Button
59
- variant="outline"
60
- size="sm"
61
- onClick={() => onEdit(memberType)}
62
- >
63
- <Pencil className="mr-2 h-4 w-4" />
64
- {t('common.edit')}
65
- </Button>
66
- ) : null}
67
66
  </div>
68
- </CardHeader>
69
- <CardContent className="space-y-3">
67
+ {canManageRoles ? (
68
+ <Button
69
+ variant="outline"
70
+ size="sm"
71
+ onClick={() => onEdit(memberType)}
72
+ >
73
+ <Pencil className="mr-2 h-4 w-4" />
74
+ {t('common.edit')}
75
+ </Button>
76
+ ) : null}
77
+ </div>
78
+
79
+ <div className="mt-4 space-y-3">
70
80
  {isLoading ? (
71
81
  <Skeleton className="h-24 rounded-lg" />
72
82
  ) : (
73
83
  <>
74
- <div className="font-semibold text-2xl tabular-nums">
75
- {enabledPermissionCount(role)}
76
- <span className="ml-1 font-normal text-base text-muted-foreground">
77
- / {permissionCount}
78
- </span>
84
+ <div>
85
+ <div className="flex items-baseline justify-between gap-1.5">
86
+ <div className="flex items-baseline gap-1.5">
87
+ <span className="font-bold text-2xl tabular-nums">
88
+ {enabled}
89
+ </span>
90
+ <span className="text-base text-muted-foreground">
91
+ / {permissionCount}
92
+ </span>
93
+ </div>
94
+ <span className="text-muted-foreground text-sm">
95
+ {t('ws-roles.permissions')}
96
+ </span>
97
+ </div>
98
+ <div className="mt-2 h-1.5 max-w-md overflow-hidden rounded-full bg-foreground/10">
99
+ <div
100
+ className={`h-full rounded-full ${barColor}`}
101
+ style={{ width: `${pct}%` }}
102
+ />
103
+ </div>
79
104
  </div>
80
105
  <div className="flex flex-wrap gap-2">
81
106
  <WorkspaceAccessPermissionPreview
@@ -86,7 +111,7 @@ export function WorkspaceAccessDefaultRoleCard({
86
111
  </div>
87
112
  </>
88
113
  )}
89
- </CardContent>
90
- </Card>
114
+ </div>
115
+ </div>
91
116
  );
92
117
  }
@@ -5,6 +5,7 @@ import {
5
5
  ShieldUser,
6
6
  User as UserIcon,
7
7
  UserMinus,
8
+ X,
8
9
  } from '@tuturuuu/icons';
9
10
  import type { InternalApiEnhancedWorkspaceMember } from '@tuturuuu/types';
10
11
  import { Avatar, AvatarFallback, AvatarImage } from '@tuturuuu/ui/avatar';
@@ -26,6 +27,13 @@ import {
26
27
  } from './member-filter-utils';
27
28
  import type { WorkspaceAccessLabels, WorkspaceAccessRole } from './types';
28
29
 
30
+ type GuestContext = InternalApiEnhancedWorkspaceMember & {
31
+ direct_board_guest?: boolean;
32
+ guest_board_count?: number;
33
+ guest_board_names?: string[];
34
+ guest_highest_permission?: 'edit' | 'view' | null;
35
+ };
36
+
29
37
  type Props = {
30
38
  canManageMembers: boolean;
31
39
  canManageRoles: boolean;
@@ -57,191 +65,192 @@ export function WorkspaceAccessMemberRow({
57
65
  const memberId = member.id ?? null;
58
66
  const availableRoles = getAvailableRolesForMember(member, roles);
59
67
  const memberName = getMemberDisplayName(member, t('common.unknown'));
60
- const guestContext = member as InternalApiEnhancedWorkspaceMember & {
61
- direct_board_guest?: boolean;
62
- guest_board_count?: number;
63
- guest_board_names?: string[];
64
- guest_highest_permission?: 'edit' | 'view' | null;
65
- };
68
+ const guest = member as GuestContext;
69
+ const canRemoveRoles = canManageRoles && memberId && !member.pending;
70
+
71
+ const accent = member.pending
72
+ ? 'before:bg-dynamic-orange'
73
+ : guest.direct_board_guest || member.workspace_member_type === 'GUEST'
74
+ ? 'before:bg-dynamic-blue'
75
+ : member.is_creator
76
+ ? 'before:bg-dynamic-yellow'
77
+ : 'before:bg-transparent';
66
78
 
67
79
  return (
68
80
  <article
69
- className={`relative rounded-lg border p-4 transition-colors ${
70
- member.pending
71
- ? 'border-dashed bg-transparent'
72
- : 'bg-primary-foreground/20 hover:bg-primary-foreground/30'
73
- }`}
81
+ className={`group relative overflow-hidden rounded-xl border bg-background pl-5 transition-all before:absolute before:inset-y-0 before:left-0 before:w-1 hover:border-foreground/20 hover:shadow-sm ${accent} ${member.pending ? 'border-dashed' : ''}`}
74
82
  >
75
- <div className="flex items-start gap-3">
76
- <Avatar>
77
- <AvatarImage src={member.avatar_url ?? undefined} />
78
- <AvatarFallback className="font-semibold">
79
- {member.display_name ? (
80
- getInitials(member.display_name)
81
- ) : (
82
- <UserIcon className="h-5 w-5" />
83
- )}
84
- </AvatarFallback>
85
- </Avatar>
86
-
87
- <div className="min-w-0 flex-1 space-y-2">
88
- <div className="flex flex-wrap items-center gap-1.5">
89
- <p className="truncate font-semibold lg:text-lg">{memberName}</p>
90
- {member.is_creator ? (
91
- <Badge className="h-5 gap-1 border-dynamic-yellow/50 bg-dynamic-yellow/10 px-1.5 text-dynamic-yellow text-xs">
92
- <Crown className="h-3 w-3" />
93
- {t('ws-members.creator_badge')}
94
- </Badge>
95
- ) : null}
96
- {member.pending ? (
97
- <Badge variant="outline" className="h-5 px-1.5 text-xs">
98
- {t('ws-members.invited')}
99
- </Badge>
100
- ) : null}
101
- {member.workspace_member_type === 'GUEST' ? (
102
- <Badge className="h-5 border-foreground/20 bg-foreground/5 px-1.5 text-foreground/80 text-xs">
103
- {guestContext.direct_board_guest
104
- ? t('ws-members.direct_board_guest_badge')
105
- : t('ws-members.guest_badge')}
106
- </Badge>
107
- ) : null}
108
- {guestContext.direct_board_guest ? (
109
- <Badge className="h-5 border-dynamic-blue/50 bg-dynamic-blue/10 px-1.5 text-dynamic-blue text-xs">
110
- {guestContext.guest_highest_permission === 'edit'
111
- ? t('ws-members.board_guest_can_edit')
112
- : t('ws-members.board_guest_can_view')}
113
- </Badge>
114
- ) : null}
115
- </div>
116
-
117
- <p className="truncate font-semibold text-muted-foreground text-sm">
118
- {member.email ||
119
- (member.handle ? `@${member.handle}` : t('common.unknown'))}
120
- </p>
121
- </div>
122
- </div>
83
+ <div className="p-4 pl-0">
84
+ <div className="flex items-start gap-3">
85
+ <Avatar className="h-11 w-11 border border-border">
86
+ <AvatarImage src={member.avatar_url ?? undefined} />
87
+ <AvatarFallback className="font-semibold">
88
+ {member.display_name ? (
89
+ getInitials(member.display_name)
90
+ ) : (
91
+ <UserIcon className="h-5 w-5" />
92
+ )}
93
+ </AvatarFallback>
94
+ </Avatar>
123
95
 
124
- <div className="mt-3 flex flex-wrap gap-2">
125
- {member.roles.length > 0 ? (
126
- member.roles.map((role) => (
127
- <Badge
128
- key={`${member.id ?? member.email}-${role.id}`}
129
- className="h-5 gap-1 border-dynamic-purple/50 bg-dynamic-purple/10 px-1.5 text-dynamic-purple text-xs"
130
- >
131
- <span>{role.name}</span>
132
- {canManageRoles && memberId && !member.pending ? (
133
- <button
134
- type="button"
135
- className="rounded-full px-1 hover:bg-foreground/10"
136
- onClick={() =>
137
- onRemoveRole({
138
- roleId: role.id,
139
- userId: memberId,
140
- })
141
- }
142
- aria-label={t('common.delete')}
143
- >
144
- x
145
- </button>
96
+ <div className="min-w-0 flex-1">
97
+ <div className="flex flex-wrap items-center gap-1.5">
98
+ <p className="truncate font-semibold text-base">{memberName}</p>
99
+ {member.is_creator ? (
100
+ <Badge className="h-5 gap-1 border-dynamic-yellow/50 bg-dynamic-yellow/10 px-1.5 text-dynamic-yellow text-xs">
101
+ <Crown className="h-3 w-3" />
102
+ {t('ws-members.creator_badge')}
103
+ </Badge>
146
104
  ) : null}
147
- </Badge>
148
- ))
149
- ) : (
150
- <Badge variant="outline" className="h-5 px-1.5 text-xs">
151
- {labels.noRolesLabel}
152
- </Badge>
153
- )}
154
- </div>
155
-
156
- {guestContext.direct_board_guest ? (
157
- <div className="mt-3 rounded-md border border-dynamic-blue/20 bg-dynamic-blue/5 p-3 text-sm">
158
- <div className="font-medium text-dynamic-blue">
159
- {t('ws-members.direct_board_guest_scope_title')}
160
- </div>
161
- <p className="mt-1 text-muted-foreground">
162
- {t('ws-members.direct_board_guest_scope_description', {
163
- count: guestContext.guest_board_count ?? 0,
164
- })}
165
- </p>
166
- {guestContext.guest_board_names?.length ? (
167
- <div className="mt-2 flex flex-wrap gap-1">
168
- {guestContext.guest_board_names.slice(0, 4).map((name) => (
169
- <Badge key={name} variant="outline" className="text-xs">
170
- {name}
105
+ {member.pending ? (
106
+ <Badge className="h-5 border-dynamic-orange/50 bg-dynamic-orange/10 px-1.5 text-dynamic-orange text-xs">
107
+ {t('ws-members.invited')}
171
108
  </Badge>
172
- ))}
173
- {guestContext.guest_board_names.length > 4 ? (
174
- <Badge variant="outline" className="text-xs">
175
- +{guestContext.guest_board_names.length - 4}
109
+ ) : null}
110
+ {member.workspace_member_type === 'GUEST' ? (
111
+ <Badge className="h-5 border-foreground/20 bg-foreground/5 px-1.5 text-foreground/80 text-xs">
112
+ {guest.direct_board_guest
113
+ ? t('ws-members.direct_board_guest_badge')
114
+ : t('ws-members.guest_badge')}
115
+ </Badge>
116
+ ) : null}
117
+ {guest.direct_board_guest ? (
118
+ <Badge className="h-5 border-dynamic-blue/50 bg-dynamic-blue/10 px-1.5 text-dynamic-blue text-xs">
119
+ {guest.guest_highest_permission === 'edit'
120
+ ? t('ws-members.board_guest_can_edit')
121
+ : t('ws-members.board_guest_can_view')}
176
122
  </Badge>
177
123
  ) : null}
178
124
  </div>
179
- ) : null}
125
+ <p className="mt-0.5 truncate text-muted-foreground text-sm">
126
+ {member.email ||
127
+ (member.handle ? `@${member.handle}` : t('common.unknown'))}
128
+ </p>
129
+ </div>
180
130
  </div>
181
- ) : null}
182
131
 
183
- <div className="mt-3 flex flex-col gap-3 border-t pt-3 text-sm md:flex-row md:items-center md:justify-between">
184
- {member.created_at ? (
185
- <div className="text-muted-foreground">
186
- <span>
187
- {t(
188
- member.pending
189
- ? 'ws-members.invited'
190
- : 'ws-members.member_since'
191
- )}
192
- </span>{' '}
193
- <span className="font-semibold text-foreground">
194
- {moment(member.created_at).locale(locale).fromNow()}
195
- </span>
132
+ <div className="mt-3 flex flex-wrap gap-1.5">
133
+ {member.roles.length > 0 ? (
134
+ member.roles.map((role) => (
135
+ <Badge
136
+ key={`${member.id ?? member.email}-${role.id}`}
137
+ className="h-5 gap-1 border-dynamic-purple/40 bg-dynamic-purple/10 px-1.5 text-dynamic-purple text-xs"
138
+ >
139
+ <span>{role.name}</span>
140
+ {canRemoveRoles ? (
141
+ <button
142
+ type="button"
143
+ className="rounded-full p-0.5 transition-colors hover:bg-dynamic-purple/20"
144
+ onClick={() =>
145
+ onRemoveRole({ roleId: role.id, userId: memberId })
146
+ }
147
+ aria-label={t('common.delete')}
148
+ >
149
+ <X className="h-3 w-3" />
150
+ </button>
151
+ ) : null}
152
+ </Badge>
153
+ ))
154
+ ) : (
155
+ <Badge variant="outline" className="h-5 px-1.5 text-xs">
156
+ {labels.noRolesLabel}
157
+ </Badge>
158
+ )}
159
+ </div>
160
+
161
+ {guest.direct_board_guest ? (
162
+ <div className="mt-3 rounded-lg border border-dynamic-blue/20 bg-dynamic-blue/5 p-3 text-sm">
163
+ <div className="font-medium text-dynamic-blue">
164
+ {t('ws-members.direct_board_guest_scope_title')}
165
+ </div>
166
+ <p className="mt-1 text-muted-foreground">
167
+ {t('ws-members.direct_board_guest_scope_description', {
168
+ count: guest.guest_board_count ?? 0,
169
+ })}
170
+ </p>
171
+ {guest.guest_board_names?.length ? (
172
+ <div className="mt-2 flex flex-wrap gap-1">
173
+ {guest.guest_board_names.slice(0, 4).map((name) => (
174
+ <Badge key={name} variant="outline" className="text-xs">
175
+ {name}
176
+ </Badge>
177
+ ))}
178
+ {guest.guest_board_names.length > 4 ? (
179
+ <Badge variant="outline" className="text-xs">
180
+ +{guest.guest_board_names.length - 4}
181
+ </Badge>
182
+ ) : null}
183
+ </div>
184
+ ) : null}
196
185
  </div>
197
186
  ) : null}
198
187
 
199
- <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
200
- {canManageRoles && memberId && !member.pending ? (
201
- <Select
202
- onValueChange={(roleId) =>
203
- onAssignRole({ roleId, userId: memberId })
204
- }
205
- >
206
- <SelectTrigger className="min-w-[180px]">
207
- <SelectValue placeholder={labels.assignRolePlaceholder} />
208
- </SelectTrigger>
209
- <SelectContent>
210
- {availableRoles.length === 0 ? (
211
- <SelectItem value="__no_roles__" disabled>
212
- {labels.noAdditionalRoles}
213
- </SelectItem>
214
- ) : (
215
- availableRoles.map((role) => (
216
- <SelectItem key={role.id} value={role.id}>
217
- {role.name}
218
- </SelectItem>
219
- ))
188
+ <div className="mt-3 flex flex-col gap-3 border-border border-t pt-3 text-sm md:flex-row md:items-center md:justify-between">
189
+ {member.created_at ? (
190
+ <div className="text-muted-foreground text-xs">
191
+ <span>
192
+ {t(
193
+ member.pending
194
+ ? 'ws-members.invited'
195
+ : 'ws-members.member_since'
220
196
  )}
221
- </SelectContent>
222
- </Select>
223
- ) : null}
224
-
225
- {canManageMembers && !member.is_creator ? (
226
- <Button
227
- variant="outline"
228
- disabled={isMutating}
229
- onClick={() =>
230
- onRemoveMember({
231
- email: member.email,
232
- userId: member.pending ? null : member.id,
233
- })
234
- }
235
- >
236
- <UserMinus className="mr-2 h-4 w-4" />
237
- {labels.removeMemberAction}
238
- </Button>
239
- ) : (
240
- <div className="inline-flex items-center gap-2 rounded-md border px-3 py-2 text-muted-foreground text-sm">
241
- <ShieldUser className="h-4 w-4" />
242
- {labels.protectedMemberLabel}
197
+ </span>{' '}
198
+ <span className="font-semibold text-foreground">
199
+ {moment(member.created_at).locale(locale).fromNow()}
200
+ </span>
243
201
  </div>
202
+ ) : (
203
+ <span />
244
204
  )}
205
+
206
+ <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
207
+ {canRemoveRoles ? (
208
+ <Select
209
+ onValueChange={(roleId) =>
210
+ onAssignRole({ roleId, userId: memberId })
211
+ }
212
+ >
213
+ <SelectTrigger className="h-9 min-w-[180px]">
214
+ <SelectValue placeholder={labels.assignRolePlaceholder} />
215
+ </SelectTrigger>
216
+ <SelectContent>
217
+ {availableRoles.length === 0 ? (
218
+ <SelectItem value="__no_roles__" disabled>
219
+ {labels.noAdditionalRoles}
220
+ </SelectItem>
221
+ ) : (
222
+ availableRoles.map((role) => (
223
+ <SelectItem key={role.id} value={role.id}>
224
+ {role.name}
225
+ </SelectItem>
226
+ ))
227
+ )}
228
+ </SelectContent>
229
+ </Select>
230
+ ) : null}
231
+
232
+ {canManageMembers && !member.is_creator ? (
233
+ <Button
234
+ variant="outline"
235
+ size="sm"
236
+ disabled={isMutating}
237
+ onClick={() =>
238
+ onRemoveMember({
239
+ email: member.email,
240
+ userId: member.pending ? null : member.id,
241
+ })
242
+ }
243
+ >
244
+ <UserMinus className="mr-2 h-4 w-4" />
245
+ {labels.removeMemberAction}
246
+ </Button>
247
+ ) : (
248
+ <div className="inline-flex items-center gap-2 rounded-md border border-border px-3 py-1.5 text-muted-foreground text-xs">
249
+ <ShieldUser className="h-3.5 w-3.5" />
250
+ {labels.protectedMemberLabel}
251
+ </div>
252
+ )}
253
+ </div>
245
254
  </div>
246
255
  </div>
247
256
  </article>
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { UsersRound } from '@tuturuuu/icons';
3
4
  import type { InternalApiEnhancedWorkspaceMember } from '@tuturuuu/types';
4
5
  import { Skeleton } from '@tuturuuu/ui/skeleton';
5
6
  import { useTranslations } from 'next-intl';
@@ -43,22 +44,27 @@ export function WorkspaceAccessMembers({
43
44
  if (isLoading) {
44
45
  return (
45
46
  <div className="grid gap-4 lg:grid-cols-2">
46
- <Skeleton className="h-52 rounded-lg" />
47
- <Skeleton className="h-52 rounded-lg" />
48
- <Skeleton className="h-52 rounded-lg" />
49
- <Skeleton className="h-52 rounded-lg" />
47
+ <Skeleton className="h-48 rounded-xl" />
48
+ <Skeleton className="h-48 rounded-xl" />
49
+ <Skeleton className="h-48 rounded-xl" />
50
+ <Skeleton className="h-48 rounded-xl" />
50
51
  </div>
51
52
  );
52
53
  }
53
54
 
54
55
  if (members.length === 0) {
55
56
  return (
56
- <div className="flex min-h-56 items-center justify-center rounded-lg border border-dashed p-6 text-center text-muted-foreground text-sm">
57
- {searchTerm.trim()
58
- ? t('ws-members.no_members_match')
59
- : status === 'invited'
60
- ? t('ws-members.no_invited_members_found')
61
- : t('ws-members.no_members_found')}
57
+ <div className="flex min-h-56 flex-col items-center justify-center gap-3 rounded-xl border border-border border-dashed p-8 text-center">
58
+ <div className="flex h-12 w-12 items-center justify-center rounded-full bg-foreground/5 text-muted-foreground">
59
+ <UsersRound className="h-6 w-6" />
60
+ </div>
61
+ <p className="text-muted-foreground text-sm">
62
+ {searchTerm.trim()
63
+ ? t('ws-members.no_members_match')
64
+ : status === 'invited'
65
+ ? t('ws-members.no_invited_members_found')
66
+ : t('ws-members.no_members_found')}
67
+ </p>
62
68
  </div>
63
69
  );
64
70
  }