@knotpad/app 0.1.0 → 0.1.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 (341) hide show
  1. package/bin/brief.js +165 -78
  2. package/package.json +3 -17
  3. package/app/(app)/calendar/page.tsx +0 -57
  4. package/app/(app)/error.tsx +0 -35
  5. package/app/(app)/graph/page.tsx +0 -32
  6. package/app/(app)/guide/page.tsx +0 -21
  7. package/app/(app)/kanban/loading.tsx +0 -24
  8. package/app/(app)/kanban/page.tsx +0 -59
  9. package/app/(app)/layout.tsx +0 -122
  10. package/app/(app)/list/loading.tsx +0 -21
  11. package/app/(app)/list/page.tsx +0 -137
  12. package/app/(app)/loading.tsx +0 -18
  13. package/app/(app)/notes/[noteId]/page.tsx +0 -84
  14. package/app/(app)/notes/layout.tsx +0 -30
  15. package/app/(app)/notes/page.tsx +0 -39
  16. package/app/(app)/page.tsx +0 -5
  17. package/app/(app)/settings/agent-token/page.tsx +0 -59
  18. package/app/(app)/settings/backup/page.tsx +0 -49
  19. package/app/(app)/settings/billing/page.tsx +0 -53
  20. package/app/(app)/settings/calendar/page.tsx +0 -41
  21. package/app/(app)/settings/layout.test.tsx +0 -39
  22. package/app/(app)/settings/layout.tsx +0 -71
  23. package/app/(app)/settings/page.tsx +0 -4
  24. package/app/(app)/settings/security/page.tsx +0 -43
  25. package/app/(app)/settings/team/page.tsx +0 -74
  26. package/app/(app)/settings/workspace/page.tsx +0 -27
  27. package/app/(app)/tasks/[taskId]/page.tsx +0 -79
  28. package/app/(auth)/forgot-password/page.tsx +0 -106
  29. package/app/(auth)/guest/page.tsx +0 -56
  30. package/app/(auth)/layout.tsx +0 -13
  31. package/app/(auth)/login/page.tsx +0 -14
  32. package/app/(auth)/register/page.tsx +0 -193
  33. package/app/(auth)/reset-password/page.tsx +0 -138
  34. package/app/api/account/claim/route.tsx +0 -135
  35. package/app/api/admin/backfill-encryption/route.tsx +0 -43
  36. package/app/api/admin/license/route.tsx +0 -42
  37. package/app/api/auth/2fa/route.tsx +0 -148
  38. package/app/api/auth/[...nextauth]/route.tsx +0 -3
  39. package/app/api/auth/change-password/route.tsx +0 -61
  40. package/app/api/auth/check-2fa/route.tsx +0 -19
  41. package/app/api/auth/forgot-password/route.tsx +0 -65
  42. package/app/api/auth/reset-password/route.tsx +0 -52
  43. package/app/api/auth/verify-2fa/route.tsx +0 -88
  44. package/app/api/backup/download/db/route.ts +0 -29
  45. package/app/api/backup/download/notes/route.ts +0 -25
  46. package/app/api/backup/settings/route.ts +0 -92
  47. package/app/api/billing/checkout/route.tsx +0 -81
  48. package/app/api/billing/migrate/route.tsx +0 -163
  49. package/app/api/billing/portal/route.tsx +0 -24
  50. package/app/api/billing/setup-intent/route.tsx +0 -55
  51. package/app/api/billing/status/route.tsx +0 -36
  52. package/app/api/billing/subscribe/route.tsx +0 -85
  53. package/app/api/billing/webhook/route.tsx +0 -199
  54. package/app/api/calendar-feeds/[feedId]/route.tsx +0 -67
  55. package/app/api/calendar-feeds/[feedId]/sync/route.tsx +0 -37
  56. package/app/api/calendar-feeds/events/route.tsx +0 -82
  57. package/app/api/calendar-feeds/route.tsx +0 -52
  58. package/app/api/calendar-feeds/sync-all/route.tsx +0 -34
  59. package/app/api/cron/calendar-feeds/route.tsx +0 -31
  60. package/app/api/cron/stale-tasks/route.tsx +0 -51
  61. package/app/api/cron/sync/route.tsx +0 -34
  62. package/app/api/devices/[deviceId]/route.tsx +0 -25
  63. package/app/api/devices/route.tsx +0 -41
  64. package/app/api/export/route.tsx +0 -40
  65. package/app/api/feedback/route.tsx +0 -54
  66. package/app/api/folders/[folderId]/route.tsx +0 -51
  67. package/app/api/folders/route.tsx +0 -37
  68. package/app/api/graph/route.tsx +0 -242
  69. package/app/api/guest/route.tsx +0 -58
  70. package/app/api/health/route.tsx +0 -10
  71. package/app/api/holidays/countries/route.tsx +0 -14
  72. package/app/api/holidays/route.tsx +0 -49
  73. package/app/api/holidays/states/route.tsx +0 -21
  74. package/app/api/invites/[token]/route.tsx +0 -131
  75. package/app/api/invites/route.tsx +0 -74
  76. package/app/api/mcp/generate-token/route.tsx +0 -55
  77. package/app/api/mcp/revoke-token/[tokenId]/route.tsx +0 -30
  78. package/app/api/mcp/update-alias/[tokenId]/route.tsx +0 -22
  79. package/app/api/notes/[noteId]/export/route.tsx +0 -45
  80. package/app/api/notes/[noteId]/route.tsx +0 -360
  81. package/app/api/notes/route.tsx +0 -112
  82. package/app/api/notifications/route.tsx +0 -44
  83. package/app/api/register/route.tsx +0 -67
  84. package/app/api/restore/route.tsx +0 -148
  85. package/app/api/sync/conflicts/[conflictId]/route.tsx +0 -134
  86. package/app/api/sync/conflicts/route.tsx +0 -48
  87. package/app/api/sync/status/route.tsx +0 -49
  88. package/app/api/sync/trigger/route.tsx +0 -15
  89. package/app/api/tasks/[taskId]/detail/route.tsx +0 -68
  90. package/app/api/tasks/[taskId]/route.tsx +0 -259
  91. package/app/api/tasks/bulk/route.tsx +0 -133
  92. package/app/api/tasks/route.tsx +0 -36
  93. package/app/api/workspace/active/route.tsx +0 -39
  94. package/app/api/workspace/create-team/route.tsx +0 -42
  95. package/app/api/workspace/kanban-statuses/route.tsx +0 -71
  96. package/app/api/workspace/members/[memberId]/route.tsx +0 -69
  97. package/app/api/workspace/route.tsx +0 -24
  98. package/app/download/page.tsx +0 -170
  99. package/app/favicon.ico +0 -0
  100. package/app/generated/prisma/client.d.ts +0 -1
  101. package/app/generated/prisma/client.js +0 -5
  102. package/app/generated/prisma/default.d.ts +0 -1
  103. package/app/generated/prisma/default.js +0 -5
  104. package/app/generated/prisma/edge.d.ts +0 -1
  105. package/app/generated/prisma/edge.js +0 -497
  106. package/app/generated/prisma/index-browser.js +0 -523
  107. package/app/generated/prisma/index.d.ts +0 -46376
  108. package/app/generated/prisma/index.js +0 -497
  109. package/app/generated/prisma/package.json +0 -144
  110. package/app/generated/prisma/query_compiler_fast_bg.js +0 -2
  111. package/app/generated/prisma/query_compiler_fast_bg.wasm +0 -0
  112. package/app/generated/prisma/query_compiler_fast_bg.wasm-base64.js +0 -2
  113. package/app/generated/prisma/runtime/client.d.ts +0 -3386
  114. package/app/generated/prisma/runtime/client.js +0 -86
  115. package/app/generated/prisma/runtime/index-browser.d.ts +0 -90
  116. package/app/generated/prisma/runtime/index-browser.js +0 -6
  117. package/app/generated/prisma/runtime/wasm-compiler-edge.js +0 -76
  118. package/app/generated/prisma/schema.prisma +0 -456
  119. package/app/generated/prisma/wasm-edge-light-loader.mjs +0 -5
  120. package/app/generated/prisma/wasm-worker-loader.mjs +0 -5
  121. package/app/globals.css +0 -54
  122. package/app/invite/[token]/page.tsx +0 -52
  123. package/app/layout.tsx +0 -90
  124. package/app/mcp/route.tsx +0 -430
  125. package/app/opengraph-image.tsx +0 -120
  126. package/app/page.tsx +0 -398
  127. package/app/privacy/page.tsx +0 -69
  128. package/app/robots.tsx +0 -25
  129. package/app/sitemap.tsx +0 -36
  130. package/app/terms/page.tsx +0 -69
  131. package/app/upgrade/page.tsx +0 -75
  132. package/auth.config.ts +0 -33
  133. package/auth.ts +0 -79
  134. package/components/auth/login-form.tsx +0 -302
  135. package/components/auth/password-checklist.tsx +0 -31
  136. package/components/auth/password-input.tsx +0 -36
  137. package/components/auth/switch-account-button.test.tsx +0 -22
  138. package/components/auth/switch-account-button.tsx +0 -19
  139. package/components/auth/two-factor-input.tsx +0 -116
  140. package/components/billing/billing-dashboard.tsx +0 -265
  141. package/components/billing/card-form.tsx +0 -210
  142. package/components/billing/claim-account-form.tsx +0 -99
  143. package/components/branding/app-logo.test.tsx +0 -20
  144. package/components/branding/app-logo.tsx +0 -25
  145. package/components/calendar/calendar-agenda.tsx +0 -150
  146. package/components/calendar/calendar-drag.test.tsx +0 -177
  147. package/components/calendar/calendar-grid.tsx +0 -357
  148. package/components/calendar/calendar-hooks.test.tsx +0 -27
  149. package/components/calendar/calendar-hooks.ts +0 -351
  150. package/components/calendar/calendar-toolbar.test.tsx +0 -68
  151. package/components/calendar/calendar-toolbar.tsx +0 -291
  152. package/components/calendar/calendar-types.ts +0 -148
  153. package/components/calendar/calendar-view.test.tsx +0 -295
  154. package/components/calendar/calendar-view.tsx +0 -307
  155. package/components/calendar/day-detail-popover.tsx +0 -174
  156. package/components/calendar/task-chip.tsx +0 -86
  157. package/components/command/command-palette.test.tsx +0 -33
  158. package/components/command/command-palette.tsx +0 -310
  159. package/components/download-cta.tsx +0 -87
  160. package/components/feedback/feedback-popup.tsx +0 -207
  161. package/components/graph/graph-draw.ts +0 -337
  162. package/components/graph/graph-overlays.tsx +0 -160
  163. package/components/graph/graph-page.test.tsx +0 -131
  164. package/components/graph/graph-page.tsx +0 -263
  165. package/components/graph/graph-types.ts +0 -47
  166. package/components/graph/graph-view.tsx +0 -322
  167. package/components/guide/guide-view.tsx +0 -522
  168. package/components/kanban/kanban-board.test.tsx +0 -128
  169. package/components/kanban/kanban-board.tsx +0 -361
  170. package/components/kanban/kanban-card-menu.tsx +0 -102
  171. package/components/kanban/kanban-card.tsx +0 -227
  172. package/components/kanban/kanban-column.tsx +0 -49
  173. package/components/kanban/kanban-status-context.tsx +0 -28
  174. package/components/landing/calendar-sandbox.test.tsx +0 -15
  175. package/components/landing/calendar-sandbox.tsx +0 -107
  176. package/components/landing/graph-sandbox.test.tsx +0 -27
  177. package/components/landing/graph-sandbox.tsx +0 -80
  178. package/components/landing/kanban-sandbox.test.tsx +0 -24
  179. package/components/landing/kanban-sandbox.tsx +0 -101
  180. package/components/landing/landing-showcase.test.tsx +0 -21
  181. package/components/landing/landing-showcase.tsx +0 -54
  182. package/components/landing/list-sandbox.tsx +0 -86
  183. package/components/landing/mock-workspace.ts +0 -168
  184. package/components/landing/notes-sandbox.test.tsx +0 -14
  185. package/components/landing/notes-sandbox.tsx +0 -88
  186. package/components/layout/app-shell.tsx +0 -83
  187. package/components/layout/backup-scheduler.tsx +0 -122
  188. package/components/layout/bottom-nav.tsx +0 -43
  189. package/components/layout/icon-bar.test.tsx +0 -29
  190. package/components/layout/icon-bar.tsx +0 -118
  191. package/components/layout/mobile-top-bar.tsx +0 -68
  192. package/components/layout/notes-panel-folder.tsx +0 -127
  193. package/components/layout/notes-panel-note-item.tsx +0 -140
  194. package/components/layout/notes-panel-task-tab.tsx +0 -63
  195. package/components/layout/notes-panel-types.ts +0 -44
  196. package/components/layout/notes-panel.tsx +0 -476
  197. package/components/layout/notification-bell.tsx +0 -251
  198. package/components/layout/paywall-screen.tsx +0 -41
  199. package/components/layout/pro-banner.tsx +0 -76
  200. package/components/layout/sw-register.tsx +0 -27
  201. package/components/layout/workspace-switcher.tsx +0 -90
  202. package/components/notes/mobile-bottom-sheet.tsx +0 -99
  203. package/components/notes/note-editor-context-menu.tsx +0 -47
  204. package/components/notes/note-editor-dom.ts +0 -33
  205. package/components/notes/note-editor-dropdowns.tsx +0 -484
  206. package/components/notes/note-editor-hooks.ts +0 -692
  207. package/components/notes/note-editor-keyboard.ts +0 -305
  208. package/components/notes/note-editor-overlay.tsx +0 -90
  209. package/components/notes/note-editor.test.tsx +0 -372
  210. package/components/notes/note-editor.tsx +0 -662
  211. package/components/notes/note-preview-pane.tsx +0 -156
  212. package/components/notes/note-tabs.tsx +0 -120
  213. package/components/notes/note-types.tsx +0 -157
  214. package/components/settings/accept-invite.tsx +0 -108
  215. package/components/settings/agent-token-settings.tsx +0 -369
  216. package/components/settings/backup-restore-settings.test.tsx +0 -25
  217. package/components/settings/backup-restore-settings.tsx +0 -327
  218. package/components/settings/calendar-feeds-settings.tsx +0 -489
  219. package/components/settings/calendar-general-settings.tsx +0 -174
  220. package/components/settings/confirm-danger-action.test.tsx +0 -215
  221. package/components/settings/confirm-danger-action.tsx +0 -65
  222. package/components/settings/security-settings.tsx +0 -252
  223. package/components/settings/settings-guidance.test.tsx +0 -98
  224. package/components/settings/team-settings.tsx +0 -319
  225. package/components/settings/two-factor-auth.tsx +0 -296
  226. package/components/settings/workspace-settings-client.tsx +0 -363
  227. package/components/settings/workspace-settings-form.tsx +0 -73
  228. package/components/sync/conflict-viewer.tsx +0 -247
  229. package/components/sync/sync-indicator.tsx +0 -171
  230. package/components/tasks/snippet-thread.tsx +0 -119
  231. package/components/tasks/status-dot.tsx +0 -47
  232. package/components/tasks/task-badge.tsx +0 -43
  233. package/components/tasks/task-detail.test.tsx +0 -187
  234. package/components/tasks/task-detail.tsx +0 -458
  235. package/components/tasks/task-list-filters.test.tsx +0 -75
  236. package/components/tasks/task-list-filters.tsx +0 -163
  237. package/components/tasks/task-list-types.ts +0 -20
  238. package/components/tasks/task-list.test.tsx +0 -175
  239. package/components/tasks/task-list.tsx +0 -481
  240. package/components/tasks/task-row.tsx +0 -85
  241. package/components/tasks/task-table-row.tsx +0 -259
  242. package/components/ui/skeleton.tsx +0 -3
  243. package/components/ui/toast.test.tsx +0 -42
  244. package/components/ui/toast.tsx +0 -70
  245. package/electron/main.ts +0 -251
  246. package/electron/preload.ts +0 -56
  247. package/instrumentation.tsx +0 -23
  248. package/lib/api-error.ts +0 -50
  249. package/lib/backup/backup-runner.test.ts +0 -32
  250. package/lib/backup/backup-runner.ts +0 -19
  251. package/lib/backup/backup-schedule.test.ts +0 -23
  252. package/lib/backup/backup-schedule.ts +0 -55
  253. package/lib/backup/backup-settings.test.ts +0 -30
  254. package/lib/backup/backup-settings.ts +0 -27
  255. package/lib/backup/export-notes-zip.test.ts +0 -26
  256. package/lib/backup/export-notes-zip.ts +0 -82
  257. package/lib/backup/export-workspace-backup.test.ts +0 -17
  258. package/lib/backup/export-workspace-backup.ts +0 -77
  259. package/lib/backup/restore-workspace-from-export.test.ts +0 -18
  260. package/lib/backup/restore-workspace-from-export.ts +0 -183
  261. package/lib/backup/types.ts +0 -14
  262. package/lib/brand-icons.ts +0 -1
  263. package/lib/calendar-feed-crypto.ts +0 -38
  264. package/lib/calendar-feed.ts +0 -239
  265. package/lib/client/online-status.ts +0 -47
  266. package/lib/conflict-resolver.test.ts +0 -57
  267. package/lib/conflict-resolver.ts +0 -240
  268. package/lib/db-init.ts +0 -79
  269. package/lib/email.ts +0 -159
  270. package/lib/encryption.test.ts +0 -41
  271. package/lib/encryption.ts +0 -98
  272. package/lib/extract-snippet.test.ts +0 -123
  273. package/lib/extract-snippet.ts +0 -69
  274. package/lib/kanban-status.ts +0 -55
  275. package/lib/license.ts +0 -21
  276. package/lib/limits.ts +0 -31
  277. package/lib/mcp-auth.test.ts +0 -58
  278. package/lib/mcp-auth.ts +0 -65
  279. package/lib/mcp-contract.test.ts +0 -25
  280. package/lib/mcp-contract.ts +0 -210
  281. package/lib/mcp-handler.ts +0 -31
  282. package/lib/mcp-url.test.ts +0 -12
  283. package/lib/mcp-url.ts +0 -7
  284. package/lib/mentions.test.ts +0 -45
  285. package/lib/mentions.ts +0 -73
  286. package/lib/note-crypto.ts +0 -108
  287. package/lib/note-sync.ts +0 -201
  288. package/lib/note-title.ts +0 -93
  289. package/lib/prisma.ts +0 -193
  290. package/lib/pro-flush.ts +0 -292
  291. package/lib/rate-limit.ts +0 -57
  292. package/lib/stripe.ts +0 -38
  293. package/lib/sync-worker.ts +0 -388
  294. package/lib/task-parser.test.ts +0 -91
  295. package/lib/task-parser.ts +0 -81
  296. package/lib/task-utils.ts +0 -52
  297. package/lib/use-is-electron.ts +0 -19
  298. package/lib/use-is-mobile.ts +0 -22
  299. package/lib/validation/calendar-feed.ts +0 -31
  300. package/lib/validation/note.ts +0 -27
  301. package/lib/validation/task.ts +0 -26
  302. package/lib/view-preferences.test.ts +0 -54
  303. package/lib/view-preferences.ts +0 -28
  304. package/lib/workspace.ts +0 -66
  305. package/next.config.ts +0 -21
  306. package/postcss.config.mjs +0 -7
  307. package/prisma/migrations/20260519021916_init/migration.sql +0 -388
  308. package/prisma/migrations/20260519061113_drop_sync_password/migration.sql +0 -8
  309. package/prisma/migrations/20260520065016_add_task_start_date/migration.sql +0 -2
  310. package/prisma/migrations/20260529010600_remove_encryption_fields/migration.sql +0 -12
  311. package/prisma/migrations/20260529020000_restore_encryption_salt/migration.sql +0 -3
  312. package/prisma/migrations/20260529030000_add_folders/migration.sql +0 -17
  313. package/prisma/migrations/20260605000000_deferred_fixes/migration.sql +0 -31
  314. package/prisma/migrations/20260605020806_add_pending_sync_to_note_and_task/migration.sql +0 -5
  315. package/prisma/migrations/20260605063634_add_stripe_webhook_event_sync_lock/migration.sql +0 -14
  316. package/prisma/migrations/20260605100000_add_prod_indexes/migration.sql +0 -26
  317. package/prisma/migrations/20260608081404_add_kanban_statuses/migration.sql +0 -23
  318. package/prisma/migrations/20260611032723_add_calendar_feeds/migration.sql +0 -43
  319. package/prisma/migrations/20260611040000_add_calendar_feed_color/migration.sql +0 -2
  320. package/prisma/migrations/20260611050000_add_task_priority/migration.sql +0 -14
  321. package/prisma/migrations/20260612060000_add_critical_priority/migration.sql +0 -2
  322. package/prisma/migrations/20260613090000_add_backup_settings/migration.sql +0 -25
  323. package/prisma/migrations/20260614160000_add_feedback/migration.sql +0 -20
  324. package/prisma/migrations/20260614210000_add_2fa/migration.sql +0 -4
  325. package/prisma/migrations/migration_lock.toml +0 -3
  326. package/prisma/schema.prisma +0 -457
  327. package/public/Logo_icon.svg +0 -1
  328. package/public/file.svg +0 -1
  329. package/public/globe.svg +0 -1
  330. package/public/icon-192.png +0 -0
  331. package/public/icon-512.png +0 -0
  332. package/public/icon.svg +0 -4
  333. package/public/icon_dark.svg +0 -1
  334. package/public/knotpad_icon.svg +0 -1
  335. package/public/knotpad_logo_full.svg +0 -1
  336. package/public/manifest.json +0 -14
  337. package/public/next.svg +0 -1
  338. package/public/sw.js +0 -137
  339. package/public/vercel.svg +0 -1
  340. package/public/window.svg +0 -1
  341. package/tsconfig.json +0 -35
@@ -1,361 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useRef, useEffect } from "react";
4
- import { useRouter } from "next/navigation";
5
- import {
6
- DndContext,
7
- DragEndEvent,
8
- DragOverEvent,
9
- DragOverlay,
10
- DragStartEvent,
11
- PointerSensor,
12
- TouchSensor,
13
- useSensor,
14
- useSensors,
15
- pointerWithin,
16
- rectIntersection,
17
- } from "@dnd-kit/core";
18
- import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
19
- import { KanbanColumn } from "./kanban-column";
20
- import { KanbanCard } from "./kanban-card";
21
- import { useKanbanStatuses } from "./kanban-status-context";
22
- import { loadPreference, savePreference } from "@/lib/view-preferences";
23
- import { isOverdue } from "@/lib/task-utils";
24
-
25
- export type KanbanTask = {
26
- id: string;
27
- title: string;
28
- status: string;
29
- claimedBy: string | null;
30
- claimedByAlias: string | null;
31
- lastHeartbeat: string | null;
32
- assigneeType: "HUMAN" | "AGENT";
33
- assignee: { id: string; name: string | null; email: string | null } | null;
34
- note: { id: string; title: string; folder?: { id: string; name: string } | null };
35
- dueDate: string | null;
36
- };
37
-
38
- type Props = { tasks: KanbanTask[]; filterNoteId?: string; filterFolderId?: string };
39
-
40
- type BoardFilter = "all" | "overdue" | "agent";
41
-
42
- export function KanbanBoard({ tasks: initialTasks, filterNoteId, filterFolderId }: Props) {
43
- const router = useRouter();
44
- const [tasks, setTasks] = useState(initialTasks);
45
- const [activeTask, setActiveTask] = useState<KanbanTask | null>(null);
46
- const [density, setDensity] = useState<"comfortable" | "compact">(() =>
47
- loadPreference("brief:kanban-density", "comfortable")
48
- );
49
- const [boardFilter, setBoardFilter] = useState<BoardFilter>(() =>
50
- loadPreference("brief:kanban-filter", "all")
51
- );
52
- // Track the status before drag starts so we can revert on bad drops or errors.
53
- const preDragStatusRef = useRef<string | null>(null);
54
- // True while a drag (or its pending PATCH) is in flight. Used to avoid
55
- // re-syncing from stale `initialTasks` and snapping the card back to its
56
- // pre-drag column before the server refresh lands.
57
- const isDraggingRef = useRef(false);
58
- const kanbanStatuses = useKanbanStatuses();
59
- const visibleColumns = kanbanStatuses.filter((s) => s.isVisible);
60
-
61
- // Sync server-filtered tasks when props change, but never interrupt an active drag.
62
- useEffect(() => {
63
- if (!isDraggingRef.current) setTasks(initialTasks);
64
- }, [initialTasks]);
65
-
66
- useEffect(() => {
67
- savePreference("brief:kanban-density", density);
68
- }, [density]);
69
-
70
- useEffect(() => {
71
- savePreference("brief:kanban-filter", boardFilter);
72
- }, [boardFilter]);
73
-
74
- const sensors = useSensors(
75
- useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
76
- // Press-and-hold to "pick up" a card on touch. A generous tolerance means
77
- // natural finger drift during the hold doesn't cancel the pickup and fall
78
- // through to a tap (which opens the task instead of dragging it).
79
- useSensor(TouchSensor, { activationConstraint: { delay: 200, tolerance: 25 } })
80
- );
81
-
82
- const filtered = filterNoteId ? tasks.filter((t) => t.note.id === filterNoteId) : tasks;
83
- const filteredTasks = filtered.filter((task) => {
84
- if (boardFilter === "overdue") return isOverdue(task);
85
- if (boardFilter === "agent") return task.assigneeType === "AGENT";
86
- return true;
87
- });
88
-
89
- // Collect orphaned tasks (status not in any visible column)
90
- const visibleColumnKeys = new Set(visibleColumns.map((c) => c.key));
91
- const orphanedTasks = filteredTasks.filter((t) => !visibleColumnKeys.has(t.status));
92
-
93
- function handleDragStart({ active }: DragStartEvent) {
94
- const task = tasks.find((t) => t.id === active.id) ?? null;
95
- setActiveTask(task);
96
- preDragStatusRef.current = task?.status ?? null;
97
- isDraggingRef.current = true;
98
- }
99
-
100
- // over.id can be a column ID or a card ID (when hovering over a card).
101
- // If it's a card ID, infer the target column from that card's current status.
102
- function resolveColumn(overId: string): string | undefined {
103
- return (
104
- visibleColumns.find((c) => c.key === overId)?.key ??
105
- tasks.find((t) => t.id === overId)?.status
106
- );
107
- }
108
-
109
- function handleDragOver({ active, over }: DragOverEvent) {
110
- if (!over) return;
111
- const columnId = resolveColumn(over.id as string);
112
- if (!columnId) return;
113
-
114
- setTasks((prev) =>
115
- prev.map((t) => (t.id === active.id ? { ...t, status: columnId } : t))
116
- );
117
- }
118
-
119
- // Reusable function to move a task to a new status. Used by both drag-end
120
- // and the context-menu "Move to status" action. Optimistic with revert on failure.
121
- // `prevStatus` is the status to compare against (the task's status *before* the
122
- // move). It defaults to the current state value, but callers that have already
123
- // applied an optimistic update must pass the original status explicitly —
124
- // otherwise the current-state read will already equal `newStatus` and the
125
- // PATCH will be skipped.
126
- async function moveTask(taskId: string, newStatus: string, prevStatus?: string) {
127
- const currentState = tasks.find((t) => t.id === taskId)?.status;
128
- const originalStatus = prevStatus ?? currentState;
129
- if (!originalStatus || newStatus === originalStatus) return;
130
-
131
- // Optimistic update (idempotent — caller may have already applied it)
132
- setTasks((prev) => prev.map((t) => (t.id === taskId ? { ...t, status: newStatus } : t)));
133
-
134
- try {
135
- const res = await fetch(`/api/tasks/${taskId}`, {
136
- method: "PATCH",
137
- headers: { "Content-Type": "application/json" },
138
- body: JSON.stringify({ status: newStatus }),
139
- });
140
- if (!res.ok) throw new Error("patch failed");
141
- router.refresh();
142
- } catch {
143
- // Revert optimistic update on failure.
144
- setTasks((prev) => prev.map((t) => (t.id === taskId ? { ...t, status: originalStatus } : t)));
145
- }
146
- }
147
-
148
- async function handleDragEnd({ active, over }: DragEndEvent) {
149
- setActiveTask(null);
150
-
151
- const columnId = over ? resolveColumn(over.id as string) : undefined;
152
-
153
- // Dropped outside any valid target — revert to original status.
154
- if (!columnId) {
155
- if (preDragStatusRef.current) {
156
- setTasks((prev) =>
157
- prev.map((t) =>
158
- t.id === active.id ? { ...t, status: preDragStatusRef.current! } : t
159
- )
160
- );
161
- }
162
- preDragStatusRef.current = null;
163
- isDraggingRef.current = false;
164
- return;
165
- }
166
- // Capture the pre-drag status BEFORE resetting the ref, so moveTask can
167
- // compare against the *original* column (not the one dragOver already set).
168
- const prevStatus = preDragStatusRef.current;
169
- preDragStatusRef.current = null;
170
-
171
- // Apply the resolved column immediately — don't rely on dragOver having
172
- // already fired (touch drags can drop before a final dragOver event).
173
- setTasks((prev) =>
174
- prev.map((t) => (t.id === active.id ? { ...t, status: columnId } : t))
175
- );
176
-
177
- isDraggingRef.current = false;
178
- // Pass the pre-drag status so moveTask compares against the *original*
179
- // column, not the one handleDragOver already applied optimistically.
180
- await moveTask(active.id as string, columnId, prevStatus ?? undefined);
181
- }
182
-
183
- if (filtered.length === 0) {
184
- return (
185
- <div className="flex flex-1 flex-col items-center justify-center gap-3 text-center">
186
- <p className="text-sm font-medium text-zinc-500">No tasks yet</p>
187
- <p className="text-xs text-zinc-700 max-w-xs">
188
- Write a note and add <code className="font-mono text-zinc-500">- [ ] Task description</code> to create tasks.
189
- </p>
190
- </div>
191
- );
192
- }
193
-
194
- const boardScopeLabel = filterNoteId
195
- ? "Filtered to one note"
196
- : filterFolderId
197
- ? "Filtered to one folder"
198
- : "All visible tasks";
199
-
200
- return (
201
- <DndContext
202
- sensors={sensors}
203
- collisionDetection={(args) => {
204
- const hits = pointerWithin(args);
205
- return hits.length > 0 ? hits : rectIntersection(args);
206
- }}
207
- autoScroll={{ threshold: { x: 0.1, y: 0.1 } }}
208
- onDragStart={handleDragStart}
209
- onDragOver={handleDragOver}
210
- onDragEnd={handleDragEnd}
211
- >
212
- <div className="flex h-full min-h-0 flex-col gap-3 p-3 md:p-4">
213
- <div className="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-zinc-800 bg-zinc-950/60 px-3 py-2">
214
- <p className="text-xs text-zinc-500">{boardScopeLabel}</p>
215
- <div className="flex flex-wrap items-center gap-2">
216
- <div
217
- aria-label="Quick filters"
218
- className="inline-flex items-center rounded-md border border-zinc-800 bg-zinc-950 p-1"
219
- >
220
- {[
221
- { key: "all", label: "All" },
222
- { key: "overdue", label: "Overdue" },
223
- { key: "agent", label: "Agent" },
224
- ].map((filterOption) => (
225
- <button
226
- key={filterOption.key}
227
- type="button"
228
- aria-pressed={boardFilter === filterOption.key}
229
- onClick={() => setBoardFilter(filterOption.key as BoardFilter)}
230
- className={`rounded px-2 py-1 text-xs transition-colors ${
231
- boardFilter === filterOption.key
232
- ? "bg-zinc-800 text-zinc-200"
233
- : "text-zinc-500 hover:text-zinc-300"
234
- }`}
235
- >
236
- {filterOption.label}
237
- </button>
238
- ))}
239
- </div>
240
- <div
241
- aria-label="Card density"
242
- className="inline-flex items-center rounded-md border border-zinc-800 bg-zinc-950 p-1"
243
- >
244
- <button
245
- type="button"
246
- aria-pressed={density === "comfortable"}
247
- onClick={() => setDensity("comfortable")}
248
- className={`rounded px-2 py-1 text-xs transition-colors ${
249
- density === "comfortable"
250
- ? "bg-zinc-800 text-zinc-200"
251
- : "text-zinc-500 hover:text-zinc-300"
252
- }`}
253
- >
254
- Comfortable
255
- </button>
256
- <button
257
- type="button"
258
- aria-pressed={density === "compact"}
259
- onClick={() => setDensity("compact")}
260
- className={`rounded px-2 py-1 text-xs transition-colors ${
261
- density === "compact"
262
- ? "bg-zinc-800 text-zinc-200"
263
- : "text-zinc-500 hover:text-zinc-300"
264
- }`}
265
- >
266
- Compact
267
- </button>
268
- </div>
269
- </div>
270
- </div>
271
-
272
- <div className="flex min-h-0 flex-col gap-3 overflow-y-auto md:flex-row md:overflow-x-auto md:overflow-y-hidden">
273
- {visibleColumns.map((col) => {
274
- const colTasks = filteredTasks.filter((t) => t.status === col.key);
275
- return (
276
- <SortableContext
277
- key={col.key}
278
- id={col.key}
279
- items={colTasks.map((t) => t.id)}
280
- strategy={verticalListSortingStrategy}
281
- >
282
- <KanbanColumn
283
- id={col.key}
284
- label={col.label}
285
- color={col.color}
286
- count={colTasks.length}
287
- density={density}
288
- isFilteredEmpty={boardFilter !== "all" && colTasks.length === 0}
289
- >
290
- {colTasks.map((task) => (
291
- <KanbanCard
292
- key={task.id}
293
- task={task}
294
- onMoveTask={moveTask}
295
- backHref={
296
- filterNoteId
297
- ? `/kanban?note=${filterNoteId}`
298
- : filterFolderId
299
- ? `/kanban?folder=${filterFolderId}`
300
- : "/kanban"
301
- }
302
- />
303
- ))}
304
- </KanbanColumn>
305
- </SortableContext>
306
- );
307
- })}
308
-
309
- {/* Orphaned tasks column - tasks with status not in visible columns */}
310
- {orphanedTasks.length > 0 && (
311
- <SortableContext
312
- key="orphaned"
313
- id="orphaned"
314
- items={orphanedTasks.map((t) => t.id)}
315
- strategy={verticalListSortingStrategy}
316
- >
317
- <KanbanColumn
318
- id="orphaned"
319
- label="Uncategorized"
320
- color="border-zinc-600"
321
- count={orphanedTasks.length}
322
- density={density}
323
- >
324
- {orphanedTasks.map((task) => (
325
- <KanbanCard
326
- key={task.id}
327
- task={task}
328
- onMoveTask={moveTask}
329
- backHref={
330
- filterNoteId
331
- ? `/kanban?note=${filterNoteId}`
332
- : filterFolderId
333
- ? `/kanban?folder=${filterFolderId}`
334
- : "/kanban"
335
- }
336
- />
337
- ))}
338
- </KanbanColumn>
339
- </SortableContext>
340
- )}
341
- </div>
342
- </div>
343
-
344
- <DragOverlay>
345
- {activeTask && (
346
- <KanbanCard
347
- task={activeTask}
348
- isDragging
349
- backHref={
350
- filterNoteId
351
- ? `/kanban?note=${filterNoteId}`
352
- : filterFolderId
353
- ? `/kanban?folder=${filterFolderId}`
354
- : "/kanban"
355
- }
356
- />
357
- )}
358
- </DragOverlay>
359
- </DndContext>
360
- );
361
- }
@@ -1,102 +0,0 @@
1
- "use client";
2
-
3
- import React, { forwardRef } from "react";
4
- import type { KanbanTask } from "./kanban-board";
5
- import type { KanbanStatusConfig } from "@/lib/kanban-status";
6
-
7
- // Map status border-color classes to a dot color class for the menu indicator.
8
- const DOT_COLORS: Record<string, string> = {
9
- "border-zinc-700": "bg-zinc-500",
10
- "border-violet-700": "bg-violet-500",
11
- "border-blue-700": "bg-blue-500",
12
- "border-amber-700": "bg-amber-500",
13
- "border-emerald-700": "bg-emerald-500",
14
- "border-zinc-600": "bg-zinc-400",
15
- };
16
-
17
- type MenuProps = {
18
- task: KanbanTask;
19
- statuses: KanbanStatusConfig[];
20
- onSelect: (statusKey: string) => void;
21
- onClose: () => void;
22
- /** When true, renders without the floating wrapper (for MobileBottomSheet). */
23
- mobile?: boolean;
24
- /** Position for the desktop floating menu. */
25
- pos?: { top: number; left: number };
26
- };
27
-
28
- function StatusList({
29
- task,
30
- statuses,
31
- onSelect,
32
- onClose,
33
- }: Pick<MenuProps, "task" | "statuses" | "onSelect" | "onClose">) {
34
- return (
35
- <div className="flex flex-col">
36
- {statuses.map((s) => {
37
- const isCurrent = s.key === task.status;
38
- const dotColor = DOT_COLORS[s.color] ?? "bg-zinc-500";
39
- return (
40
- <button
41
- key={s.key}
42
- type="button"
43
- disabled={isCurrent}
44
- onMouseDown={(e) => {
45
- // Use mousedown so the menu closes before the outside-click handler fires
46
- e.preventDefault();
47
- if (!isCurrent) {
48
- onSelect(s.key);
49
- onClose();
50
- }
51
- }}
52
- onTouchEnd={(e) => {
53
- e.preventDefault();
54
- if (!isCurrent) {
55
- onSelect(s.key);
56
- onClose();
57
- }
58
- }}
59
- className={`flex items-center gap-2.5 px-3 py-2 text-sm text-left transition-colors ${
60
- isCurrent
61
- ? "text-zinc-500 cursor-default"
62
- : "text-zinc-300 hover:bg-zinc-800 active:bg-zinc-700"
63
- }`}
64
- >
65
- <span className={`h-2 w-2 shrink-0 rounded-full ${dotColor}`} aria-hidden />
66
- <span className="flex-1 truncate">{s.label}</span>
67
- {isCurrent && (
68
- <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden className="text-zinc-500 shrink-0">
69
- <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z" />
70
- </svg>
71
- )}
72
- </button>
73
- );
74
- })}
75
- </div>
76
- );
77
- }
78
-
79
- /**
80
- * Status picker menu for kanban cards.
81
- * Desktop: rendered as a fixed-position floating menu.
82
- * Mobile: rendered inline inside a MobileBottomSheet (pass `mobile` prop).
83
- */
84
- export const KanbanCardMenu = forwardRef<HTMLDivElement, MenuProps>(
85
- function KanbanCardMenu({ task, statuses, onSelect, onClose, mobile, pos }, ref) {
86
- if (mobile) {
87
- return (
88
- <StatusList task={task} statuses={statuses} onSelect={onSelect} onClose={onClose} />
89
- );
90
- }
91
-
92
- return (
93
- <div
94
- ref={ref}
95
- className="fixed z-[70] w-44 rounded-lg border border-zinc-700 bg-zinc-900 py-1 shadow-xl"
96
- style={pos ? { top: pos.top, left: pos.left } : undefined}
97
- >
98
- <StatusList task={task} statuses={statuses} onSelect={onSelect} onClose={onClose} />
99
- </div>
100
- );
101
- }
102
- );
@@ -1,227 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useRef, useEffect } from "react";
4
- import { useSortable } from "@dnd-kit/sortable";
5
- import { CSS } from "@dnd-kit/utilities";
6
- import { useRouter } from "next/navigation";
7
- import type { KanbanTask } from "./kanban-board";
8
- import { isOverdue } from "@/lib/task-utils";
9
- import { useIsMobile } from "@/lib/use-is-mobile";
10
- import { useKanbanStatuses } from "./kanban-status-context";
11
- import { KanbanCardMenu } from "./kanban-card-menu";
12
- import { MobileBottomSheet } from "@/components/notes/mobile-bottom-sheet";
13
- import { createPortal } from "react-dom";
14
-
15
- type Props = {
16
- task: KanbanTask;
17
- isDragging?: boolean;
18
- backHref: string;
19
- onMoveTask?: (taskId: string, newStatus: string) => void;
20
- };
21
-
22
- export function KanbanCard({ task, isDragging, backHref, onMoveTask }: Props) {
23
- const router = useRouter();
24
- const isMobile = useIsMobile();
25
- const statuses = useKanbanStatuses();
26
- const { attributes, listeners, setNodeRef, transform, transition, isDragging: isSortableDragging } =
27
- useSortable({ id: task.id });
28
-
29
- const [menuOpen, setMenuOpen] = useState(false);
30
- const [menuPos, setMenuPos] = useState<{ top: number; left: number } | null>(null);
31
- const menuRef = useRef<HTMLDivElement>(null);
32
- const buttonRef = useRef<HTMLButtonElement>(null);
33
-
34
- const style = {
35
- transform: CSS.Transform.toString(transform),
36
- transition,
37
- opacity: isSortableDragging ? 0.4 : 1,
38
- touchAction: "none" as const,
39
- };
40
-
41
- const assigneeLabel =
42
- task.assigneeType === "AGENT"
43
- ? task.claimedByAlias
44
- ? `@agent:${task.claimedByAlias}`
45
- : "@agent"
46
- : task.assignee?.name
47
- ? `@${task.assignee.name}`
48
- : null;
49
-
50
- // Stale if claimed and no heartbeat for >30 minutes
51
- const isStale =
52
- task.claimedBy != null &&
53
- task.lastHeartbeat != null &&
54
- Date.now() - new Date(task.lastHeartbeat).getTime() > 30 * 60 * 1000;
55
-
56
- const taskIsOverdue = isOverdue(task);
57
- const formattedDueDate = task.dueDate
58
- ? new Date(task.dueDate).toLocaleDateString(undefined, { month: "short", day: "numeric" })
59
- : null;
60
-
61
- // Dismiss floating menu on outside click or Escape
62
- useEffect(() => {
63
- if (!menuOpen || isMobile) return;
64
- function onMouseDown(e: MouseEvent) {
65
- if (
66
- menuRef.current && !menuRef.current.contains(e.target as Node) &&
67
- buttonRef.current && !buttonRef.current.contains(e.target as Node)
68
- ) {
69
- setMenuOpen(false);
70
- setMenuPos(null);
71
- }
72
- }
73
- function onKeyDown(e: KeyboardEvent) {
74
- if (e.key === "Escape") { setMenuOpen(false); setMenuPos(null); }
75
- }
76
- document.addEventListener("mousedown", onMouseDown);
77
- document.addEventListener("keydown", onKeyDown);
78
- return () => { document.removeEventListener("mousedown", onMouseDown); document.removeEventListener("keydown", onKeyDown); };
79
- }, [menuOpen, isMobile]);
80
-
81
- function handleMenuButton(e: React.MouseEvent) {
82
- e.stopPropagation();
83
- e.preventDefault();
84
- if (menuOpen) { setMenuOpen(false); setMenuPos(null); return; }
85
- if (isMobile) {
86
- setMenuOpen(true);
87
- } else {
88
- const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
89
- setMenuPos({ top: rect.bottom + 4, left: Math.min(rect.left, window.innerWidth - 200) });
90
- setMenuOpen(true);
91
- }
92
- }
93
-
94
- function handleContextMenu(e: React.MouseEvent) {
95
- e.preventDefault();
96
- e.stopPropagation();
97
- if (isMobile) return; // long-press not needed on mobile (we have the button)
98
- setMenuPos({ top: e.clientY, left: Math.min(e.clientX, window.innerWidth - 200) });
99
- setMenuOpen(true);
100
- }
101
-
102
- function handleSelectStatus(statusKey: string) {
103
- setMenuOpen(false);
104
- setMenuPos(null);
105
- if (statusKey !== task.status && onMoveTask) {
106
- onMoveTask(task.id, statusKey);
107
- }
108
- }
109
-
110
- function handleCardClick(e: React.MouseEvent) {
111
- // Don't navigate if clicking the menu button or menu itself
112
- if (buttonRef.current?.contains(e.target as Node)) return;
113
- if (menuRef.current?.contains(e.target as Node)) return;
114
- router.push(`/tasks/${task.id}?from=${encodeURIComponent(backHref)}`);
115
- }
116
-
117
- function handleCardKeyDown(e: React.KeyboardEvent) {
118
- if (e.key === "Enter" || e.key === " ") {
119
- e.preventDefault();
120
- router.push(`/tasks/${task.id}?from=${encodeURIComponent(backHref)}`);
121
- }
122
- }
123
-
124
- return (
125
- <div
126
- ref={setNodeRef}
127
- style={style}
128
- {...attributes}
129
- {...listeners}
130
- data-kanban-card
131
- onContextMenu={handleContextMenu}
132
- className={`group relative rounded-md border p-2.5 cursor-grab active:cursor-grabbing select-none [-webkit-touch-callout:none] [-webkit-user-select:none] transition-[transform,box-shadow,border-color] duration-150 active:scale-[1.02] active:border-zinc-500 active:shadow-lg active:shadow-black/30 ${
133
- taskIsOverdue
134
- ? "border-red-800/60 bg-red-950/40"
135
- : "border-zinc-700 bg-zinc-900"
136
- } ${isDragging ? "shadow-lg shadow-black/40 rotate-1" : "hover:border-zinc-600"}`}
137
- >
138
- {/* Card body — click navigates to task */}
139
- <div
140
- role="link"
141
- tabIndex={0}
142
- onClick={handleCardClick}
143
- onKeyDown={handleCardKeyDown}
144
- draggable={false}
145
- className="block pr-6"
146
- >
147
- <div className="flex items-start gap-1.5">
148
- <p className="flex-1 text-sm font-medium leading-snug text-zinc-100 line-clamp-2">
149
- {task.title}
150
- </p>
151
- {taskIsOverdue && <span title="Overdue" className="shrink-0 text-red-400 text-xs">!</span>}
152
- {isStale && <span title="No heartbeat — agent may be inactive" className="shrink-0 text-amber-400 text-xs">⚠</span>}
153
- </div>
154
- <div className="mt-1.5 flex items-center gap-1.5 text-xs text-zinc-600">
155
- {task.note.folder && (
156
- <>
157
- <span className="truncate max-w-[80px] text-zinc-500">{task.note.folder.name}</span>
158
- <span>/</span>
159
- </>
160
- )}
161
- <span className="truncate max-w-[100px]">{task.note.title}</span>
162
- {formattedDueDate && (
163
- <>
164
- <span>·</span>
165
- <span className={taskIsOverdue ? "text-red-300" : "text-zinc-500"}>{formattedDueDate}</span>
166
- </>
167
- )}
168
- {assigneeLabel && (
169
- <>
170
- <span>·</span>
171
- <span className={task.assigneeType === "AGENT" ? "text-blue-400" : "text-zinc-500"}>
172
- {assigneeLabel}
173
- </span>
174
- </>
175
- )}
176
- </div>
177
- </div>
178
-
179
- {/* "..." menu button */}
180
- <button
181
- ref={buttonRef}
182
- type="button"
183
- aria-label="Move task"
184
- onPointerDown={(e) => e.stopPropagation()}
185
- onTouchStart={(e) => e.stopPropagation()}
186
- onClick={handleMenuButton}
187
- className="absolute top-1.5 right-1.5 flex h-6 w-6 items-center justify-center rounded text-zinc-500 hover:text-zinc-300 hover:bg-zinc-800 opacity-0 group-hover:opacity-100 md:opacity-0 md:group-hover:opacity-100 max-md:opacity-70 transition-opacity"
188
- >
189
- <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden>
190
- <circle cx="8" cy="3" r="1.5" />
191
- <circle cx="8" cy="8" r="1.5" />
192
- <circle cx="8" cy="13" r="1.5" />
193
- </svg>
194
- </button>
195
-
196
- {/* Desktop: floating context menu */}
197
- {!isMobile && menuOpen && menuPos && createPortal(
198
- <KanbanCardMenu
199
- ref={menuRef}
200
- task={task}
201
- statuses={statuses}
202
- pos={menuPos}
203
- onSelect={handleSelectStatus}
204
- onClose={() => { setMenuOpen(false); setMenuPos(null); }}
205
- />,
206
- document.body,
207
- )}
208
-
209
- {/* Mobile: bottom sheet */}
210
- {isMobile && (
211
- <MobileBottomSheet
212
- open={menuOpen}
213
- onClose={() => { setMenuOpen(false); setMenuPos(null); }}
214
- title="Move to status"
215
- >
216
- <KanbanCardMenu
217
- task={task}
218
- statuses={statuses}
219
- onSelect={handleSelectStatus}
220
- onClose={() => { setMenuOpen(false); setMenuPos(null); }}
221
- mobile
222
- />
223
- </MobileBottomSheet>
224
- )}
225
- </div>
226
- );
227
- }