@tuturuuu/ui 0.1.0 → 0.3.1

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 (128) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/package.json +82 -70
  3. package/src/components/ui/__tests__/avatar.test.tsx +8 -5
  4. package/src/components/ui/calendar-app/components/calendar-connections-compact.tsx +414 -0
  5. package/src/components/ui/calendar-app/components/calendar-connections-manager.tsx +5 -1
  6. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +529 -0
  7. package/src/components/ui/calendar-app/components/calendar-connections-unified.tsx +26 -1429
  8. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +711 -0
  9. package/src/components/ui/chart.test.tsx +29 -0
  10. package/src/components/ui/chart.tsx +12 -3
  11. package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +43 -13
  12. package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +138 -74
  13. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +70 -0
  14. package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +60 -1
  15. package/src/components/ui/chat/chat-agent-details-sidebar.tsx +13 -5
  16. package/src/components/ui/chat/chat-sidebar-panel.test.tsx +110 -0
  17. package/src/components/ui/chat/chat-sidebar-panel.tsx +13 -3
  18. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +24 -1
  19. package/src/components/ui/custom/__tests__/tuturuuu-logo.test.ts +12 -3
  20. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +39 -0
  21. package/src/components/ui/custom/common-footer.tsx +16 -1
  22. package/src/components/ui/custom/production-indicator.tsx +1 -1
  23. package/src/components/ui/custom/settings/sidebar-settings.tsx +1 -1
  24. package/src/components/ui/custom/settings/task-settings.tsx +18 -0
  25. package/src/components/ui/custom/settings-dialog-shell.tsx +38 -23
  26. package/src/components/ui/custom/sidebar-context-compile-graph.test.ts +60 -0
  27. package/src/components/ui/custom/sidebar-context.tsx +61 -61
  28. package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +123 -0
  29. package/src/components/ui/custom/tuturuuu-logo-urls.ts +6 -0
  30. package/src/components/ui/custom/tuturuuu-logo.tsx +25 -7
  31. package/src/components/ui/custom/workspace-select-helpers.ts +20 -0
  32. package/src/components/ui/custom/workspace-select.tsx +33 -12
  33. package/src/components/ui/finance/invoices/components/invoice-checkout-summary.tsx +7 -1
  34. package/src/components/ui/finance/invoices/components/invoice-payment-settings.tsx +3 -0
  35. package/src/components/ui/finance/invoices/components/invoice-products-permission-warning.tsx +58 -0
  36. package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +12 -20
  37. package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +10 -9
  38. package/src/components/ui/finance/invoices/hooks/use-subscription-invoice-content.ts +10 -5
  39. package/src/components/ui/finance/invoices/hooks.ts +75 -20
  40. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +137 -0
  41. package/src/components/ui/finance/invoices/new-invoice-page.tsx +86 -37
  42. package/src/components/ui/finance/invoices/product-selection.test.tsx +8 -26
  43. package/src/components/ui/finance/invoices/product-selection.tsx +2 -10
  44. package/src/components/ui/finance/invoices/standard-invoice.tsx +88 -26
  45. package/src/components/ui/finance/invoices/subscription-invoice.tsx +154 -46
  46. package/src/components/ui/finance/invoices/utils.test.ts +50 -0
  47. package/src/components/ui/finance/invoices/utils.ts +75 -17
  48. package/src/components/ui/finance/shared/finance-display-amount.tsx +3 -1
  49. package/src/components/ui/finance/shared/finance-permission-warning-dialog.test.tsx +34 -0
  50. package/src/components/ui/finance/shared/finance-permission-warning-dialog.tsx +157 -0
  51. package/src/components/ui/finance/transactions/form-basic-tab.tsx +8 -0
  52. package/src/components/ui/finance/transactions/form-more-tab.tsx +8 -0
  53. package/src/components/ui/finance/transactions/form-types.ts +2 -0
  54. package/src/components/ui/finance/transactions/form.test.tsx +43 -0
  55. package/src/components/ui/finance/transactions/form.tsx +60 -0
  56. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +27 -0
  57. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +13 -1
  58. package/src/components/ui/finance/transactions/transactions-infinite-page.tsx +4 -0
  59. package/src/components/ui/finance/transactions/transactions-page.tsx +23 -1
  60. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  61. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +5 -0
  62. package/src/components/ui/legacy/calendar/calendar-content.tsx +9 -1
  63. package/src/components/ui/legacy/calendar/event-modal.tsx +146 -2
  64. package/src/components/ui/legacy/calendar/event-preview-popover.tsx +200 -0
  65. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +76 -0
  66. package/src/components/ui/legacy/calendar/smart-calendar.tsx +13 -1
  67. package/src/components/ui/legacy/meet/page.test.ts +180 -0
  68. package/src/components/ui/legacy/meet/page.tsx +87 -39
  69. package/src/components/ui/legacy/meet/planId/page.tsx +10 -4
  70. package/src/components/ui/text-editor/__tests__/task-mention-chip.test.tsx +203 -6
  71. package/src/components/ui/text-editor/task-mention-chip.tsx +29 -7
  72. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +79 -25
  73. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-external-workspaces.test.tsx +392 -0
  74. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.test.tsx +57 -0
  75. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.tsx +106 -0
  76. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +106 -161
  77. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-assignees.ts +96 -150
  78. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-labels.ts +63 -79
  79. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-projects.ts +64 -83
  80. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +115 -155
  81. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-utils.ts +319 -2
  82. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operations.ts +8 -1
  83. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +63 -37
  84. package/src/components/ui/tu-do/boards/boardId/kanban/kanban-column-collapse.ts +16 -0
  85. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +46 -0
  86. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +5 -3
  87. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +19 -7
  88. package/src/components/ui/tu-do/boards/boardId/menus/__tests__/task-menus.test.tsx +181 -2
  89. package/src/components/ui/tu-do/boards/boardId/menus/index.ts +1 -0
  90. package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-menu.tsx +463 -0
  91. package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-utils.ts +109 -0
  92. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +4 -0
  93. package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardCheckbox.tsx +6 -3
  94. package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardDates.tsx +26 -9
  95. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-checkbox-style.ts +39 -0
  96. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.test.ts +43 -0
  97. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +33 -0
  98. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.test.ts +31 -0
  99. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.ts +9 -0
  100. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.test.tsx +124 -0
  101. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.tsx +88 -0
  102. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +151 -76
  103. package/src/components/ui/tu-do/boards/boardId/task-card/task-scheduling-badge.tsx +174 -0
  104. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +34 -13
  105. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +54 -1
  106. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +158 -0
  107. package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +5 -2
  108. package/src/components/ui/tu-do/shared/board-client.tsx +12 -2
  109. package/src/components/ui/tu-do/shared/board-views.tsx +195 -328
  110. package/src/components/ui/tu-do/shared/list-view.tsx +18 -8
  111. package/src/components/ui/tu-do/shared/task-due-date-visibility.test.ts +72 -0
  112. package/src/components/ui/tu-do/shared/task-due-date-visibility.ts +38 -0
  113. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-realtime-sync.test.tsx +37 -9
  114. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +6 -3
  115. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +89 -70
  116. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +2 -2
  117. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +33 -0
  118. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +74 -3
  119. package/src/hooks/__tests__/use-task-actions.test.tsx +118 -0
  120. package/src/hooks/__tests__/use-user-config.test.tsx +65 -0
  121. package/src/hooks/__tests__/use-workspace-presence.test.tsx +1 -1
  122. package/src/hooks/use-calendar-sync.tsx +22 -277
  123. package/src/hooks/use-calendar.tsx +95 -525
  124. package/src/hooks/use-task-actions.ts +43 -117
  125. package/src/hooks/use-user-config.ts +1 -1
  126. package/src/hooks/use-workspace-config.ts +6 -2
  127. package/src/hooks/use-workspace-presence.ts +1 -1
  128. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-bar.tsx +0 -94
@@ -0,0 +1,76 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { render, screen } from '@testing-library/react';
4
+ import type { Workspace } from '@tuturuuu/types';
5
+ import type { ReactNode } from 'react';
6
+ import { describe, expect, it, vi } from 'vitest';
7
+ import { SmartCalendar } from './smart-calendar';
8
+
9
+ vi.mock('@tuturuuu/ui/hooks/use-calendar', () => ({
10
+ CalendarProvider: ({ children }: { children: ReactNode }) => (
11
+ <div data-testid="calendar-provider">{children}</div>
12
+ ),
13
+ }));
14
+
15
+ vi.mock('./settings/settings-context', () => ({
16
+ CalendarSettingsProvider: ({ children }: { children: ReactNode }) => (
17
+ <div data-testid="settings-provider">{children}</div>
18
+ ),
19
+ }));
20
+
21
+ vi.mock('./calendar-content', () => ({
22
+ CalendarContent: ({ extras }: { extras?: ReactNode }) => (
23
+ <div data-testid="calendar-content">
24
+ <div data-testid="header-extras">{extras}</div>
25
+ </div>
26
+ ),
27
+ }));
28
+
29
+ vi.mock('../../calendar-app/components/calendar-connections-unified', () => ({
30
+ default: ({ wsId }: { wsId: string }) => (
31
+ <span data-testid="connections-manager">{wsId}</span>
32
+ ),
33
+ }));
34
+
35
+ const baseProps = {
36
+ locale: 'en',
37
+ t: (key: string) => key,
38
+ useQuery: vi.fn(),
39
+ useQueryClient: vi.fn(),
40
+ };
41
+
42
+ describe('SmartCalendar', () => {
43
+ it('renders the compact connections manager before existing header extras', () => {
44
+ render(
45
+ <SmartCalendar
46
+ {...baseProps}
47
+ workspace={{ id: 'workspace-1' } as Workspace}
48
+ extras={<span data-testid="custom-extra">extra</span>}
49
+ />
50
+ );
51
+
52
+ expect(screen.getByTestId('connections-manager').textContent).toBe(
53
+ 'workspace-1'
54
+ );
55
+ expect(
56
+ Array.from(screen.getByTestId('header-extras').children).map((element) =>
57
+ element.getAttribute('data-testid')
58
+ )
59
+ ).toEqual(['connections-manager', 'custom-extra']);
60
+ });
61
+
62
+ it('keeps existing header extras when the manager is disabled', () => {
63
+ render(
64
+ <SmartCalendar
65
+ {...baseProps}
66
+ workspace={{ id: 'workspace-1' } as Workspace}
67
+ disabled
68
+ showConnectionsManager={false}
69
+ extras={<span data-testid="custom-extra">extra</span>}
70
+ />
71
+ );
72
+
73
+ expect(screen.queryByTestId('connections-manager')).toBeNull();
74
+ expect(screen.getByTestId('custom-extra').textContent).toBe('extra');
75
+ });
76
+ });
@@ -6,6 +6,7 @@ import type {
6
6
  } from '@tuturuuu/types';
7
7
  import { CalendarProvider } from '@tuturuuu/ui/hooks/use-calendar';
8
8
  import type { CalendarView } from '../../../../hooks/use-view-transition';
9
+ import CalendarConnectionsUnified from '../../calendar-app/components/calendar-connections-unified';
9
10
  import { CalendarContent } from './calendar-content';
10
11
  import {
11
12
  type CalendarSettings,
@@ -26,6 +27,7 @@ export const SmartCalendar = ({
26
27
  overlay,
27
28
  initialSettings,
28
29
  onSaveSettings,
30
+ showConnectionsManager = true,
29
31
  }: {
30
32
  t: any;
31
33
  locale: string;
@@ -46,12 +48,22 @@ export const SmartCalendar = ({
46
48
  overlay?: React.ReactNode;
47
49
  initialSettings?: Partial<CalendarSettings>;
48
50
  onSaveSettings?: (settings: CalendarSettings) => Promise<void>;
51
+ showConnectionsManager?: boolean;
49
52
  }) => {
50
53
  const handleSaveSettings = async (newSettings: CalendarSettings) => {
51
54
  if (onSaveSettings) {
52
55
  await onSaveSettings(newSettings);
53
56
  }
54
57
  };
58
+ const headerExtras =
59
+ showConnectionsManager && workspace?.id ? (
60
+ <>
61
+ <CalendarConnectionsUnified wsId={workspace.id} />
62
+ {extras}
63
+ </>
64
+ ) : (
65
+ extras
66
+ );
55
67
 
56
68
  return (
57
69
  <CalendarProvider
@@ -73,7 +85,7 @@ export const SmartCalendar = ({
73
85
  enableHeader={enableHeader}
74
86
  experimentalGoogleToken={experimentalGoogleToken}
75
87
  externalState={externalState}
76
- extras={extras}
88
+ extras={headerExtras}
77
89
  overlay={overlay}
78
90
  />
79
91
  </CalendarSettingsProvider>
@@ -0,0 +1,180 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { getMeetTogetherPlansData } from './page';
3
+
4
+ const mocks = vi.hoisted(() => ({
5
+ createAdminClient: vi.fn(),
6
+ createClient: vi.fn(),
7
+ }));
8
+
9
+ vi.mock('@tuturuuu/supabase/next/server', () => ({
10
+ createAdminClient: (...args: Parameters<typeof mocks.createAdminClient>) =>
11
+ mocks.createAdminClient(...args),
12
+ createClient: (...args: Parameters<typeof mocks.createClient>) =>
13
+ mocks.createClient(...args),
14
+ }));
15
+
16
+ vi.mock('next-intl/server', () => ({
17
+ getLocale: vi.fn().mockResolvedValue('en'),
18
+ getTranslations: vi.fn().mockResolvedValue((key: string) => key),
19
+ }));
20
+
21
+ type QueryCall = {
22
+ column?: string;
23
+ table: string;
24
+ type: 'select' | 'eq' | 'neq' | 'order' | 'in';
25
+ value?: unknown;
26
+ };
27
+
28
+ function createBuilder(table: string, data: unknown[], calls: QueryCall[]) {
29
+ const builder = Object.assign(Promise.resolve({ data, error: null }), {
30
+ eq: vi.fn((column: string, value: unknown) => {
31
+ calls.push({ column, table, type: 'eq', value });
32
+ return builder;
33
+ }),
34
+ in: vi.fn((column: string, value: unknown) => {
35
+ calls.push({ column, table, type: 'in', value });
36
+ return builder;
37
+ }),
38
+ neq: vi.fn((column: string, value: unknown) => {
39
+ calls.push({ column, table, type: 'neq', value });
40
+ return builder;
41
+ }),
42
+ order: vi.fn((column: string, value: unknown) => {
43
+ calls.push({ column, table, type: 'order', value });
44
+ return builder;
45
+ }),
46
+ select: vi.fn((column: string) => {
47
+ calls.push({ column, table, type: 'select' });
48
+ return builder;
49
+ }),
50
+ });
51
+
52
+ return builder;
53
+ }
54
+
55
+ describe('MeetTogether plans data', () => {
56
+ beforeEach(() => {
57
+ vi.clearAllMocks();
58
+ mocks.createClient.mockResolvedValue({
59
+ auth: {
60
+ getUser: vi.fn().mockResolvedValue({
61
+ data: { user: { id: 'user-1' } },
62
+ }),
63
+ },
64
+ });
65
+ });
66
+
67
+ it('consolidates personal workspace plans with previously interacted plans', async () => {
68
+ const calls: QueryCall[] = [];
69
+ const personalPlan = {
70
+ created_at: '2026-01-01T00:00:00.000Z',
71
+ creator_id: 'user-1',
72
+ id: 'personal-created',
73
+ ws_id: 'personal-ws',
74
+ };
75
+ const interactedPlan = {
76
+ created_at: '2026-01-03T00:00:00.000Z',
77
+ creator_id: 'other-user',
78
+ id: 'team-interacted',
79
+ ws_id: 'team-ws',
80
+ };
81
+ const participant = {
82
+ display_name: 'User',
83
+ is_guest: false,
84
+ plan_id: 'team-interacted',
85
+ timeblock_count: 1,
86
+ user_id: 'user-1',
87
+ };
88
+ const builders = [
89
+ createBuilder('meet_together_plans', [personalPlan], calls),
90
+ createBuilder(
91
+ 'meet_together_user_timeblocks',
92
+ [interactedPlan, personalPlan],
93
+ calls
94
+ ),
95
+ createBuilder('meet_together_users', [participant], calls),
96
+ ];
97
+
98
+ mocks.createAdminClient.mockResolvedValue({
99
+ from: vi.fn(() => builders.shift()),
100
+ });
101
+
102
+ const result = await getMeetTogetherPlansData({
103
+ scope: 'personal-consolidated',
104
+ wsId: 'personal-ws',
105
+ });
106
+
107
+ expect(result.data.map((plan) => plan.id)).toEqual([
108
+ 'team-interacted',
109
+ 'personal-created',
110
+ ]);
111
+ expect(result.totalCount).toBe(2);
112
+ expect(result.data[0]?.participants).toEqual([participant]);
113
+ expect(calls).toEqual(
114
+ expect.arrayContaining([
115
+ {
116
+ column: 'ws_id',
117
+ table: 'meet_together_plans',
118
+ type: 'eq',
119
+ value: 'personal-ws',
120
+ },
121
+ {
122
+ column: 'user_id',
123
+ table: 'meet_together_user_timeblocks',
124
+ type: 'eq',
125
+ value: 'user-1',
126
+ },
127
+ ])
128
+ );
129
+ expect(calls).not.toEqual(
130
+ expect.arrayContaining([
131
+ expect.objectContaining({
132
+ column: 'meet_together_plans.ws_id',
133
+ table: 'meet_together_user_timeblocks',
134
+ type: 'eq',
135
+ }),
136
+ ])
137
+ );
138
+ });
139
+
140
+ it('keeps team workspace plans scoped to the active workspace', async () => {
141
+ const calls: QueryCall[] = [];
142
+ const builders = [
143
+ createBuilder('meet_together_plans', [], calls),
144
+ createBuilder('meet_together_user_timeblocks', [], calls),
145
+ createBuilder('meet_together_users', [], calls),
146
+ ];
147
+
148
+ mocks.createAdminClient.mockResolvedValue({
149
+ from: vi.fn(() => builders.shift()),
150
+ });
151
+
152
+ await getMeetTogetherPlansData({
153
+ scope: 'workspace',
154
+ wsId: 'team-ws',
155
+ });
156
+
157
+ expect(calls).toEqual(
158
+ expect.arrayContaining([
159
+ {
160
+ column: 'ws_id',
161
+ table: 'meet_together_plans',
162
+ type: 'eq',
163
+ value: 'team-ws',
164
+ },
165
+ {
166
+ column: 'meet_together_plans.ws_id',
167
+ table: 'meet_together_user_timeblocks',
168
+ type: 'eq',
169
+ value: 'team-ws',
170
+ },
171
+ {
172
+ column: 'meet_together_plans.creator_id',
173
+ table: 'meet_together_user_timeblocks',
174
+ type: 'neq',
175
+ value: 'user-1',
176
+ },
177
+ ])
178
+ );
179
+ });
180
+ });
@@ -27,8 +27,18 @@ export interface MeetTogetherPlanWithParticipants extends MeetTogetherPlan {
27
27
  }>;
28
28
  }
29
29
 
30
+ type MeetTogetherPlanRow = Record<string, unknown> & {
31
+ created_at?: string | null;
32
+ creator_id?: string | null;
33
+ description?: string | null;
34
+ id?: string | null;
35
+ name?: string | null;
36
+ ws_id?: string | null;
37
+ };
38
+
30
39
  // Server component props type
31
40
  interface MeetTogetherPageProps {
41
+ scope?: MeetTogetherPlansScope;
32
42
  wsId?: string;
33
43
  path?: string;
34
44
  searchParams?: Promise<{
@@ -38,7 +48,10 @@ interface MeetTogetherPageProps {
38
48
  }>;
39
49
  }
40
50
 
51
+ export type MeetTogetherPlansScope = 'workspace' | 'personal-consolidated';
52
+
41
53
  export async function MeetTogetherPage({
54
+ scope = 'workspace',
42
55
  wsId,
43
56
  path,
44
57
  searchParams,
@@ -55,7 +68,7 @@ export async function MeetTogetherPage({
55
68
  data: plans,
56
69
  user,
57
70
  totalCount,
58
- } = await getData({ wsId, page, pageSize });
71
+ } = await getMeetTogetherPlansData({ scope, wsId, page, pageSize });
59
72
  const totalPages = Math.ceil(totalCount / pageSize);
60
73
 
61
74
  return (
@@ -222,11 +235,13 @@ export async function MeetTogetherPage({
222
235
  );
223
236
  }
224
237
 
225
- async function getData({
238
+ export async function getMeetTogetherPlansData({
239
+ scope = 'workspace',
226
240
  wsId,
227
241
  page = 1,
228
242
  pageSize = 9,
229
243
  }: {
244
+ scope?: MeetTogetherPlansScope;
230
245
  wsId?: string;
231
246
  page?: number;
232
247
  pageSize?: number;
@@ -241,47 +256,80 @@ async function getData({
241
256
 
242
257
  const sbAdmin = await createAdminClient();
243
258
 
244
- const createdPlansQuery = sbAdmin
245
- .from('meet_together_plans')
246
- .select('*')
247
- .eq('creator_id', user.id)
248
- .order('created_at', { ascending: false });
249
-
250
- const joinedPlansQuery = sbAdmin
251
- .from('meet_together_user_timeblocks')
252
- .select('...meet_together_plans!inner(*)')
253
- .eq('user_id', user.id)
254
- .neq('meet_together_plans.creator_id', user.id)
255
- .order('created_at', {
256
- ascending: false,
257
- referencedTable: 'meet_together_plans',
259
+ const planMap = new Map<string, MeetTogetherPlan>();
260
+ const addPlans = (plans: MeetTogetherPlanRow[] | null | undefined) => {
261
+ plans?.forEach((plan) => {
262
+ const planId = plan.id ?? undefined;
263
+
264
+ if (planId && !planMap.has(planId)) {
265
+ planMap.set(planId, {
266
+ ...plan,
267
+ agenda_content: plan.agenda_content ?? undefined,
268
+ created_at: plan.created_at ?? undefined,
269
+ creator_id: plan.creator_id ?? undefined,
270
+ description: plan.description ?? undefined,
271
+ id: planId,
272
+ name: plan.name ?? undefined,
273
+ ws_id: plan.ws_id ?? undefined,
274
+ } as MeetTogetherPlan);
275
+ }
258
276
  });
277
+ };
259
278
 
260
- if (wsId) {
261
- createdPlansQuery.eq('ws_id', wsId);
262
- joinedPlansQuery.eq('meet_together_plans.ws_id', wsId);
263
- }
264
-
265
- const [createdPlansResult, joinedPlansResult] = await Promise.all([
266
- createdPlansQuery,
267
- joinedPlansQuery,
268
- ]);
279
+ if (scope === 'personal-consolidated' && wsId) {
280
+ const personalPlansQuery = sbAdmin
281
+ .from('meet_together_plans')
282
+ .select('*')
283
+ .eq('creator_id', user.id)
284
+ .eq('ws_id', wsId)
285
+ .order('created_at', { ascending: false });
286
+
287
+ const interactedPlansQuery = sbAdmin
288
+ .from('meet_together_user_timeblocks')
289
+ .select('...meet_together_plans!inner(*)')
290
+ .eq('user_id', user.id)
291
+ .order('created_at', {
292
+ ascending: false,
293
+ referencedTable: 'meet_together_plans',
294
+ });
295
+
296
+ const [personalPlansResult, interactedPlansResult] = await Promise.all([
297
+ personalPlansQuery,
298
+ interactedPlansQuery,
299
+ ]);
300
+
301
+ addPlans(personalPlansResult.data);
302
+ addPlans(interactedPlansResult.data);
303
+ } else {
304
+ const createdPlansQuery = sbAdmin
305
+ .from('meet_together_plans')
306
+ .select('*')
307
+ .eq('creator_id', user.id)
308
+ .order('created_at', { ascending: false });
309
+
310
+ const joinedPlansQuery = sbAdmin
311
+ .from('meet_together_user_timeblocks')
312
+ .select('...meet_together_plans!inner(*)')
313
+ .eq('user_id', user.id)
314
+ .neq('meet_together_plans.creator_id', user.id)
315
+ .order('created_at', {
316
+ ascending: false,
317
+ referencedTable: 'meet_together_plans',
318
+ });
319
+
320
+ if (wsId) {
321
+ createdPlansQuery.eq('ws_id', wsId);
322
+ joinedPlansQuery.eq('meet_together_plans.ws_id', wsId);
323
+ }
269
324
 
270
- const { data: createdPlans } = createdPlansResult;
271
- const { data: joinedPlans } = joinedPlansResult;
325
+ const [createdPlansResult, joinedPlansResult] = await Promise.all([
326
+ createdPlansQuery,
327
+ joinedPlansQuery,
328
+ ]);
272
329
 
273
- // Combine and deduplicate
274
- const planMap = new Map();
275
- createdPlans?.forEach((plan) => {
276
- if (!planMap.has(plan.id)) {
277
- planMap.set(plan.id, plan);
278
- }
279
- });
280
- joinedPlans?.forEach((plan) => {
281
- if (!planMap.has(plan.id)) {
282
- planMap.set(plan.id, plan);
283
- }
284
- });
330
+ addPlans(createdPlansResult.data);
331
+ addPlans(joinedPlansResult.data);
332
+ }
285
333
 
286
334
  const allPlans = Array.from(planMap.values()).sort(
287
335
  (a, b) =>
@@ -17,23 +17,29 @@ interface Props {
17
17
  params: Promise<{
18
18
  planId: string;
19
19
  }>;
20
+ actorUserId?: string | null;
20
21
  baseUrl: string;
21
22
  }
22
23
 
23
24
  export default async function MeetTogetherPlanDetailsPage({
25
+ actorUserId,
24
26
  params,
25
27
  baseUrl,
26
28
  }: Props) {
27
29
  const { planId } = await params;
28
30
 
29
31
  const platformUser = await getCurrentUser();
30
- const plan = await getPlan(planId);
31
- const users: PlanUser[] = await getUsers(planId);
32
- const polls = await getPollsForPlan(planId);
33
- const timeblocks = await getTimeBlocks(planId);
32
+ const plan = await getPlan(planId, { actorUserId });
34
33
 
35
34
  if (!plan) return notFound();
36
35
 
36
+ const canonicalPlanId = plan.id;
37
+ if (!canonicalPlanId) return notFound();
38
+
39
+ const users: PlanUser[] = await getUsers(canonicalPlanId);
40
+ const polls = await getPollsForPlan(canonicalPlanId);
41
+ const timeblocks = await getTimeBlocks(canonicalPlanId);
42
+
37
43
  return (
38
44
  <div className="flex min-h-screen w-full flex-col items-center">
39
45
  <Suspense fallback={null}>