@btst/stack 1.11.0 → 1.12.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 (163) hide show
  1. package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.cjs +1 -1
  2. package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.mjs +1 -1
  3. package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.cjs +77 -0
  4. package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.mjs +79 -3
  5. package/dist/node_modules/.pnpm/@radix-ui_react-avatar@1.1.11_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react_850cfbef1935a6e49a6ad6c93c7ca70d/node_modules/@radix-ui/react-avatar/dist/index.cjs +140 -0
  6. package/dist/node_modules/.pnpm/@radix-ui_react-avatar@1.1.11_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react_850cfbef1935a6e49a6ad6c93c7ca70d/node_modules/@radix-ui/react-avatar/dist/index.mjs +119 -0
  7. package/dist/node_modules/.pnpm/@radix-ui_react-context@1.1.3_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-context/dist/index.cjs +80 -0
  8. package/dist/node_modules/.pnpm/@radix-ui_react-context@1.1.3_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-context/dist/index.mjs +64 -0
  9. package/dist/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.cjs +18 -0
  10. package/dist/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.mjs +16 -0
  11. package/dist/packages/better-stack/src/plugins/kanban/api/plugin.cjs +846 -0
  12. package/dist/packages/better-stack/src/plugins/kanban/api/plugin.mjs +844 -0
  13. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/board-form.cjs +85 -0
  14. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/board-form.mjs +83 -0
  15. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/column-form.cjs +72 -0
  16. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/column-form.mjs +70 -0
  17. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/task-form.cjs +200 -0
  18. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/task-form.mjs +198 -0
  19. package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/board-skeleton.cjs +47 -0
  20. package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/board-skeleton.mjs +45 -0
  21. package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/boards-list-skeleton.cjs +30 -0
  22. package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/boards-list-skeleton.mjs +28 -0
  23. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/404-page.cjs +27 -0
  24. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/404-page.mjs +25 -0
  25. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.cjs +31 -0
  26. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.internal.cjs +458 -0
  27. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.internal.mjs +456 -0
  28. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.mjs +29 -0
  29. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.cjs +30 -0
  30. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.internal.cjs +72 -0
  31. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.internal.mjs +70 -0
  32. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.mjs +28 -0
  33. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.cjs +30 -0
  34. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.internal.cjs +51 -0
  35. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.internal.mjs +49 -0
  36. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.mjs +28 -0
  37. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/column-content.cjs +76 -0
  38. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/column-content.mjs +74 -0
  39. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/default-error.cjs +27 -0
  40. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/default-error.mjs +25 -0
  41. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/empty-state.cjs +32 -0
  42. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/empty-state.mjs +30 -0
  43. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/kanban-board.cjs +78 -0
  44. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/kanban-board.mjs +76 -0
  45. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/page-wrapper.cjs +15 -0
  46. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/page-wrapper.mjs +13 -0
  47. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/task-card.cjs +68 -0
  48. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/task-card.mjs +66 -0
  49. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/user-avatar.cjs +32 -0
  50. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/user-avatar.mjs +30 -0
  51. package/dist/packages/better-stack/src/plugins/kanban/client/hooks/kanban-hooks.cjs +391 -0
  52. package/dist/packages/better-stack/src/plugins/kanban/client/hooks/kanban-hooks.mjs +381 -0
  53. package/dist/packages/better-stack/src/plugins/kanban/client/plugin.cjs +290 -0
  54. package/dist/packages/better-stack/src/plugins/kanban/client/plugin.mjs +288 -0
  55. package/dist/packages/better-stack/src/plugins/kanban/db.cjs +125 -0
  56. package/dist/packages/better-stack/src/plugins/kanban/db.mjs +123 -0
  57. package/dist/packages/better-stack/src/plugins/kanban/schemas.cjs +117 -0
  58. package/dist/packages/better-stack/src/plugins/kanban/schemas.mjs +102 -0
  59. package/dist/packages/better-stack/src/plugins/kanban/utils.cjs +49 -0
  60. package/dist/packages/better-stack/src/plugins/kanban/utils.mjs +45 -0
  61. package/dist/packages/ui/src/components/avatar.cjs +58 -0
  62. package/dist/packages/ui/src/components/avatar.mjs +54 -0
  63. package/dist/packages/ui/src/components/command.cjs +3 -3
  64. package/dist/packages/ui/src/components/command.mjs +3 -3
  65. package/dist/packages/ui/src/components/form-builder/index.mjs +2 -2
  66. package/dist/packages/ui/src/components/kanban.cjs +835 -0
  67. package/dist/packages/ui/src/components/kanban.mjs +805 -0
  68. package/dist/packages/ui/src/components/popover.cjs +8 -3
  69. package/dist/packages/ui/src/components/popover.mjs +9 -4
  70. package/dist/packages/ui/src/components/search-select.cjs +75 -0
  71. package/dist/packages/ui/src/components/search-select.mjs +73 -0
  72. package/dist/packages/ui/src/lib/compose-refs.cjs +56 -0
  73. package/dist/packages/ui/src/lib/compose-refs.mjs +39 -0
  74. package/dist/plugins/blog/api/index.d.cts +1 -1
  75. package/dist/plugins/blog/api/index.d.mts +1 -1
  76. package/dist/plugins/blog/api/index.d.ts +1 -1
  77. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  78. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  79. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  80. package/dist/plugins/blog/client/index.d.cts +1 -1
  81. package/dist/plugins/blog/client/index.d.mts +1 -1
  82. package/dist/plugins/blog/client/index.d.ts +1 -1
  83. package/dist/plugins/blog/query-keys.d.cts +2 -2
  84. package/dist/plugins/blog/query-keys.d.mts +2 -2
  85. package/dist/plugins/blog/query-keys.d.ts +2 -2
  86. package/dist/plugins/kanban/api/index.cjs +7 -0
  87. package/dist/plugins/kanban/api/index.d.cts +403 -0
  88. package/dist/plugins/kanban/api/index.d.mts +403 -0
  89. package/dist/plugins/kanban/api/index.d.ts +403 -0
  90. package/dist/plugins/kanban/api/index.mjs +1 -0
  91. package/dist/plugins/kanban/client/components/index.cjs +35 -0
  92. package/dist/plugins/kanban/client/components/index.d.cts +102 -0
  93. package/dist/plugins/kanban/client/components/index.d.mts +102 -0
  94. package/dist/plugins/kanban/client/components/index.d.ts +102 -0
  95. package/dist/plugins/kanban/client/components/index.mjs +15 -0
  96. package/dist/plugins/kanban/client/hooks/index.cjs +15 -0
  97. package/dist/plugins/kanban/client/hooks/index.d.cts +143 -0
  98. package/dist/plugins/kanban/client/hooks/index.d.mts +143 -0
  99. package/dist/plugins/kanban/client/hooks/index.d.ts +143 -0
  100. package/dist/plugins/kanban/client/hooks/index.mjs +1 -0
  101. package/dist/plugins/kanban/client/index.cjs +7 -0
  102. package/dist/plugins/kanban/client/index.d.cts +196 -0
  103. package/dist/plugins/kanban/client/index.d.mts +196 -0
  104. package/dist/plugins/kanban/client/index.d.ts +196 -0
  105. package/dist/plugins/kanban/client/index.mjs +1 -0
  106. package/dist/plugins/kanban/client.css +68 -0
  107. package/dist/plugins/kanban/query-keys.cjs +105 -0
  108. package/dist/plugins/kanban/query-keys.d.cts +59 -0
  109. package/dist/plugins/kanban/query-keys.d.mts +59 -0
  110. package/dist/plugins/kanban/query-keys.d.ts +59 -0
  111. package/dist/plugins/kanban/query-keys.mjs +103 -0
  112. package/dist/plugins/kanban/style.css +7 -0
  113. package/dist/plugins/ui-builder/style.css +6 -0
  114. package/dist/shared/stack.DKDMI-QO.d.cts +70 -0
  115. package/dist/shared/stack.DKDMI-QO.d.mts +70 -0
  116. package/dist/shared/stack.DKDMI-QO.d.ts +70 -0
  117. package/dist/shared/stack.FeaWkglm.d.cts +190 -0
  118. package/dist/shared/stack.FeaWkglm.d.mts +190 -0
  119. package/dist/shared/stack.FeaWkglm.d.ts +190 -0
  120. package/package.json +56 -2
  121. package/src/plugins/kanban/api/index.ts +6 -0
  122. package/src/plugins/kanban/api/plugin.ts +1245 -0
  123. package/src/plugins/kanban/client/components/forms/board-form.tsx +108 -0
  124. package/src/plugins/kanban/client/components/forms/column-form.tsx +97 -0
  125. package/src/plugins/kanban/client/components/forms/task-form.tsx +274 -0
  126. package/src/plugins/kanban/client/components/index.tsx +21 -0
  127. package/src/plugins/kanban/client/components/loading/board-skeleton.tsx +49 -0
  128. package/src/plugins/kanban/client/components/loading/boards-list-skeleton.tsx +34 -0
  129. package/src/plugins/kanban/client/components/loading/index.tsx +2 -0
  130. package/src/plugins/kanban/client/components/pages/404-page.tsx +28 -0
  131. package/src/plugins/kanban/client/components/pages/board-page.internal.tsx +575 -0
  132. package/src/plugins/kanban/client/components/pages/board-page.tsx +31 -0
  133. package/src/plugins/kanban/client/components/pages/boards-list-page.internal.tsx +101 -0
  134. package/src/plugins/kanban/client/components/pages/boards-list-page.tsx +26 -0
  135. package/src/plugins/kanban/client/components/pages/new-board-page.internal.tsx +65 -0
  136. package/src/plugins/kanban/client/components/pages/new-board-page.tsx +26 -0
  137. package/src/plugins/kanban/client/components/shared/column-content.tsx +108 -0
  138. package/src/plugins/kanban/client/components/shared/default-error.tsx +32 -0
  139. package/src/plugins/kanban/client/components/shared/empty-state.tsx +37 -0
  140. package/src/plugins/kanban/client/components/shared/kanban-board.tsx +87 -0
  141. package/src/plugins/kanban/client/components/shared/page-wrapper.tsx +20 -0
  142. package/src/plugins/kanban/client/components/shared/task-card.tsx +79 -0
  143. package/src/plugins/kanban/client/components/shared/user-avatar.tsx +63 -0
  144. package/src/plugins/kanban/client/hooks/index.tsx +11 -0
  145. package/src/plugins/kanban/client/hooks/kanban-hooks.tsx +560 -0
  146. package/src/plugins/kanban/client/index.ts +8 -0
  147. package/src/plugins/kanban/client/localization/index.ts +28 -0
  148. package/src/plugins/kanban/client/localization/kanban-common.ts +69 -0
  149. package/src/plugins/kanban/client/localization/kanban-forms.ts +70 -0
  150. package/src/plugins/kanban/client/localization/kanban-list.ts +36 -0
  151. package/src/plugins/kanban/client/overrides.ts +145 -0
  152. package/src/plugins/kanban/client/plugin.tsx +463 -0
  153. package/src/plugins/kanban/client.css +68 -0
  154. package/src/plugins/kanban/db.ts +125 -0
  155. package/src/plugins/kanban/query-keys.ts +154 -0
  156. package/src/plugins/kanban/schemas.ts +143 -0
  157. package/src/plugins/kanban/style.css +7 -0
  158. package/src/plugins/kanban/types.ts +106 -0
  159. package/src/plugins/kanban/utils.ts +107 -0
  160. package/src/plugins/ui-builder/style.css +6 -0
  161. package/dist/shared/{stack.DLhzx1-D.d.cts → stack.CcI4sYJP.d.cts} +1 -1
  162. package/dist/shared/{stack.DLhzx1-D.d.mts → stack.CcI4sYJP.d.mts} +1 -1
  163. package/dist/shared/{stack.DLhzx1-D.d.ts → stack.CcI4sYJP.d.ts} +1 -1
@@ -0,0 +1,68 @@
1
+ /* Kanban Plugin Client Styles */
2
+
3
+ /* Kanban board container */
4
+ [data-slot="kanban-board"] {
5
+ min-height: 400px;
6
+ }
7
+
8
+ /* Column styling */
9
+ [data-slot="kanban-column"] {
10
+ min-width: 0;
11
+ }
12
+
13
+ /* Item dragging state */
14
+ [data-slot="kanban-item"][data-dragging] {
15
+ opacity: 0.5;
16
+ }
17
+
18
+ /* Column dragging state */
19
+ [data-slot="kanban-column"][data-dragging] {
20
+ opacity: 0.5;
21
+ }
22
+
23
+ /* Handle cursor */
24
+ [data-slot="kanban-column-handle"],
25
+ [data-slot="kanban-item-handle"] {
26
+ cursor: grab;
27
+ }
28
+
29
+ [data-slot="kanban-column-handle"]:active,
30
+ [data-slot="kanban-item-handle"]:active {
31
+ cursor: grabbing;
32
+ }
33
+
34
+ /* Task card hover effect */
35
+ .kanban-task-card {
36
+ transition: box-shadow 0.2s ease;
37
+ }
38
+
39
+ .kanban-task-card:hover {
40
+ box-shadow:
41
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
42
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
43
+ }
44
+
45
+ /* Priority badge colors */
46
+ .kanban-priority-urgent {
47
+ background-color: var(--destructive) !important;
48
+ color: var(--destructive-foreground) !important;
49
+ border-color: var(--destructive) !important;
50
+ }
51
+
52
+ .kanban-priority-high {
53
+ background-color: rgb(249, 115, 22) !important;
54
+ color: white !important;
55
+ border-color: rgb(249, 115, 22) !important;
56
+ }
57
+
58
+ .kanban-priority-medium {
59
+ background-color: var(--primary) !important;
60
+ color: var(--primary-foreground) !important;
61
+ border-color: var(--primary) !important;
62
+ }
63
+
64
+ .kanban-priority-low {
65
+ background-color: var(--secondary) !important;
66
+ color: var(--secondary-foreground) !important;
67
+ border-color: var(--secondary) !important;
68
+ }
@@ -0,0 +1,125 @@
1
+ import { createDbPlugin } from "@btst/db";
2
+
3
+ /**
4
+ * Kanban plugin schema
5
+ * Defines the database tables for kanban boards, columns, and tasks
6
+ */
7
+ export const kanbanSchema = createDbPlugin("kanban", {
8
+ board: {
9
+ modelName: "kanbanBoard",
10
+ fields: {
11
+ name: {
12
+ type: "string",
13
+ required: true,
14
+ },
15
+ slug: {
16
+ type: "string",
17
+ required: true,
18
+ unique: true,
19
+ },
20
+ description: {
21
+ type: "string",
22
+ required: false,
23
+ },
24
+ ownerId: {
25
+ type: "string",
26
+ required: false,
27
+ },
28
+ organizationId: {
29
+ type: "string",
30
+ required: false,
31
+ },
32
+ createdAt: {
33
+ type: "date",
34
+ defaultValue: () => new Date(),
35
+ },
36
+ updatedAt: {
37
+ type: "date",
38
+ defaultValue: () => new Date(),
39
+ },
40
+ },
41
+ },
42
+ column: {
43
+ modelName: "kanbanColumn",
44
+ fields: {
45
+ title: {
46
+ type: "string",
47
+ required: true,
48
+ },
49
+ order: {
50
+ type: "number",
51
+ required: true,
52
+ defaultValue: 0,
53
+ },
54
+ boardId: {
55
+ type: "string",
56
+ required: true,
57
+ references: {
58
+ model: "kanbanBoard",
59
+ field: "id",
60
+ onDelete: "cascade",
61
+ },
62
+ },
63
+ createdAt: {
64
+ type: "date",
65
+ defaultValue: () => new Date(),
66
+ },
67
+ updatedAt: {
68
+ type: "date",
69
+ defaultValue: () => new Date(),
70
+ },
71
+ },
72
+ },
73
+ task: {
74
+ modelName: "kanbanTask",
75
+ fields: {
76
+ title: {
77
+ type: "string",
78
+ required: true,
79
+ },
80
+ description: {
81
+ type: "string",
82
+ required: false,
83
+ },
84
+ priority: {
85
+ type: "string",
86
+ required: true,
87
+ defaultValue: "MEDIUM",
88
+ },
89
+ order: {
90
+ type: "number",
91
+ required: true,
92
+ defaultValue: 0,
93
+ },
94
+ columnId: {
95
+ type: "string",
96
+ required: true,
97
+ references: {
98
+ model: "kanbanColumn",
99
+ field: "id",
100
+ onDelete: "cascade",
101
+ },
102
+ },
103
+ assigneeId: {
104
+ type: "string",
105
+ required: false,
106
+ },
107
+ completedAt: {
108
+ type: "date",
109
+ required: false,
110
+ },
111
+ isArchived: {
112
+ type: "boolean",
113
+ defaultValue: false,
114
+ },
115
+ createdAt: {
116
+ type: "date",
117
+ defaultValue: () => new Date(),
118
+ },
119
+ updatedAt: {
120
+ type: "date",
121
+ defaultValue: () => new Date(),
122
+ },
123
+ },
124
+ },
125
+ });
@@ -0,0 +1,154 @@
1
+ import {
2
+ mergeQueryKeys,
3
+ createQueryKeys,
4
+ } from "@lukemorales/query-key-factory";
5
+ import type { KanbanApiRouter } from "./api";
6
+ import { createApiClient } from "@btst/stack/plugins/client";
7
+ import type { SerializedBoardWithColumns } from "./types";
8
+
9
+ interface BoardsListParams {
10
+ slug?: string;
11
+ ownerId?: string;
12
+ organizationId?: string;
13
+ limit?: number;
14
+ offset?: number;
15
+ }
16
+
17
+ // Type guard for better-call error responses
18
+ function isErrorResponse(
19
+ response: unknown,
20
+ ): response is { error: unknown; data?: never } {
21
+ return (
22
+ typeof response === "object" &&
23
+ response !== null &&
24
+ "error" in response &&
25
+ response.error !== null &&
26
+ response.error !== undefined
27
+ );
28
+ }
29
+
30
+ // Helper to convert error to a proper Error object with meaningful message
31
+ function toError(error: unknown): Error {
32
+ if (error instanceof Error) {
33
+ return error;
34
+ }
35
+
36
+ if (typeof error === "object" && error !== null) {
37
+ const errorObj = error as Record<string, unknown>;
38
+ const message =
39
+ (typeof errorObj.message === "string" ? errorObj.message : null) ||
40
+ (typeof errorObj.error === "string" ? errorObj.error : null) ||
41
+ JSON.stringify(error);
42
+
43
+ const err = new Error(message);
44
+ Object.assign(err, error);
45
+ return err;
46
+ }
47
+
48
+ return new Error(String(error));
49
+ }
50
+
51
+ export function createKanbanQueryKeys(
52
+ client: ReturnType<typeof createApiClient<KanbanApiRouter>>,
53
+ headers?: HeadersInit,
54
+ ) {
55
+ const boards = createBoardsQueries(client, headers);
56
+
57
+ return mergeQueryKeys(boards);
58
+ }
59
+
60
+ function createBoardsQueries(
61
+ client: ReturnType<typeof createApiClient<KanbanApiRouter>>,
62
+ headers?: HeadersInit,
63
+ ) {
64
+ return createQueryKeys("boards", {
65
+ list: (params?: BoardsListParams) => ({
66
+ queryKey: [
67
+ {
68
+ slug: params?.slug,
69
+ ownerId: params?.ownerId,
70
+ organizationId: params?.organizationId,
71
+ limit: params?.limit ?? 50,
72
+ offset: params?.offset ?? 0,
73
+ },
74
+ ],
75
+ queryFn: async () => {
76
+ try {
77
+ const response = await client("/boards", {
78
+ method: "GET",
79
+ query: {
80
+ slug: params?.slug,
81
+ ownerId: params?.ownerId,
82
+ organizationId: params?.organizationId,
83
+ limit: params?.limit ?? 50,
84
+ offset: params?.offset ?? 0,
85
+ },
86
+ headers,
87
+ });
88
+
89
+ if (isErrorResponse(response)) {
90
+ const errorResponse = response as { error: unknown };
91
+ throw toError(errorResponse.error);
92
+ }
93
+
94
+ return ((response as { data?: unknown }).data ??
95
+ []) as unknown as SerializedBoardWithColumns[];
96
+ } catch (error) {
97
+ throw error;
98
+ }
99
+ },
100
+ }),
101
+
102
+ detail: (boardId: string) => ({
103
+ queryKey: [boardId],
104
+ queryFn: async () => {
105
+ if (!boardId) return null;
106
+
107
+ try {
108
+ const response = await client("/boards/:id", {
109
+ method: "GET",
110
+ params: { id: boardId },
111
+ headers,
112
+ });
113
+
114
+ if (isErrorResponse(response)) {
115
+ const errorResponse = response as { error: unknown };
116
+ throw toError(errorResponse.error);
117
+ }
118
+
119
+ return ((response as { data?: unknown }).data ??
120
+ null) as unknown as SerializedBoardWithColumns | null;
121
+ } catch (error) {
122
+ throw error;
123
+ }
124
+ },
125
+ }),
126
+
127
+ // Get board by slug
128
+ bySlug: (slug: string) => ({
129
+ queryKey: ["slug", slug],
130
+ queryFn: async () => {
131
+ if (!slug) return null;
132
+
133
+ try {
134
+ const response = await client("/boards", {
135
+ method: "GET",
136
+ query: { slug, limit: 1 },
137
+ headers,
138
+ });
139
+
140
+ if (isErrorResponse(response)) {
141
+ const errorResponse = response as { error: unknown };
142
+ throw toError(errorResponse.error);
143
+ }
144
+
145
+ const boards = ((response as { data?: unknown }).data ??
146
+ []) as unknown as SerializedBoardWithColumns[];
147
+ return boards[0] ?? null;
148
+ } catch (error) {
149
+ throw error;
150
+ }
151
+ },
152
+ }),
153
+ });
154
+ }
@@ -0,0 +1,143 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Priority enum schema
5
+ */
6
+ export const PrioritySchema = z.enum(["LOW", "MEDIUM", "HIGH", "URGENT"]);
7
+
8
+ // ============ Board Schemas ============
9
+
10
+ const boardDateFields = {
11
+ createdAt: z.coerce.date().optional(),
12
+ updatedAt: z.coerce.date().optional(),
13
+ };
14
+
15
+ const boardCoreFields = {
16
+ name: z.string().min(1, "Name is required"),
17
+ slug: z.string().min(1, "Slug is required"),
18
+ description: z.string().optional(),
19
+ ownerId: z.string().optional(),
20
+ organizationId: z.string().optional(),
21
+ };
22
+
23
+ export const BoardDomainSchema = z.object({
24
+ id: z.string().optional(),
25
+ ...boardCoreFields,
26
+ ...boardDateFields,
27
+ });
28
+
29
+ export const createBoardSchema = BoardDomainSchema.extend({
30
+ slug: BoardDomainSchema.shape.slug.optional(),
31
+ }).omit({ id: true });
32
+
33
+ export const updateBoardSchema = BoardDomainSchema.extend({
34
+ id: z.string(),
35
+ })
36
+ .partial()
37
+ .required({ id: true });
38
+
39
+ // ============ Column Schemas ============
40
+
41
+ const columnDateFields = {
42
+ createdAt: z.coerce.date().optional(),
43
+ updatedAt: z.coerce.date().optional(),
44
+ };
45
+
46
+ const columnCoreFields = {
47
+ title: z.string().min(1, "Title is required"),
48
+ order: z.number().int().min(0).optional().default(0),
49
+ boardId: z.string().min(1, "Board ID is required"),
50
+ };
51
+
52
+ export const ColumnDomainSchema = z.object({
53
+ id: z.string().optional(),
54
+ ...columnCoreFields,
55
+ ...columnDateFields,
56
+ });
57
+
58
+ export const createColumnSchema = ColumnDomainSchema.omit({ id: true });
59
+
60
+ export const updateColumnSchema = ColumnDomainSchema.extend({
61
+ id: z.string(),
62
+ })
63
+ .partial()
64
+ .required({ id: true });
65
+
66
+ // ============ Task Schemas ============
67
+
68
+ const taskDateFields = {
69
+ completedAt: z.coerce.date().optional(),
70
+ createdAt: z.coerce.date().optional(),
71
+ updatedAt: z.coerce.date().optional(),
72
+ };
73
+
74
+ const taskCoreFields = {
75
+ title: z.string().min(1, "Title is required"),
76
+ description: z.string().optional(),
77
+ priority: PrioritySchema.optional().default("MEDIUM"),
78
+ order: z.number().int().min(0).optional().default(0),
79
+ columnId: z.string().min(1, "Column ID is required"),
80
+ assigneeId: z.string().optional().nullable(),
81
+ isArchived: z.boolean().optional().default(false),
82
+ };
83
+
84
+ export const TaskDomainSchema = z.object({
85
+ id: z.string().optional(),
86
+ ...taskCoreFields,
87
+ ...taskDateFields,
88
+ });
89
+
90
+ export const createTaskSchema = TaskDomainSchema.omit({ id: true });
91
+
92
+ export const updateTaskSchema = TaskDomainSchema.extend({
93
+ id: z.string(),
94
+ })
95
+ .partial()
96
+ .required({ id: true });
97
+
98
+ // ============ Query Schemas ============
99
+
100
+ export const BoardListQuerySchema = z.object({
101
+ slug: z.string().optional(),
102
+ ownerId: z.string().optional(),
103
+ organizationId: z.string().optional(),
104
+ offset: z.coerce.number().int().min(0).optional(),
105
+ limit: z.coerce.number().int().min(1).max(100).optional(),
106
+ });
107
+
108
+ export const ColumnListQuerySchema = z.object({
109
+ boardId: z.string().optional(),
110
+ });
111
+
112
+ export const TaskListQuerySchema = z.object({
113
+ columnId: z.string().optional(),
114
+ assigneeId: z.string().optional(),
115
+ priority: PrioritySchema.optional(),
116
+ isArchived: z
117
+ .string()
118
+ .optional()
119
+ .transform((val) => {
120
+ if (val === undefined) return undefined;
121
+ if (val === "true") return true;
122
+ if (val === "false") return false;
123
+ return undefined;
124
+ }),
125
+ });
126
+
127
+ // ============ Batch Update Schemas ============
128
+
129
+ export const reorderColumnsSchema = z.object({
130
+ boardId: z.string().min(1, "Board ID is required"),
131
+ columnIds: z.array(z.string()).min(1, "Column IDs are required"),
132
+ });
133
+
134
+ export const reorderTasksSchema = z.object({
135
+ columnId: z.string().min(1, "Column ID is required"),
136
+ taskIds: z.array(z.string()).min(1, "Task IDs are required"),
137
+ });
138
+
139
+ export const moveTaskSchema = z.object({
140
+ taskId: z.string().min(1, "Task ID is required"),
141
+ targetColumnId: z.string().min(1, "Target column ID is required"),
142
+ targetOrder: z.number().int().min(0),
143
+ });
@@ -0,0 +1,7 @@
1
+ /* Kanban Plugin Full Styles */
2
+ /* This file includes Tailwind directives for consumers who need the full stylesheet */
3
+
4
+ @import "./client.css";
5
+
6
+ /* Import minimal-tiptap styles for rich text editor in task forms */
7
+ @import "@workspace/ui/components/minimal-tiptap/styles.css";
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Priority levels for tasks
3
+ */
4
+ export type Priority = "LOW" | "MEDIUM" | "HIGH" | "URGENT";
5
+
6
+ /**
7
+ * Kanban Board
8
+ */
9
+ export type Board = {
10
+ id: string;
11
+ name: string;
12
+ slug: string;
13
+ description?: string;
14
+ ownerId?: string;
15
+ organizationId?: string;
16
+ createdAt: Date;
17
+ updatedAt: Date;
18
+ };
19
+
20
+ /**
21
+ * Kanban Column
22
+ */
23
+ export type Column = {
24
+ id: string;
25
+ title: string;
26
+ order: number;
27
+ boardId: string;
28
+ createdAt: Date;
29
+ updatedAt: Date;
30
+ };
31
+
32
+ /**
33
+ * Kanban Task
34
+ */
35
+ export type Task = {
36
+ id: string;
37
+ title: string;
38
+ description?: string;
39
+ priority: Priority;
40
+ order: number;
41
+ columnId: string;
42
+ assigneeId?: string;
43
+ completedAt?: Date;
44
+ isArchived: boolean;
45
+ createdAt: Date;
46
+ updatedAt: Date;
47
+ };
48
+
49
+ /**
50
+ * Column with its tasks
51
+ */
52
+ export type ColumnWithTasks = Column & {
53
+ tasks: Task[];
54
+ };
55
+
56
+ /**
57
+ * Board with columns and tasks
58
+ */
59
+ export type BoardWithColumns = Board & {
60
+ columns: ColumnWithTasks[];
61
+ };
62
+
63
+ /**
64
+ * Board with joined column relationships from the database
65
+ * Note: The adapter returns joined data under the schema key name ("column"),
66
+ * not the model name ("kanbanColumn")
67
+ */
68
+ export type BoardWithKanbanColumn = Board & {
69
+ column?: Column[];
70
+ };
71
+
72
+ /**
73
+ * Column with joined task relationships from the database
74
+ * Note: The adapter returns joined data under the schema key name ("task"),
75
+ * not the model name ("kanbanTask")
76
+ */
77
+ export type ColumnWithKanbanTask = Column & {
78
+ task?: Task[];
79
+ };
80
+
81
+ // Serialized types for API responses (dates as strings)
82
+
83
+ export interface SerializedTask
84
+ extends Omit<Task, "createdAt" | "updatedAt" | "completedAt"> {
85
+ completedAt?: string;
86
+ createdAt: string;
87
+ updatedAt: string;
88
+ }
89
+
90
+ export interface SerializedColumn
91
+ extends Omit<Column, "createdAt" | "updatedAt"> {
92
+ createdAt: string;
93
+ updatedAt: string;
94
+ tasks?: SerializedTask[];
95
+ }
96
+
97
+ export interface SerializedBoard
98
+ extends Omit<Board, "createdAt" | "updatedAt"> {
99
+ createdAt: string;
100
+ updatedAt: string;
101
+ columns?: SerializedColumn[];
102
+ }
103
+
104
+ export interface SerializedBoardWithColumns extends SerializedBoard {
105
+ columns: SerializedColumn[];
106
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Slugify a string for use in URLs
3
+ * @param text - The text to slugify
4
+ * @returns A URL-safe slug
5
+ */
6
+ export function slugify(text: string): string {
7
+ return text
8
+ .toLowerCase()
9
+ .trim()
10
+ .replace(/[^\w\s-]/g, "") // Remove non-word characters
11
+ .replace(/[\s_-]+/g, "-") // Replace spaces and underscores with hyphens
12
+ .replace(/^-+|-+$/g, ""); // Remove leading/trailing hyphens
13
+ }
14
+
15
+ /**
16
+ * Generate a unique slug by appending a random suffix
17
+ * @param baseSlug - The base slug
18
+ * @returns A unique slug with random suffix
19
+ */
20
+ export function generateUniqueSlug(baseSlug: string): string {
21
+ const randomSuffix = Math.random().toString(36).substring(2, 8);
22
+ return `${baseSlug}-${randomSuffix}`;
23
+ }
24
+
25
+ /**
26
+ * Calculate the next order value for a list of items
27
+ * @param items - Array of items with order property
28
+ * @returns The next order value
29
+ */
30
+ export function getNextOrder(items: { order: number }[]): number {
31
+ if (items.length === 0) return 0;
32
+ return Math.max(...items.map((item) => item.order)) + 1;
33
+ }
34
+
35
+ /**
36
+ * Reorder items by swapping positions
37
+ * @param items - Array of items to reorder
38
+ * @param fromIndex - Original index
39
+ * @param toIndex - Target index
40
+ * @returns Reordered array with updated order values
41
+ */
42
+ export function reorderItems<T extends { id: string; order: number }>(
43
+ items: T[],
44
+ fromIndex: number,
45
+ toIndex: number,
46
+ ): T[] {
47
+ const result = [...items];
48
+ const [removed] = result.splice(fromIndex, 1);
49
+ if (removed) {
50
+ result.splice(toIndex, 0, removed);
51
+ }
52
+ return result.map((item, index) => ({ ...item, order: index }));
53
+ }
54
+
55
+ /**
56
+ * Get priority display configuration
57
+ * @param priority - Priority level
58
+ * @returns Display configuration for the priority
59
+ */
60
+ export function getPriorityConfig(priority: string): {
61
+ label: string;
62
+ variant: "default" | "secondary" | "destructive" | "outline";
63
+ className: string;
64
+ } {
65
+ switch (priority) {
66
+ case "URGENT":
67
+ return {
68
+ label: "Urgent",
69
+ variant: "destructive",
70
+ className: "kanban-priority-urgent",
71
+ };
72
+ case "HIGH":
73
+ return {
74
+ label: "High",
75
+ variant: "outline",
76
+ className: "kanban-priority-high",
77
+ };
78
+ case "MEDIUM":
79
+ return {
80
+ label: "Medium",
81
+ variant: "default",
82
+ className: "kanban-priority-medium",
83
+ };
84
+ case "LOW":
85
+ return {
86
+ label: "Low",
87
+ variant: "secondary",
88
+ className: "kanban-priority-low",
89
+ };
90
+ default:
91
+ return {
92
+ label: "Medium",
93
+ variant: "default",
94
+ className: "kanban-priority-medium",
95
+ };
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Priority options for forms
101
+ */
102
+ export const PRIORITY_OPTIONS = [
103
+ { value: "LOW", label: "Low" },
104
+ { value: "MEDIUM", label: "Medium" },
105
+ { value: "HIGH", label: "High" },
106
+ { value: "URGENT", label: "Urgent" },
107
+ ] as const;