@fluid-app/portal-sdk 0.1.231 → 0.1.232

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 (141) hide show
  1. package/dist/{AppDownloadScreen-BmYJ7Zgb.cjs → AppDownloadScreen-B0YwMqYM.cjs} +2 -2
  2. package/dist/{AppDownloadScreen-BmYJ7Zgb.cjs.map → AppDownloadScreen-B0YwMqYM.cjs.map} +1 -1
  3. package/dist/{AppDownloadScreen-zrhWlSE7.mjs → AppDownloadScreen-Dj5ZPsSb.mjs} +2 -2
  4. package/dist/{AppDownloadScreen-zrhWlSE7.mjs.map → AppDownloadScreen-Dj5ZPsSb.mjs.map} +1 -1
  5. package/dist/{AppNavigationContext-C1-hd9Rw.cjs → AppNavigationContext-BDs1cOuG.cjs} +1 -1
  6. package/dist/{AppNavigationContext-C1-hd9Rw.cjs.map → AppNavigationContext-BDs1cOuG.cjs.map} +1 -1
  7. package/dist/{AppNavigationContext-BcZZMtV6.mjs → AppNavigationContext-DNod9mf6.mjs} +1 -1
  8. package/dist/{AppNavigationContext-BcZZMtV6.mjs.map → AppNavigationContext-DNod9mf6.mjs.map} +1 -1
  9. package/dist/{ContactsScreen-BfS33bcq.cjs → ContactsScreen-9cZWC9PP.cjs} +6 -6
  10. package/dist/{ContactsScreen-DHHzJhO4.mjs → ContactsScreen-B4Ue9m5H.mjs} +9 -22
  11. package/dist/ContactsScreen-B4Ue9m5H.mjs.map +1 -0
  12. package/dist/{ContactsScreen-gq1fPZzb.cjs → ContactsScreen-BTQ_qfq3.cjs} +10 -23
  13. package/dist/ContactsScreen-BTQ_qfq3.cjs.map +1 -0
  14. package/dist/{CustomersScreen-D55HjYHC.mjs → CustomersScreen-CX3P2KpF.mjs} +1 -1
  15. package/dist/{CustomersScreen-D55HjYHC.mjs.map → CustomersScreen-CX3P2KpF.mjs.map} +1 -1
  16. package/dist/{CustomersScreen-DW3BuhBs.cjs → CustomersScreen-GROLIynQ.cjs} +1 -1
  17. package/dist/{CustomersScreen-DW3BuhBs.cjs.map → CustomersScreen-GROLIynQ.cjs.map} +1 -1
  18. package/dist/{FluidProvider-DWNo4QTC.cjs → FluidProvider-BNL_Apw2.cjs} +26 -6
  19. package/dist/{FluidProvider-DWNo4QTC.cjs.map → FluidProvider-BNL_Apw2.cjs.map} +1 -1
  20. package/dist/{FluidProvider-i69t4zBo.mjs → FluidProvider-Dg-eouRw.mjs} +26 -6
  21. package/dist/FluidProvider-Dg-eouRw.mjs.map +1 -0
  22. package/dist/{InfiniteScrollSentinel-bVdxImf8.mjs → InfiniteScrollSentinel-CQD9JPVe.mjs} +1 -1
  23. package/dist/{InfiniteScrollSentinel-bVdxImf8.mjs.map → InfiniteScrollSentinel-CQD9JPVe.mjs.map} +1 -1
  24. package/dist/{InfiniteScrollSentinel-LcHXpBw-.cjs → InfiniteScrollSentinel-V1ubmA8z.cjs} +1 -1
  25. package/dist/{InfiniteScrollSentinel-LcHXpBw-.cjs.map → InfiniteScrollSentinel-V1ubmA8z.cjs.map} +1 -1
  26. package/dist/{MessagingScreen-CDx6ryDj.cjs → MessagingScreen-BMoCh4MT.cjs} +6 -6
  27. package/dist/{MessagingScreen-Crp-Kl4c.cjs → MessagingScreen-CxpLlTW_.cjs} +5 -5
  28. package/dist/{MessagingScreen-Crp-Kl4c.cjs.map → MessagingScreen-CxpLlTW_.cjs.map} +1 -1
  29. package/dist/{MessagingScreen-BbIKoH2L.mjs → MessagingScreen-Q17pdhUz.mjs} +3 -3
  30. package/dist/{MessagingScreen-BbIKoH2L.mjs.map → MessagingScreen-Q17pdhUz.mjs.map} +1 -1
  31. package/dist/{MySiteScreen-DsR9VEwa.cjs → MySiteScreen-5-eNH2S6.cjs} +3 -3
  32. package/dist/{MySiteScreen-DwgWY0cl.cjs → MySiteScreen-B1yomsp6.cjs} +3 -3
  33. package/dist/{MySiteScreen-DwgWY0cl.cjs.map → MySiteScreen-B1yomsp6.cjs.map} +1 -1
  34. package/dist/{MySiteScreen-Q9xN2oxD.mjs → MySiteScreen-BBT8FN5s.mjs} +3 -3
  35. package/dist/{MySiteScreen-Q9xN2oxD.mjs.map → MySiteScreen-BBT8FN5s.mjs.map} +1 -1
  36. package/dist/OrdersScreen-Co2oatu4.cjs +9 -0
  37. package/dist/{OrdersScreen-DcC_-N8h.mjs → OrdersScreen-DkTGTQJZ.mjs} +5 -5
  38. package/dist/{OrdersScreen-DcC_-N8h.mjs.map → OrdersScreen-DkTGTQJZ.mjs.map} +1 -1
  39. package/dist/{OrdersScreen-DxQmihvZ.cjs → OrdersScreen-e5DdNpD-.cjs} +5 -5
  40. package/dist/{OrdersScreen-DxQmihvZ.cjs.map → OrdersScreen-e5DdNpD-.cjs.map} +1 -1
  41. package/dist/{PortalProductsApiProvider-CP1xk872.mjs → PortalProductsApiProvider-BIZg_c4Y.mjs} +2 -2
  42. package/dist/{PortalProductsApiProvider-CP1xk872.mjs.map → PortalProductsApiProvider-BIZg_c4Y.mjs.map} +1 -1
  43. package/dist/{PortalProductsApiProvider-jDoPfaPB.cjs → PortalProductsApiProvider-DL8nl7To.cjs} +2 -2
  44. package/dist/{PortalProductsApiProvider-jDoPfaPB.cjs.map → PortalProductsApiProvider-DL8nl7To.cjs.map} +1 -1
  45. package/dist/{ProfileScreen-B2JEvxsd.cjs → ProfileScreen-BSWw10cc.cjs} +6 -6
  46. package/dist/{ProfileScreen-ftzRbWgc.mjs → ProfileScreen-CHsIDbg8.mjs} +6 -6
  47. package/dist/{ProfileScreen-ftzRbWgc.mjs.map → ProfileScreen-CHsIDbg8.mjs.map} +1 -1
  48. package/dist/{ProfileScreen-BPdHZZXV.cjs → ProfileScreen-CVOS2By3.cjs} +6 -6
  49. package/dist/{ProfileScreen-BPdHZZXV.cjs.map → ProfileScreen-CVOS2By3.cjs.map} +1 -1
  50. package/dist/{ScreenHeaderContext-4WYXIqQ5.mjs → ScreenHeaderContext-Cemdo7bM.mjs} +1 -1
  51. package/dist/{ScreenHeaderContext-4WYXIqQ5.mjs.map → ScreenHeaderContext-Cemdo7bM.mjs.map} +1 -1
  52. package/dist/{ScreenHeaderContext-PbjwAMeB.cjs → ScreenHeaderContext-oIu5Bvhs.cjs} +1 -1
  53. package/dist/{ScreenHeaderContext-PbjwAMeB.cjs.map → ScreenHeaderContext-oIu5Bvhs.cjs.map} +1 -1
  54. package/dist/{SearchSort-BQ-nf9gJ.mjs → SearchSort-DN0gsmxk.mjs} +1 -1
  55. package/dist/{SearchSort-BQ-nf9gJ.mjs.map → SearchSort-DN0gsmxk.mjs.map} +1 -1
  56. package/dist/{SearchSort-Hwga1dIi.cjs → SearchSort-O89uV2dl.cjs} +1 -1
  57. package/dist/{SearchSort-Hwga1dIi.cjs.map → SearchSort-O89uV2dl.cjs.map} +1 -1
  58. package/dist/{ShareablesScreen-BjpwByeL.mjs → ShareablesScreen-DHKFnIOE.mjs} +11 -11
  59. package/dist/{ShareablesScreen-BjpwByeL.mjs.map → ShareablesScreen-DHKFnIOE.mjs.map} +1 -1
  60. package/dist/{ShareablesScreen-iXNQCKSx.cjs → ShareablesScreen-DQjgnn2x.cjs} +8 -8
  61. package/dist/{ShareablesScreen-BsICzJuf.cjs → ShareablesScreen-DfrTNnRw.cjs} +12 -12
  62. package/dist/{ShareablesScreen-BsICzJuf.cjs.map → ShareablesScreen-DfrTNnRw.cjs.map} +1 -1
  63. package/dist/{ShopScreen-recdiuo_.cjs → ShopScreen-CFR2O1jn.cjs} +7 -7
  64. package/dist/{ShopScreen-d6e2Bm0N.mjs → ShopScreen-CLN8FFiL.mjs} +7 -7
  65. package/dist/{ShopScreen-d6e2Bm0N.mjs.map → ShopScreen-CLN8FFiL.mjs.map} +1 -1
  66. package/dist/{ShopScreen-DBopix8F.cjs → ShopScreen-jk3Y3-x8.cjs} +7 -7
  67. package/dist/{ShopScreen-DBopix8F.cjs.map → ShopScreen-jk3Y3-x8.cjs.map} +1 -1
  68. package/dist/{SubscriptionsScreen-CcaqnqiQ.mjs → SubscriptionsScreen-CVPj6hhP.mjs} +10 -10
  69. package/dist/{SubscriptionsScreen-CcaqnqiQ.mjs.map → SubscriptionsScreen-CVPj6hhP.mjs.map} +1 -1
  70. package/dist/{SubscriptionsScreen-BI296E2y.cjs → SubscriptionsScreen-DOf7rlRP.cjs} +6 -6
  71. package/dist/{SubscriptionsScreen-DkiuXzRX.cjs → SubscriptionsScreen-r2_drNFg.cjs} +11 -11
  72. package/dist/{SubscriptionsScreen-DkiuXzRX.cjs.map → SubscriptionsScreen-r2_drNFg.cjs.map} +1 -1
  73. package/dist/ToDoWidget-BgyusdPn.cjs +8 -0
  74. package/dist/{ToDoWidget-CaDOZtAB.cjs → ToDoWidget-CQ_zTbhz.cjs} +114 -31
  75. package/dist/ToDoWidget-CQ_zTbhz.cjs.map +1 -0
  76. package/dist/{ToDoWidget-Bv258x8F.mjs → ToDoWidget-DYGt45vL.mjs} +116 -23
  77. package/dist/ToDoWidget-DYGt45vL.mjs.map +1 -0
  78. package/dist/{UpgradeScreen-4Z5_ALSr.cjs → UpgradeScreen-BJbdv9T9.cjs} +1 -1
  79. package/dist/{UpgradeScreen-CGiVn0KG.mjs → UpgradeScreen-DMxxZjj_.mjs} +1 -1
  80. package/dist/{UpgradeScreen-CGiVn0KG.mjs.map → UpgradeScreen-DMxxZjj_.mjs.map} +1 -1
  81. package/dist/{UpgradeScreen-BaclFXEh.cjs → UpgradeScreen-QhhBuHXE.cjs} +1 -1
  82. package/dist/{UpgradeScreen-BaclFXEh.cjs.map → UpgradeScreen-QhhBuHXE.cjs.map} +1 -1
  83. package/dist/{VideoWidget-BntlfHhP.cjs → VideoWidget-Bc6ZAAaA.cjs} +1 -1
  84. package/dist/{VideoWidget-BntlfHhP.cjs.map → VideoWidget-Bc6ZAAaA.cjs.map} +1 -1
  85. package/dist/{VideoWidget-DmHZ05vp.mjs → VideoWidget-lTyeZypJ.mjs} +1 -1
  86. package/dist/{VideoWidget-DmHZ05vp.mjs.map → VideoWidget-lTyeZypJ.mjs.map} +1 -1
  87. package/dist/{dist-FHf4OHgt.cjs → dist-BQZkLGL6.cjs} +1 -1
  88. package/dist/{dist-FHf4OHgt.cjs.map → dist-BQZkLGL6.cjs.map} +1 -1
  89. package/dist/{dist-BQCx-9SK.cjs → dist-DbRTQ2QF.cjs} +1 -1
  90. package/dist/{dist-BQCx-9SK.cjs.map → dist-DbRTQ2QF.cjs.map} +1 -1
  91. package/dist/{dist-q1wrtxfG.mjs → dist-PbA1vxAz.mjs} +1 -1
  92. package/dist/{dist-q1wrtxfG.mjs.map → dist-PbA1vxAz.mjs.map} +1 -1
  93. package/dist/{dist-5XPflEEG.cjs → dist-myuZC8sf.cjs} +2 -2
  94. package/dist/{dist-5XPflEEG.cjs.map → dist-myuZC8sf.cjs.map} +1 -1
  95. package/dist/{dist-CsNsoBdu.mjs → dist-o2cjwzIa.mjs} +2 -2
  96. package/dist/{dist-CsNsoBdu.mjs.map → dist-o2cjwzIa.mjs.map} +1 -1
  97. package/dist/{es-nxOxb57F.cjs → es-UfEBhcZD.cjs} +1 -1
  98. package/dist/{es-nxOxb57F.cjs.map → es-UfEBhcZD.cjs.map} +1 -1
  99. package/dist/{fluid-pay-api-adapter-COBmngde.cjs → fluid-pay-api-adapter-CLP8wfno.cjs} +1 -1
  100. package/dist/{fluid-pay-api-adapter-COBmngde.cjs.map → fluid-pay-api-adapter-CLP8wfno.cjs.map} +1 -1
  101. package/dist/{fluid-pay-api-adapter-Dv2K17WN.mjs → fluid-pay-api-adapter-Dfi0LtxL.mjs} +1 -1
  102. package/dist/{fluid-pay-api-adapter-Dv2K17WN.mjs.map → fluid-pay-api-adapter-Dfi0LtxL.mjs.map} +1 -1
  103. package/dist/{format-Dyb61f6F.cjs → format-CytB2M00.cjs} +1 -1
  104. package/dist/{format-Dyb61f6F.cjs.map → format-CytB2M00.cjs.map} +1 -1
  105. package/dist/index.cjs +49 -49
  106. package/dist/index.d.cts.map +1 -1
  107. package/dist/index.d.mts.map +1 -1
  108. package/dist/index.mjs +48 -48
  109. package/dist/{order-status-badge-CnQE7lFJ.mjs → order-status-badge-CL5XC2va.mjs} +3 -3
  110. package/dist/{order-status-badge-CnQE7lFJ.mjs.map → order-status-badge-CL5XC2va.mjs.map} +1 -1
  111. package/dist/{order-status-badge-NMygmn5d.cjs → order-status-badge-Cqkx76d8.cjs} +3 -3
  112. package/dist/{order-status-badge-NMygmn5d.cjs.map → order-status-badge-Cqkx76d8.cjs.map} +1 -1
  113. package/dist/parse-task-body-BxbA_DC6.cjs +29 -0
  114. package/dist/parse-task-body-BxbA_DC6.cjs.map +1 -0
  115. package/dist/parse-task-body-DEmYvdNM.mjs +24 -0
  116. package/dist/parse-task-body-DEmYvdNM.mjs.map +1 -0
  117. package/dist/{portal_tenant-CxChT6OB.cjs → portal_tenant-CNmiAf_A.cjs} +18 -1
  118. package/dist/{portal_tenant-CxChT6OB.cjs.map → portal_tenant-CNmiAf_A.cjs.map} +1 -1
  119. package/dist/{portal_tenant-f_Cs2YmO.mjs → portal_tenant-Q3x7ALaZ.mjs} +13 -2
  120. package/dist/{portal_tenant-f_Cs2YmO.mjs.map → portal_tenant-Q3x7ALaZ.mjs.map} +1 -1
  121. package/dist/{query-keys-CC2PXIfJ.cjs → query-keys-D3lK70Ea.cjs} +1 -1
  122. package/dist/{query-keys-BkMRwfNo.mjs.map → query-keys-D3lK70Ea.cjs.map} +1 -1
  123. package/dist/{query-keys-BkMRwfNo.mjs → query-keys-xJy_fapN.mjs} +1 -1
  124. package/dist/{query-keys-CC2PXIfJ.cjs.map → query-keys-xJy_fapN.mjs.map} +1 -1
  125. package/dist/{sortable.esm-BSpvRpWg.mjs → sortable.esm-C8G00cCP.mjs} +1 -1
  126. package/dist/{sortable.esm-BSpvRpWg.mjs.map → sortable.esm-C8G00cCP.mjs.map} +1 -1
  127. package/dist/{use-account-Ipii17ZX.mjs → use-account-CBMPhhs7.mjs} +2 -2
  128. package/dist/{use-account-Ipii17ZX.mjs.map → use-account-CBMPhhs7.mjs.map} +1 -1
  129. package/dist/{use-account-Bv_VAVxj.cjs → use-account-DcBCP06c.cjs} +2 -2
  130. package/dist/{use-account-Bv_VAVxj.cjs.map → use-account-DcBCP06c.cjs.map} +1 -1
  131. package/dist/{use-store-3holBUj4.mjs → use-store-By_7tzrN.mjs} +1 -1
  132. package/dist/{use-store-3holBUj4.mjs.map → use-store-By_7tzrN.mjs.map} +1 -1
  133. package/dist/{use-store-D2S1FywW.cjs → use-store-lOOUcpRT.cjs} +1 -1
  134. package/dist/{use-store-D2S1FywW.cjs.map → use-store-lOOUcpRT.cjs.map} +1 -1
  135. package/package.json +13 -13
  136. package/dist/ContactsScreen-DHHzJhO4.mjs.map +0 -1
  137. package/dist/ContactsScreen-gq1fPZzb.cjs.map +0 -1
  138. package/dist/FluidProvider-i69t4zBo.mjs.map +0 -1
  139. package/dist/OrdersScreen-CfnNy52h.cjs +0 -9
  140. package/dist/ToDoWidget-Bv258x8F.mjs.map +0 -1
  141. package/dist/ToDoWidget-CaDOZtAB.cjs.map +0 -1
@@ -0,0 +1,8 @@
1
+ require("./chunk-9hOWP6kD.cjs");
2
+ require("./registry-context-C7-RLxVt.cjs");
3
+ require("./error-state-BDhSltIa.cjs");
4
+ require("./registries-DBb6VjAX.cjs");
5
+ require("./fields-COJ84ouS.cjs");
6
+ require("./src-DpFIi-nj.cjs");
7
+ const require_ToDoWidget = require("./ToDoWidget-CQ_zTbhz.cjs");
8
+ exports.toDoWidgetPropertySchema = require_ToDoWidget.toDoWidgetPropertySchema;
@@ -1,7 +1,10 @@
1
- const require_chunk = require("./chunk-9hOWP6kD.cjs");
1
+ require("./chunk-9hOWP6kD.cjs");
2
2
  const require_registry_context = require("./registry-context-C7-RLxVt.cjs");
3
3
  const require_error_state = require("./error-state-BDhSltIa.cjs");
4
4
  const require_registries = require("./registries-DBb6VjAX.cjs");
5
+ const require_src = require("./src-DpFIi-nj.cjs");
6
+ const require_parse_task_body = require("./parse-task-body-BxbA_DC6.cjs");
7
+ let react = require("react");
5
8
  let _tanstack_react_query = require("@tanstack/react-query");
6
9
  let react_jsx_runtime = require("react/jsx-runtime");
7
10
  let lucide_react = require("lucide-react");
@@ -19,6 +22,7 @@ const PREVIEW_DATA = [
19
22
  dueAt: daysFromNow(1),
20
23
  completedAt: null,
21
24
  createdAt: daysFromNow(-2),
25
+ contactId: 101,
22
26
  contactName: "Sarah Johnson"
23
27
  },
24
28
  {
@@ -27,6 +31,7 @@ const PREVIEW_DATA = [
27
31
  dueAt: daysFromNow(3),
28
32
  completedAt: null,
29
33
  createdAt: daysFromNow(-1),
34
+ contactId: null,
30
35
  contactName: null
31
36
  },
32
37
  {
@@ -35,32 +40,84 @@ const PREVIEW_DATA = [
35
40
  dueAt: daysFromNow(-1),
36
41
  completedAt: null,
37
42
  createdAt: daysFromNow(-5),
43
+ contactId: 102,
38
44
  contactName: "Mike Chen"
39
45
  }
40
46
  ];
41
47
  //#endregion
42
48
  //#region ../widgets/src/hooks/use-todos.ts
49
+ /**
50
+ * Shared cache key for the todos list. Both useTodos (read) and
51
+ * useUpdateTodo (write) use this — drift would silently break optimistic
52
+ * updates.
53
+ */
54
+ function todosQueryKey(args) {
55
+ return [
56
+ "portal-widget-use",
57
+ "todos",
58
+ args.isPreview ? "preview" : args.baseUrl
59
+ ];
60
+ }
43
61
  function useTodos() {
44
62
  const widgetsApi = require_error_state.useWidgetsApi();
45
63
  const { isPreview } = require_error_state.useWidgetPreviewContext();
46
64
  const { baseUrl } = require_registry_context.useDataSourceRegistryConfig();
47
65
  return (0, _tanstack_react_query.useQuery)({
48
- queryKey: [
49
- "portal-widget-use",
50
- "todos",
51
- isPreview ? "preview" : baseUrl
52
- ],
66
+ queryKey: todosQueryKey({
67
+ baseUrl,
68
+ isPreview
69
+ }),
53
70
  queryFn: ({ signal }) => widgetsApi.fetchTodos(signal),
54
71
  enabled: !isPreview,
55
72
  ...isPreview && { placeholderData: PREVIEW_DATA }
56
73
  });
57
74
  }
58
75
  //#endregion
76
+ //#region ../widgets/src/hooks/use-update-todo.ts
77
+ function useUpdateTodo() {
78
+ const widgetsApi = require_error_state.useWidgetsApi();
79
+ const queryClient = (0, _tanstack_react_query.useQueryClient)();
80
+ const { isPreview } = require_error_state.useWidgetPreviewContext();
81
+ const { baseUrl } = require_registry_context.useDataSourceRegistryConfig();
82
+ const queryKey = todosQueryKey({
83
+ baseUrl,
84
+ isPreview
85
+ });
86
+ return (0, _tanstack_react_query.useMutation)({
87
+ mutationFn: ({ id, completed }) => {
88
+ if (isPreview) return Promise.reject(/* @__PURE__ */ new Error("Todo updates are disabled in preview mode"));
89
+ return widgetsApi.updateTodo(id, completed);
90
+ },
91
+ onMutate: async ({ id, completed }) => {
92
+ await queryClient.cancelQueries({ queryKey });
93
+ const previous = queryClient.getQueryData(queryKey);
94
+ queryClient.setQueryData(queryKey, (current) => (current ?? []).map((todo) => todo.id === id ? {
95
+ ...todo,
96
+ completedAt: completed ? (/* @__PURE__ */ new Date()).toISOString() : null
97
+ } : todo));
98
+ return { previous };
99
+ },
100
+ onSuccess: (updated, { completed }) => {
101
+ queryClient.setQueryData(queryKey, (current) => (current ?? []).map((todo) => todo.id === updated.id ? updated : todo));
102
+ if (completed) require_src.fluidToast({
103
+ title: "Todo completed",
104
+ type: "success"
105
+ });
106
+ },
107
+ onError: (_error, _variables, context) => {
108
+ if (context?.previous) queryClient.setQueryData(queryKey, context.previous);
109
+ require_src.fluidToast({
110
+ title: "Failed to update todo",
111
+ type: "error"
112
+ });
113
+ },
114
+ onSettled: () => {
115
+ queryClient.invalidateQueries({ queryKey });
116
+ }
117
+ });
118
+ }
119
+ //#endregion
59
120
  //#region ../widgets/src/widgets/ToDoWidget.tsx
60
- var ToDoWidget_exports = /* @__PURE__ */ require_chunk.__exportAll({
61
- ToDoWidget: () => ToDoWidget,
62
- toDoWidgetPropertySchema: () => toDoWidgetPropertySchema
63
- });
64
121
  function ToDoWidget({ titleEnabled = true, titleText = "To-Do", titleFontSize = "lg", titleColor = "foreground", background = {
65
122
  type: "solid",
66
123
  color: "background"
@@ -68,10 +125,35 @@ function ToDoWidget({ titleEnabled = true, titleText = "To-Do", titleFontSize =
68
125
  const backgroundColor = background.color || "background";
69
126
  const backgroundImage = (background.resource?.image_url || background.resource?.imageUrl) && background.type === "image" ? `url(${background.resource.image_url || background.resource.imageUrl})` : "none";
70
127
  const { data: todos = [], isLoading, isError } = useTodos();
71
- const activeTodos = (todos.length > 0 ? todos : []).filter((todo) => !todo.completedAt);
128
+ const updateTodo = useUpdateTodo();
129
+ const { isPreview } = require_error_state.useWidgetPreviewContext();
130
+ const [pendingIds, setPendingIds] = (0, react.useState)(/* @__PURE__ */ new Set());
131
+ const toggleTodo = (id, completed) => {
132
+ if (isPreview || pendingIds.has(id)) return;
133
+ setPendingIds((prev) => new Set(prev).add(id));
134
+ updateTodo.mutate({
135
+ id,
136
+ completed
137
+ }, { onSettled: () => setPendingIds((prev) => {
138
+ const next = new Set(prev);
139
+ next.delete(id);
140
+ return next;
141
+ }) });
142
+ };
143
+ const activeTodos = todos.filter((todo) => !todo.completedAt);
72
144
  const todosToShow = activeTodos.slice(0, maxItems);
73
145
  const remainingCount = activeTodos.length - todosToShow.length;
74
146
  const isEmpty = activeTodos.length === 0;
147
+ const groupedTodos = todosToShow.reduce((groups, todo) => {
148
+ const existing = groups.find((g) => g.contactId === todo.contactId);
149
+ if (existing) existing.todos.push(todo);
150
+ else groups.push({
151
+ contactId: todo.contactId,
152
+ contactName: todo.contactName,
153
+ todos: [todo]
154
+ });
155
+ return groups;
156
+ }, []);
75
157
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
76
158
  className: `overflow-hidden rounded-${borderRadius} ${require_registries.borderWidthClasses[borderWidth]} ${borderWidth !== "none" ? require_registries.borderColorClasses[borderColor] : ""} bg-${backgroundColor} text-${textColor} p-${padding} ${className ?? ""}`,
77
159
  style: { backgroundImage },
@@ -104,19 +186,26 @@ function ToDoWidget({ titleEnabled = true, titleText = "To-Do", titleFontSize =
104
186
  children: "You've got nothing else To-Do!"
105
187
  })
106
188
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
107
- className: "flex flex-col",
108
- children: todosToShow.map((todo, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
109
- className: `flex items-center gap-3 py-2.5 ${index !== todosToShow.length - 1 ? `border-b border-${textColor}/10` : ""}`,
110
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
111
- type: "checkbox",
112
- className: `h-5 w-5 rounded-full border-2 border-${textColor}/30 bg-transparent`,
113
- checked: !!todo.completedAt,
114
- readOnly: true
115
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
116
- className: "line-clamp-1 flex-1 text-sm",
117
- children: todo.body
118
- })]
119
- }, todo.id))
189
+ className: "flex flex-col gap-3",
190
+ children: groupedTodos.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
191
+ className: "flex flex-col",
192
+ children: [group.contactName && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
193
+ className: `mb-1 text-xs font-semibold tracking-wide uppercase text-${textColor}/60`,
194
+ children: group.contactName
195
+ }), group.todos.map((todo, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
196
+ className: `flex items-center gap-3 py-2 ${index !== group.todos.length - 1 ? `border-b border-${textColor}/10` : ""}`,
197
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
198
+ type: "checkbox",
199
+ className: `h-5 w-5 rounded-full border-2 border-${textColor}/30 bg-transparent not-disabled:cursor-pointer disabled:cursor-not-allowed disabled:opacity-60`,
200
+ checked: !!todo.completedAt,
201
+ disabled: isPreview || pendingIds.has(todo.id),
202
+ onChange: (event) => toggleTodo(todo.id, event.target.checked)
203
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
204
+ className: "line-clamp-1 flex-1 text-sm",
205
+ children: require_parse_task_body.parseTaskBody(todo.body).title
206
+ })]
207
+ }, todo.id))]
208
+ }, group.contactId ?? "__unassigned__"))
120
209
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
121
210
  className: "mt-2 flex items-center justify-between",
122
211
  children: [remainingCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -263,12 +352,6 @@ Object.defineProperty(exports, "ToDoWidget", {
263
352
  return ToDoWidget;
264
353
  }
265
354
  });
266
- Object.defineProperty(exports, "ToDoWidget_exports", {
267
- enumerable: true,
268
- get: function() {
269
- return ToDoWidget_exports;
270
- }
271
- });
272
355
  Object.defineProperty(exports, "toDoWidgetPropertySchema", {
273
356
  enumerable: true,
274
357
  get: function() {
@@ -276,4 +359,4 @@ Object.defineProperty(exports, "toDoWidgetPropertySchema", {
276
359
  }
277
360
  });
278
361
 
279
- //# sourceMappingURL=ToDoWidget-CaDOZtAB.cjs.map
362
+ //# sourceMappingURL=ToDoWidget-CQ_zTbhz.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToDoWidget-CQ_zTbhz.cjs","names":["useWidgetsApi","useWidgetPreviewContext","useDataSourceRegistryConfig","useWidgetsApi","useWidgetPreviewContext","useDataSourceRegistryConfig","useWidgetPreviewContext","borderWidthClasses","borderColorClasses","ListChecks","ErrorState","parseTaskBody","Plus","getFontSizeField","getColorField","getPaddingField","getBorderRadiusField","getBorderWidthField","getBorderColorField"],"sources":["../../widgets/src/hooks/use-todos.preview.ts","../../widgets/src/hooks/use-todos.ts","../../widgets/src/hooks/use-update-todo.ts","../../widgets/src/widgets/ToDoWidget.tsx"],"sourcesContent":["import type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\nconst now = new Date();\n\nfunction daysFromNow(days: number): string {\n const d = new Date(now);\n d.setDate(d.getDate() + days);\n return d.toISOString();\n}\n\nexport const PREVIEW_DATA: Todo[] = [\n {\n id: 1,\n body: \"Send follow-up email to new leads\",\n dueAt: daysFromNow(1),\n completedAt: null,\n createdAt: daysFromNow(-2),\n contactId: 101,\n contactName: \"Sarah Johnson\",\n },\n {\n id: 2,\n body: \"Prepare slides for team training\",\n dueAt: daysFromNow(3),\n completedAt: null,\n createdAt: daysFromNow(-1),\n contactId: null,\n contactName: null,\n },\n {\n id: 3,\n body: \"Review monthly sales report\",\n dueAt: daysFromNow(-1),\n completedAt: null,\n createdAt: daysFromNow(-5),\n contactId: 102,\n contactName: \"Mike Chen\",\n },\n];\n","import { useQuery, type UseQueryResult } from \"@tanstack/react-query\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useDataSourceRegistryConfig } from \"@fluid-app/portal-react/data-sources/registry-context\";\nimport { PREVIEW_DATA } from \"./use-todos.preview\";\nimport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\nexport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\n/**\n * Shared cache key for the todos list. Both useTodos (read) and\n * useUpdateTodo (write) use this — drift would silently break optimistic\n * updates.\n */\nexport function todosQueryKey(args: {\n baseUrl: string | undefined;\n isPreview: boolean;\n}) {\n return [\n \"portal-widget-use\",\n \"todos\",\n args.isPreview ? \"preview\" : args.baseUrl,\n ] as const;\n}\n\nexport function useTodos(): UseQueryResult<Todo[], Error> {\n const widgetsApi = useWidgetsApi();\n const { isPreview } = useWidgetPreviewContext();\n const { baseUrl } = useDataSourceRegistryConfig();\n\n return useQuery({\n queryKey: todosQueryKey({ baseUrl, isPreview }),\n queryFn: ({ signal }) => widgetsApi.fetchTodos(signal),\n enabled: !isPreview,\n ...(isPreview && { placeholderData: PREVIEW_DATA }),\n });\n}\n","import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { fluidToast } from \"@fluid-app/ui-primitives\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useDataSourceRegistryConfig } from \"@fluid-app/portal-react/data-sources/registry-context\";\nimport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\nimport { todosQueryKey } from \"./use-todos\";\n\ntype UpdateVariables = { id: number; completed: boolean };\n\nexport function useUpdateTodo() {\n const widgetsApi = useWidgetsApi();\n const queryClient = useQueryClient();\n const { isPreview } = useWidgetPreviewContext();\n const { baseUrl } = useDataSourceRegistryConfig();\n\n const queryKey = todosQueryKey({ baseUrl, isPreview });\n\n return useMutation({\n mutationFn: ({ id, completed }: UpdateVariables) => {\n // Defense in depth: ToDoWidget already disables the checkbox in\n // preview mode, but if any other caller fires this mutation while\n // previewing we'd PATCH demo data on the real backend. Fail closed.\n if (isPreview) {\n return Promise.reject(\n new Error(\"Todo updates are disabled in preview mode\"),\n );\n }\n return widgetsApi.updateTodo(id, completed);\n },\n onMutate: async ({ id, completed }) => {\n await queryClient.cancelQueries({ queryKey });\n const previous = queryClient.getQueryData<Todo[]>(queryKey);\n queryClient.setQueryData<Todo[]>(queryKey, (current) =>\n (current ?? []).map((todo) =>\n todo.id === id\n ? {\n ...todo,\n completedAt: completed ? new Date().toISOString() : null,\n }\n : todo,\n ),\n );\n return { previous };\n },\n onSuccess: (updated, { completed }) => {\n queryClient.setQueryData<Todo[]>(queryKey, (current) =>\n (current ?? []).map((todo) =>\n todo.id === updated.id ? updated : todo,\n ),\n );\n // Match the documented test plan: completing toasts, un-completing\n // is silent. The widget today only ever fires completed=true, but\n // gating here keeps the contract honest for any future caller that\n // toggles either direction.\n if (completed) {\n fluidToast({ title: \"Todo completed\", type: \"success\" });\n }\n },\n onError: (_error, _variables, context) => {\n if (context?.previous) {\n queryClient.setQueryData(queryKey, context.previous);\n }\n fluidToast({ title: \"Failed to update todo\", type: \"error\" });\n },\n // Belt-and-suspenders refetch to reconcile any race we couldn't see\n // (e.g., a different tab mutated the same todo). Cheap — one GET per\n // toggle.\n onSettled: () => {\n queryClient.invalidateQueries({ queryKey });\n },\n });\n}\n","import { useState, type ComponentProps } from \"react\";\nimport type React from \"react\";\nimport type {\n BackgroundValue,\n BorderRadiusOptions,\n BorderWidthOptions,\n ColorOptions,\n FontSizeOptions,\n PaddingOptions,\n} from \"@fluid-app/portal-core/types\";\nimport type { WidgetPropertySchema } from \"@fluid-app/portal-core/registries\";\nimport {\n getBorderRadiusField,\n getBorderWidthField,\n getBorderColorField,\n borderWidthClasses,\n borderColorClasses,\n getColorField,\n getFontSizeField,\n getPaddingField,\n} from \"../core/fields\";\nimport { ListChecks, Plus } from \"lucide-react\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { parseTaskBody } from \"@fluid-app/contacts-core/parse-task-body\";\nimport { useTodos } from \"../hooks/use-todos\";\nimport { useUpdateTodo } from \"../hooks/use-update-todo\";\nimport { ErrorState } from \"../components/error-state\";\n\ntype ToDoWidgetProps = ComponentProps<\"div\"> & {\n // Title\n titleEnabled?: boolean;\n titleText?: string;\n titleFontSize?: FontSizeOptions;\n titleColor?: ColorOptions;\n\n // Styling\n background?: BackgroundValue;\n textColor?: ColorOptions;\n accentColor?: ColorOptions;\n padding?: PaddingOptions;\n borderRadius?: BorderRadiusOptions;\n borderWidth?: BorderWidthOptions;\n borderColor?: ColorOptions;\n\n // Content\n maxItems?: number;\n};\n\nexport function ToDoWidget({\n // Title defaults\n titleEnabled = true,\n titleText = \"To-Do\",\n titleFontSize = \"lg\",\n titleColor = \"foreground\",\n\n // Styling defaults\n background = {\n type: \"solid\",\n color: \"background\",\n },\n textColor = \"foreground\",\n accentColor = \"primary\",\n padding = 4,\n borderRadius = \"md\",\n borderWidth = \"none\",\n borderColor = \"muted\",\n\n // Content defaults\n maxItems = 5,\n\n className,\n ...props\n}: ToDoWidgetProps): React.JSX.Element {\n const backgroundColor = background.color || \"background\";\n const backgroundImage =\n (background.resource?.image_url || background.resource?.imageUrl) &&\n background.type === \"image\"\n ? `url(${background.resource.image_url || background.resource.imageUrl})`\n : \"none\";\n const { data: todos = [], isLoading, isError } = useTodos();\n const updateTodo = useUpdateTodo();\n const { isPreview } = useWidgetPreviewContext();\n const [pendingIds, setPendingIds] = useState<Set<number>>(new Set());\n\n const toggleTodo = (id: number, completed: boolean) => {\n if (isPreview || pendingIds.has(id)) return;\n setPendingIds((prev) => new Set(prev).add(id));\n updateTodo.mutate(\n { id, completed },\n {\n onSettled: () =>\n setPendingIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n }),\n },\n );\n };\n\n const activeTodos = todos.filter((todo) => !todo.completedAt);\n const todosToShow = activeTodos.slice(0, maxItems);\n const remainingCount = activeTodos.length - todosToShow.length;\n const isEmpty = activeTodos.length === 0;\n\n // Group todos by contactId so same-named contacts stay separate.\n const groupedTodos = todosToShow.reduce<\n {\n contactId: number | null;\n contactName: string | null;\n todos: typeof todosToShow;\n }[]\n >((groups, todo) => {\n const existing = groups.find((g) => g.contactId === todo.contactId);\n if (existing) {\n existing.todos.push(todo);\n } else {\n groups.push({\n contactId: todo.contactId,\n contactName: todo.contactName,\n todos: [todo],\n });\n }\n return groups;\n }, []);\n\n return (\n <div\n className={`overflow-hidden rounded-${borderRadius} ${borderWidthClasses[borderWidth]} ${borderWidth !== \"none\" ? borderColorClasses[borderColor] : \"\"} bg-${backgroundColor} text-${textColor} p-${padding} ${className ?? \"\"}`}\n style={{ backgroundImage }}\n {...props}\n >\n {/* Header */}\n <div className=\"mb-3 flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n {titleEnabled && titleText && (\n <h2\n className={`text-${titleFontSize} font-header font-bold text-${titleColor}`}\n >\n {titleText}\n </h2>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n {!isEmpty && !isLoading && (\n <span className={`text-2xl font-bold text-${accentColor}`}>\n {activeTodos.length}\n </span>\n )}\n <div\n className={`flex h-10 w-10 shrink-0 items-center justify-center`}\n >\n <ListChecks className={`h-5 w-5 text-${accentColor}-foreground`} />\n </div>\n </div>\n </div>\n\n {/* Loading state */}\n {isLoading ? (\n <div className=\"flex min-h-[120px] items-center justify-center\">\n <div className=\"h-6 w-6 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n </div>\n ) : isError ? (\n /* Error state */\n <ErrorState />\n ) : isEmpty ? (\n /* Empty state */\n <div className=\"flex flex-col items-center justify-center py-8\">\n <p className={`text-center text-${textColor}/60`}>\n You&apos;ve got nothing else To-Do!\n </p>\n </div>\n ) : (\n /* Todo List */\n <>\n <div className=\"flex flex-col gap-3\">\n {groupedTodos.map((group) => (\n <div\n key={group.contactId ?? \"__unassigned__\"}\n className=\"flex flex-col\"\n >\n {group.contactName && (\n <div\n className={`mb-1 text-xs font-semibold tracking-wide uppercase text-${textColor}/60`}\n >\n {group.contactName}\n </div>\n )}\n {group.todos.map((todo, index) => (\n <div\n key={todo.id}\n className={`flex items-center gap-3 py-2 ${\n index !== group.todos.length - 1\n ? `border-b border-${textColor}/10`\n : \"\"\n }`}\n >\n <input\n type=\"checkbox\"\n className={`h-5 w-5 rounded-full border-2 border-${textColor}/30 bg-transparent not-disabled:cursor-pointer disabled:cursor-not-allowed disabled:opacity-60`}\n checked={!!todo.completedAt}\n disabled={isPreview || pendingIds.has(todo.id)}\n onChange={(event) =>\n toggleTodo(todo.id, event.target.checked)\n }\n />\n <span className=\"line-clamp-1 flex-1 text-sm\">\n {parseTaskBody(todo.body).title}\n </span>\n </div>\n ))}\n </div>\n ))}\n </div>\n\n {/* Footer */}\n <div className=\"mt-2 flex items-center justify-between\">\n {remainingCount > 0 && (\n <span className={`text-sm text-${textColor}/50 underline`}>\n {remainingCount} more task{remainingCount > 1 ? \"s\" : \"\"}\n </span>\n )}\n <div className=\"ml-auto\">\n <Plus className={`h-5 w-5 text-${textColor}/50`} />\n </div>\n </div>\n </>\n )}\n </div>\n );\n}\n\nexport const toDoWidgetPropertySchema: WidgetPropertySchema = {\n widgetType: \"ToDoWidget\",\n displayName: \"To-Do Widget\",\n tabsConfig: [{ id: \"styling\", label: \"Styling\" }],\n fields: [\n // Styling Tab - Title Group\n {\n key: \"titleEnabled\",\n label: \"Widget Title\",\n type: \"boolean\",\n description: \"Enable the title displayed above the todo list\",\n defaultValue: true,\n tab: \"styling\",\n group: \"Title\",\n },\n {\n key: \"titleText\",\n label: \"Title\",\n type: \"text\",\n description: \"Title text displayed above the todo list\",\n defaultValue: \"To-Do\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n },\n getFontSizeField({\n key: \"titleFontSize\",\n label: \"Title Font Size\",\n description: \"Font size for the widget title\",\n defaultValue: \"xl\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n getColorField({\n key: \"titleColor\",\n label: \"Title Color\",\n description: \"Color for the widget title\",\n defaultValue: \"foreground\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n\n // Styling Tab - Design Group\n {\n type: \"background\",\n key: \"background\",\n label: \"Background\",\n description: \"Background for the widget container\",\n defaultValue: \"background\",\n tab: \"styling\",\n group: \"Design\",\n },\n getColorField({\n key: \"textColor\",\n label: \"Text Color\",\n description: \"Default text color for todo items\",\n defaultValue: \"foreground\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getColorField({\n key: \"accentColor\",\n label: \"Accent Color\",\n description: \"Color used for count badge and icon\",\n defaultValue: \"primary\",\n tab: \"styling\",\n group: \"Design\",\n }),\n {\n key: \"separator\",\n type: \"separator\",\n label: \"Separator\",\n tab: \"styling\",\n group: \"Design\",\n },\n {\n key: \"maxItems\",\n label: \"Max Items\",\n type: \"number\",\n description: \"Maximum number of todo items to display\",\n min: 1,\n max: 20,\n step: 1,\n defaultValue: 5,\n tab: \"styling\",\n group: \"Design\",\n },\n getPaddingField({\n key: \"padding\",\n label: \"Padding\",\n description: \"Padding around the widget container\",\n defaultValue: 4,\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderRadiusField({\n key: \"borderRadius\",\n label: \"Border Radius\",\n description: \"Border radius for the widget container\",\n defaultValue: \"md\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderWidthField({\n key: \"borderWidth\",\n label: \"Border Width\",\n description: \"Border width for the widget\",\n defaultValue: \"none\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderColorField({\n key: \"borderColor\",\n label: \"Border Color\",\n description: \"Border color for the widget\",\n defaultValue: \"muted\",\n tab: \"styling\",\n group: \"Design\",\n }),\n ],\n} as const satisfies WidgetPropertySchema;\n"],"mappings":"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"}
@@ -2,8 +2,11 @@ import { r as __exportAll } from "./es-BkP8gyWU.mjs";
2
2
  import { n as useDataSourceRegistryConfig } from "./registry-context-BDH0vNHR.mjs";
3
3
  import { i as useWidgetsApi, n as useWidgetPreviewContext, t as ErrorState } from "./error-state-DYzHx8tt.mjs";
4
4
  import { i as getBorderColorField, l as getColorField, m as getPaddingField, n as borderWidthClasses, o as getBorderRadiusField, s as getBorderWidthField, t as borderColorClasses, u as getFontSizeField } from "./registries-Ct8o2YRe.mjs";
5
- import { useQuery } from "@tanstack/react-query";
6
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { b as fluidToast } from "./src-CCqVyAdS.mjs";
6
+ import { t as parseTaskBody } from "./parse-task-body-DEmYvdNM.mjs";
7
+ import { useState } from "react";
8
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
9
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
10
  import { ListChecks, Plus } from "lucide-react";
8
11
  //#region ../widgets/src/hooks/use-todos.preview.ts
9
12
  const now = /* @__PURE__ */ new Date();
@@ -19,6 +22,7 @@ const PREVIEW_DATA = [
19
22
  dueAt: daysFromNow(1),
20
23
  completedAt: null,
21
24
  createdAt: daysFromNow(-2),
25
+ contactId: 101,
22
26
  contactName: "Sarah Johnson"
23
27
  },
24
28
  {
@@ -27,6 +31,7 @@ const PREVIEW_DATA = [
27
31
  dueAt: daysFromNow(3),
28
32
  completedAt: null,
29
33
  createdAt: daysFromNow(-1),
34
+ contactId: null,
30
35
  contactName: null
31
36
  },
32
37
  {
@@ -35,27 +40,83 @@ const PREVIEW_DATA = [
35
40
  dueAt: daysFromNow(-1),
36
41
  completedAt: null,
37
42
  createdAt: daysFromNow(-5),
43
+ contactId: 102,
38
44
  contactName: "Mike Chen"
39
45
  }
40
46
  ];
41
47
  //#endregion
42
48
  //#region ../widgets/src/hooks/use-todos.ts
49
+ /**
50
+ * Shared cache key for the todos list. Both useTodos (read) and
51
+ * useUpdateTodo (write) use this — drift would silently break optimistic
52
+ * updates.
53
+ */
54
+ function todosQueryKey(args) {
55
+ return [
56
+ "portal-widget-use",
57
+ "todos",
58
+ args.isPreview ? "preview" : args.baseUrl
59
+ ];
60
+ }
43
61
  function useTodos() {
44
62
  const widgetsApi = useWidgetsApi();
45
63
  const { isPreview } = useWidgetPreviewContext();
46
64
  const { baseUrl } = useDataSourceRegistryConfig();
47
65
  return useQuery({
48
- queryKey: [
49
- "portal-widget-use",
50
- "todos",
51
- isPreview ? "preview" : baseUrl
52
- ],
66
+ queryKey: todosQueryKey({
67
+ baseUrl,
68
+ isPreview
69
+ }),
53
70
  queryFn: ({ signal }) => widgetsApi.fetchTodos(signal),
54
71
  enabled: !isPreview,
55
72
  ...isPreview && { placeholderData: PREVIEW_DATA }
56
73
  });
57
74
  }
58
75
  //#endregion
76
+ //#region ../widgets/src/hooks/use-update-todo.ts
77
+ function useUpdateTodo() {
78
+ const widgetsApi = useWidgetsApi();
79
+ const queryClient = useQueryClient();
80
+ const { isPreview } = useWidgetPreviewContext();
81
+ const { baseUrl } = useDataSourceRegistryConfig();
82
+ const queryKey = todosQueryKey({
83
+ baseUrl,
84
+ isPreview
85
+ });
86
+ return useMutation({
87
+ mutationFn: ({ id, completed }) => {
88
+ if (isPreview) return Promise.reject(/* @__PURE__ */ new Error("Todo updates are disabled in preview mode"));
89
+ return widgetsApi.updateTodo(id, completed);
90
+ },
91
+ onMutate: async ({ id, completed }) => {
92
+ await queryClient.cancelQueries({ queryKey });
93
+ const previous = queryClient.getQueryData(queryKey);
94
+ queryClient.setQueryData(queryKey, (current) => (current ?? []).map((todo) => todo.id === id ? {
95
+ ...todo,
96
+ completedAt: completed ? (/* @__PURE__ */ new Date()).toISOString() : null
97
+ } : todo));
98
+ return { previous };
99
+ },
100
+ onSuccess: (updated, { completed }) => {
101
+ queryClient.setQueryData(queryKey, (current) => (current ?? []).map((todo) => todo.id === updated.id ? updated : todo));
102
+ if (completed) fluidToast({
103
+ title: "Todo completed",
104
+ type: "success"
105
+ });
106
+ },
107
+ onError: (_error, _variables, context) => {
108
+ if (context?.previous) queryClient.setQueryData(queryKey, context.previous);
109
+ fluidToast({
110
+ title: "Failed to update todo",
111
+ type: "error"
112
+ });
113
+ },
114
+ onSettled: () => {
115
+ queryClient.invalidateQueries({ queryKey });
116
+ }
117
+ });
118
+ }
119
+ //#endregion
59
120
  //#region ../widgets/src/widgets/ToDoWidget.tsx
60
121
  var ToDoWidget_exports = /* @__PURE__ */ __exportAll({
61
122
  ToDoWidget: () => ToDoWidget,
@@ -68,10 +129,35 @@ function ToDoWidget({ titleEnabled = true, titleText = "To-Do", titleFontSize =
68
129
  const backgroundColor = background.color || "background";
69
130
  const backgroundImage = (background.resource?.image_url || background.resource?.imageUrl) && background.type === "image" ? `url(${background.resource.image_url || background.resource.imageUrl})` : "none";
70
131
  const { data: todos = [], isLoading, isError } = useTodos();
71
- const activeTodos = (todos.length > 0 ? todos : []).filter((todo) => !todo.completedAt);
132
+ const updateTodo = useUpdateTodo();
133
+ const { isPreview } = useWidgetPreviewContext();
134
+ const [pendingIds, setPendingIds] = useState(/* @__PURE__ */ new Set());
135
+ const toggleTodo = (id, completed) => {
136
+ if (isPreview || pendingIds.has(id)) return;
137
+ setPendingIds((prev) => new Set(prev).add(id));
138
+ updateTodo.mutate({
139
+ id,
140
+ completed
141
+ }, { onSettled: () => setPendingIds((prev) => {
142
+ const next = new Set(prev);
143
+ next.delete(id);
144
+ return next;
145
+ }) });
146
+ };
147
+ const activeTodos = todos.filter((todo) => !todo.completedAt);
72
148
  const todosToShow = activeTodos.slice(0, maxItems);
73
149
  const remainingCount = activeTodos.length - todosToShow.length;
74
150
  const isEmpty = activeTodos.length === 0;
151
+ const groupedTodos = todosToShow.reduce((groups, todo) => {
152
+ const existing = groups.find((g) => g.contactId === todo.contactId);
153
+ if (existing) existing.todos.push(todo);
154
+ else groups.push({
155
+ contactId: todo.contactId,
156
+ contactName: todo.contactName,
157
+ todos: [todo]
158
+ });
159
+ return groups;
160
+ }, []);
75
161
  return /* @__PURE__ */ jsxs("div", {
76
162
  className: `overflow-hidden rounded-${borderRadius} ${borderWidthClasses[borderWidth]} ${borderWidth !== "none" ? borderColorClasses[borderColor] : ""} bg-${backgroundColor} text-${textColor} p-${padding} ${className ?? ""}`,
77
163
  style: { backgroundImage },
@@ -103,20 +189,27 @@ function ToDoWidget({ titleEnabled = true, titleText = "To-Do", titleFontSize =
103
189
  className: `text-center text-${textColor}/60`,
104
190
  children: "You've got nothing else To-Do!"
105
191
  })
106
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
107
- className: "flex flex-col",
108
- children: todosToShow.map((todo, index) => /* @__PURE__ */ jsxs("div", {
109
- className: `flex items-center gap-3 py-2.5 ${index !== todosToShow.length - 1 ? `border-b border-${textColor}/10` : ""}`,
110
- children: [/* @__PURE__ */ jsx("input", {
111
- type: "checkbox",
112
- className: `h-5 w-5 rounded-full border-2 border-${textColor}/30 bg-transparent`,
113
- checked: !!todo.completedAt,
114
- readOnly: true
115
- }), /* @__PURE__ */ jsx("span", {
116
- className: "line-clamp-1 flex-1 text-sm",
117
- children: todo.body
118
- })]
119
- }, todo.id))
192
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
193
+ className: "flex flex-col gap-3",
194
+ children: groupedTodos.map((group) => /* @__PURE__ */ jsxs("div", {
195
+ className: "flex flex-col",
196
+ children: [group.contactName && /* @__PURE__ */ jsx("div", {
197
+ className: `mb-1 text-xs font-semibold tracking-wide uppercase text-${textColor}/60`,
198
+ children: group.contactName
199
+ }), group.todos.map((todo, index) => /* @__PURE__ */ jsxs("div", {
200
+ className: `flex items-center gap-3 py-2 ${index !== group.todos.length - 1 ? `border-b border-${textColor}/10` : ""}`,
201
+ children: [/* @__PURE__ */ jsx("input", {
202
+ type: "checkbox",
203
+ className: `h-5 w-5 rounded-full border-2 border-${textColor}/30 bg-transparent not-disabled:cursor-pointer disabled:cursor-not-allowed disabled:opacity-60`,
204
+ checked: !!todo.completedAt,
205
+ disabled: isPreview || pendingIds.has(todo.id),
206
+ onChange: (event) => toggleTodo(todo.id, event.target.checked)
207
+ }), /* @__PURE__ */ jsx("span", {
208
+ className: "line-clamp-1 flex-1 text-sm",
209
+ children: parseTaskBody(todo.body).title
210
+ })]
211
+ }, todo.id))]
212
+ }, group.contactId ?? "__unassigned__"))
120
213
  }), /* @__PURE__ */ jsxs("div", {
121
214
  className: "mt-2 flex items-center justify-between",
122
215
  children: [remainingCount > 0 && /* @__PURE__ */ jsxs("span", {
@@ -259,4 +352,4 @@ const toDoWidgetPropertySchema = {
259
352
  //#endregion
260
353
  export { ToDoWidget_exports as n, toDoWidgetPropertySchema as r, ToDoWidget as t };
261
354
 
262
- //# sourceMappingURL=ToDoWidget-Bv258x8F.mjs.map
355
+ //# sourceMappingURL=ToDoWidget-DYGt45vL.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToDoWidget-DYGt45vL.mjs","names":[],"sources":["../../widgets/src/hooks/use-todos.preview.ts","../../widgets/src/hooks/use-todos.ts","../../widgets/src/hooks/use-update-todo.ts","../../widgets/src/widgets/ToDoWidget.tsx"],"sourcesContent":["import type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\nconst now = new Date();\n\nfunction daysFromNow(days: number): string {\n const d = new Date(now);\n d.setDate(d.getDate() + days);\n return d.toISOString();\n}\n\nexport const PREVIEW_DATA: Todo[] = [\n {\n id: 1,\n body: \"Send follow-up email to new leads\",\n dueAt: daysFromNow(1),\n completedAt: null,\n createdAt: daysFromNow(-2),\n contactId: 101,\n contactName: \"Sarah Johnson\",\n },\n {\n id: 2,\n body: \"Prepare slides for team training\",\n dueAt: daysFromNow(3),\n completedAt: null,\n createdAt: daysFromNow(-1),\n contactId: null,\n contactName: null,\n },\n {\n id: 3,\n body: \"Review monthly sales report\",\n dueAt: daysFromNow(-1),\n completedAt: null,\n createdAt: daysFromNow(-5),\n contactId: 102,\n contactName: \"Mike Chen\",\n },\n];\n","import { useQuery, type UseQueryResult } from \"@tanstack/react-query\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useDataSourceRegistryConfig } from \"@fluid-app/portal-react/data-sources/registry-context\";\nimport { PREVIEW_DATA } from \"./use-todos.preview\";\nimport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\nexport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\n\n/**\n * Shared cache key for the todos list. Both useTodos (read) and\n * useUpdateTodo (write) use this — drift would silently break optimistic\n * updates.\n */\nexport function todosQueryKey(args: {\n baseUrl: string | undefined;\n isPreview: boolean;\n}) {\n return [\n \"portal-widget-use\",\n \"todos\",\n args.isPreview ? \"preview\" : args.baseUrl,\n ] as const;\n}\n\nexport function useTodos(): UseQueryResult<Todo[], Error> {\n const widgetsApi = useWidgetsApi();\n const { isPreview } = useWidgetPreviewContext();\n const { baseUrl } = useDataSourceRegistryConfig();\n\n return useQuery({\n queryKey: todosQueryKey({ baseUrl, isPreview }),\n queryFn: ({ signal }) => widgetsApi.fetchTodos(signal),\n enabled: !isPreview,\n ...(isPreview && { placeholderData: PREVIEW_DATA }),\n });\n}\n","import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { fluidToast } from \"@fluid-app/ui-primitives\";\nimport { useWidgetsApi } from \"@fluid-app/portal-core/widgets-api-context\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useDataSourceRegistryConfig } from \"@fluid-app/portal-react/data-sources/registry-context\";\nimport type { Todo } from \"@fluid-app/portal-core/widgets-api-types\";\nimport { todosQueryKey } from \"./use-todos\";\n\ntype UpdateVariables = { id: number; completed: boolean };\n\nexport function useUpdateTodo() {\n const widgetsApi = useWidgetsApi();\n const queryClient = useQueryClient();\n const { isPreview } = useWidgetPreviewContext();\n const { baseUrl } = useDataSourceRegistryConfig();\n\n const queryKey = todosQueryKey({ baseUrl, isPreview });\n\n return useMutation({\n mutationFn: ({ id, completed }: UpdateVariables) => {\n // Defense in depth: ToDoWidget already disables the checkbox in\n // preview mode, but if any other caller fires this mutation while\n // previewing we'd PATCH demo data on the real backend. Fail closed.\n if (isPreview) {\n return Promise.reject(\n new Error(\"Todo updates are disabled in preview mode\"),\n );\n }\n return widgetsApi.updateTodo(id, completed);\n },\n onMutate: async ({ id, completed }) => {\n await queryClient.cancelQueries({ queryKey });\n const previous = queryClient.getQueryData<Todo[]>(queryKey);\n queryClient.setQueryData<Todo[]>(queryKey, (current) =>\n (current ?? []).map((todo) =>\n todo.id === id\n ? {\n ...todo,\n completedAt: completed ? new Date().toISOString() : null,\n }\n : todo,\n ),\n );\n return { previous };\n },\n onSuccess: (updated, { completed }) => {\n queryClient.setQueryData<Todo[]>(queryKey, (current) =>\n (current ?? []).map((todo) =>\n todo.id === updated.id ? updated : todo,\n ),\n );\n // Match the documented test plan: completing toasts, un-completing\n // is silent. The widget today only ever fires completed=true, but\n // gating here keeps the contract honest for any future caller that\n // toggles either direction.\n if (completed) {\n fluidToast({ title: \"Todo completed\", type: \"success\" });\n }\n },\n onError: (_error, _variables, context) => {\n if (context?.previous) {\n queryClient.setQueryData(queryKey, context.previous);\n }\n fluidToast({ title: \"Failed to update todo\", type: \"error\" });\n },\n // Belt-and-suspenders refetch to reconcile any race we couldn't see\n // (e.g., a different tab mutated the same todo). Cheap — one GET per\n // toggle.\n onSettled: () => {\n queryClient.invalidateQueries({ queryKey });\n },\n });\n}\n","import { useState, type ComponentProps } from \"react\";\nimport type React from \"react\";\nimport type {\n BackgroundValue,\n BorderRadiusOptions,\n BorderWidthOptions,\n ColorOptions,\n FontSizeOptions,\n PaddingOptions,\n} from \"@fluid-app/portal-core/types\";\nimport type { WidgetPropertySchema } from \"@fluid-app/portal-core/registries\";\nimport {\n getBorderRadiusField,\n getBorderWidthField,\n getBorderColorField,\n borderWidthClasses,\n borderColorClasses,\n getColorField,\n getFontSizeField,\n getPaddingField,\n} from \"../core/fields\";\nimport { ListChecks, Plus } from \"lucide-react\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { parseTaskBody } from \"@fluid-app/contacts-core/parse-task-body\";\nimport { useTodos } from \"../hooks/use-todos\";\nimport { useUpdateTodo } from \"../hooks/use-update-todo\";\nimport { ErrorState } from \"../components/error-state\";\n\ntype ToDoWidgetProps = ComponentProps<\"div\"> & {\n // Title\n titleEnabled?: boolean;\n titleText?: string;\n titleFontSize?: FontSizeOptions;\n titleColor?: ColorOptions;\n\n // Styling\n background?: BackgroundValue;\n textColor?: ColorOptions;\n accentColor?: ColorOptions;\n padding?: PaddingOptions;\n borderRadius?: BorderRadiusOptions;\n borderWidth?: BorderWidthOptions;\n borderColor?: ColorOptions;\n\n // Content\n maxItems?: number;\n};\n\nexport function ToDoWidget({\n // Title defaults\n titleEnabled = true,\n titleText = \"To-Do\",\n titleFontSize = \"lg\",\n titleColor = \"foreground\",\n\n // Styling defaults\n background = {\n type: \"solid\",\n color: \"background\",\n },\n textColor = \"foreground\",\n accentColor = \"primary\",\n padding = 4,\n borderRadius = \"md\",\n borderWidth = \"none\",\n borderColor = \"muted\",\n\n // Content defaults\n maxItems = 5,\n\n className,\n ...props\n}: ToDoWidgetProps): React.JSX.Element {\n const backgroundColor = background.color || \"background\";\n const backgroundImage =\n (background.resource?.image_url || background.resource?.imageUrl) &&\n background.type === \"image\"\n ? `url(${background.resource.image_url || background.resource.imageUrl})`\n : \"none\";\n const { data: todos = [], isLoading, isError } = useTodos();\n const updateTodo = useUpdateTodo();\n const { isPreview } = useWidgetPreviewContext();\n const [pendingIds, setPendingIds] = useState<Set<number>>(new Set());\n\n const toggleTodo = (id: number, completed: boolean) => {\n if (isPreview || pendingIds.has(id)) return;\n setPendingIds((prev) => new Set(prev).add(id));\n updateTodo.mutate(\n { id, completed },\n {\n onSettled: () =>\n setPendingIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n }),\n },\n );\n };\n\n const activeTodos = todos.filter((todo) => !todo.completedAt);\n const todosToShow = activeTodos.slice(0, maxItems);\n const remainingCount = activeTodos.length - todosToShow.length;\n const isEmpty = activeTodos.length === 0;\n\n // Group todos by contactId so same-named contacts stay separate.\n const groupedTodos = todosToShow.reduce<\n {\n contactId: number | null;\n contactName: string | null;\n todos: typeof todosToShow;\n }[]\n >((groups, todo) => {\n const existing = groups.find((g) => g.contactId === todo.contactId);\n if (existing) {\n existing.todos.push(todo);\n } else {\n groups.push({\n contactId: todo.contactId,\n contactName: todo.contactName,\n todos: [todo],\n });\n }\n return groups;\n }, []);\n\n return (\n <div\n className={`overflow-hidden rounded-${borderRadius} ${borderWidthClasses[borderWidth]} ${borderWidth !== \"none\" ? borderColorClasses[borderColor] : \"\"} bg-${backgroundColor} text-${textColor} p-${padding} ${className ?? \"\"}`}\n style={{ backgroundImage }}\n {...props}\n >\n {/* Header */}\n <div className=\"mb-3 flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n {titleEnabled && titleText && (\n <h2\n className={`text-${titleFontSize} font-header font-bold text-${titleColor}`}\n >\n {titleText}\n </h2>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n {!isEmpty && !isLoading && (\n <span className={`text-2xl font-bold text-${accentColor}`}>\n {activeTodos.length}\n </span>\n )}\n <div\n className={`flex h-10 w-10 shrink-0 items-center justify-center`}\n >\n <ListChecks className={`h-5 w-5 text-${accentColor}-foreground`} />\n </div>\n </div>\n </div>\n\n {/* Loading state */}\n {isLoading ? (\n <div className=\"flex min-h-[120px] items-center justify-center\">\n <div className=\"h-6 w-6 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n </div>\n ) : isError ? (\n /* Error state */\n <ErrorState />\n ) : isEmpty ? (\n /* Empty state */\n <div className=\"flex flex-col items-center justify-center py-8\">\n <p className={`text-center text-${textColor}/60`}>\n You&apos;ve got nothing else To-Do!\n </p>\n </div>\n ) : (\n /* Todo List */\n <>\n <div className=\"flex flex-col gap-3\">\n {groupedTodos.map((group) => (\n <div\n key={group.contactId ?? \"__unassigned__\"}\n className=\"flex flex-col\"\n >\n {group.contactName && (\n <div\n className={`mb-1 text-xs font-semibold tracking-wide uppercase text-${textColor}/60`}\n >\n {group.contactName}\n </div>\n )}\n {group.todos.map((todo, index) => (\n <div\n key={todo.id}\n className={`flex items-center gap-3 py-2 ${\n index !== group.todos.length - 1\n ? `border-b border-${textColor}/10`\n : \"\"\n }`}\n >\n <input\n type=\"checkbox\"\n className={`h-5 w-5 rounded-full border-2 border-${textColor}/30 bg-transparent not-disabled:cursor-pointer disabled:cursor-not-allowed disabled:opacity-60`}\n checked={!!todo.completedAt}\n disabled={isPreview || pendingIds.has(todo.id)}\n onChange={(event) =>\n toggleTodo(todo.id, event.target.checked)\n }\n />\n <span className=\"line-clamp-1 flex-1 text-sm\">\n {parseTaskBody(todo.body).title}\n </span>\n </div>\n ))}\n </div>\n ))}\n </div>\n\n {/* Footer */}\n <div className=\"mt-2 flex items-center justify-between\">\n {remainingCount > 0 && (\n <span className={`text-sm text-${textColor}/50 underline`}>\n {remainingCount} more task{remainingCount > 1 ? \"s\" : \"\"}\n </span>\n )}\n <div className=\"ml-auto\">\n <Plus className={`h-5 w-5 text-${textColor}/50`} />\n </div>\n </div>\n </>\n )}\n </div>\n );\n}\n\nexport const toDoWidgetPropertySchema: WidgetPropertySchema = {\n widgetType: \"ToDoWidget\",\n displayName: \"To-Do Widget\",\n tabsConfig: [{ id: \"styling\", label: \"Styling\" }],\n fields: [\n // Styling Tab - Title Group\n {\n key: \"titleEnabled\",\n label: \"Widget Title\",\n type: \"boolean\",\n description: \"Enable the title displayed above the todo list\",\n defaultValue: true,\n tab: \"styling\",\n group: \"Title\",\n },\n {\n key: \"titleText\",\n label: \"Title\",\n type: \"text\",\n description: \"Title text displayed above the todo list\",\n defaultValue: \"To-Do\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n },\n getFontSizeField({\n key: \"titleFontSize\",\n label: \"Title Font Size\",\n description: \"Font size for the widget title\",\n defaultValue: \"xl\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n getColorField({\n key: \"titleColor\",\n label: \"Title Color\",\n description: \"Color for the widget title\",\n defaultValue: \"foreground\",\n tab: \"styling\",\n group: \"Title\",\n requiresKeyToBeTrue: \"titleEnabled\",\n }),\n\n // Styling Tab - Design Group\n {\n type: \"background\",\n key: \"background\",\n label: \"Background\",\n description: \"Background for the widget container\",\n defaultValue: \"background\",\n tab: \"styling\",\n group: \"Design\",\n },\n getColorField({\n key: \"textColor\",\n label: \"Text Color\",\n description: \"Default text color for todo items\",\n defaultValue: \"foreground\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getColorField({\n key: \"accentColor\",\n label: \"Accent Color\",\n description: \"Color used for count badge and icon\",\n defaultValue: \"primary\",\n tab: \"styling\",\n group: \"Design\",\n }),\n {\n key: \"separator\",\n type: \"separator\",\n label: \"Separator\",\n tab: \"styling\",\n group: \"Design\",\n },\n {\n key: \"maxItems\",\n label: \"Max Items\",\n type: \"number\",\n description: \"Maximum number of todo items to display\",\n min: 1,\n max: 20,\n step: 1,\n defaultValue: 5,\n tab: \"styling\",\n group: \"Design\",\n },\n getPaddingField({\n key: \"padding\",\n label: \"Padding\",\n description: \"Padding around the widget container\",\n defaultValue: 4,\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderRadiusField({\n key: \"borderRadius\",\n label: \"Border Radius\",\n description: \"Border radius for the widget container\",\n defaultValue: \"md\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderWidthField({\n key: \"borderWidth\",\n label: \"Border Width\",\n description: \"Border width for the widget\",\n defaultValue: \"none\",\n tab: \"styling\",\n group: \"Design\",\n }),\n getBorderColorField({\n key: \"borderColor\",\n label: \"Border Color\",\n description: \"Border color for the widget\",\n defaultValue: \"muted\",\n tab: \"styling\",\n group: \"Design\",\n }),\n ],\n} as const satisfies WidgetPropertySchema;\n"],"mappings":"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KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR;EACD,cAAc;GACZ,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACF,cAAc;GACZ,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACF;GACE,KAAK;GACL,MAAM;GACN,OAAO;GACP,KAAK;GACL,OAAO;GACR;EACD;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,KAAK;GACL,KAAK;GACL,MAAM;GACN,cAAc;GACd,KAAK;GACL,OAAO;GACR;EACD,gBAAgB;GACd,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACF,qBAAqB;GACnB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACF,oBAAoB;GAClB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACF,oBAAoB;GAClB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,KAAK;GACL,OAAO;GACR,CAAC;EACH;CACF"}
@@ -1,5 +1,5 @@
1
1
  require("./chunk-9hOWP6kD.cjs");
2
2
  require("./src-DpFIi-nj.cjs");
3
- const require_UpgradeScreen = require("./UpgradeScreen-BaclFXEh.cjs");
3
+ const require_UpgradeScreen = require("./UpgradeScreen-QhhBuHXE.cjs");
4
4
  exports.UpgradeScreen = require_UpgradeScreen.UpgradeScreen;
5
5
  exports.upgradeScreenPropertySchema = require_UpgradeScreen.upgradeScreenPropertySchema;
@@ -126,4 +126,4 @@ function UpgradeScreen() {
126
126
  //#endregion
127
127
  export { UpgradeScreen_exports as t };
128
128
 
129
- //# sourceMappingURL=UpgradeScreen-CGiVn0KG.mjs.map
129
+ //# sourceMappingURL=UpgradeScreen-DMxxZjj_.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"UpgradeScreen-CGiVn0KG.mjs","names":[],"sources":["../../pro-upgrade-ui/src/lib/cn.ts","../../pro-upgrade-ui/src/components/PortalProUpgradeScreen.tsx","../src/screens/UpgradeScreen.tsx"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import {\n Sparkles,\n Phone,\n MessagesSquare,\n CalendarMinus,\n MessageSquareHeart,\n type LucideIcon,\n} from \"lucide-react\";\nimport {\n Button,\n Card,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@fluid-app/ui-primitives\";\nimport { cn } from \"../lib/cn\";\n\nexport interface PortalProUpgradeScreenProps {\n onJoinWaitlist: () => void;\n waitlistLoading?: boolean;\n className?: string;\n}\n\ninterface Feature {\n icon: LucideIcon;\n title: string;\n description: string;\n}\n\nconst FEATURES: Feature[] = [\n {\n icon: Sparkles,\n title: \"Generative AI\",\n description: \"Get quick drafts and creative messaging help.\",\n },\n {\n icon: Phone,\n title: \"Business phone number\",\n description: \"Look professional with a dedicated client line.\",\n },\n {\n icon: MessagesSquare,\n title: \"Mass Messaging\",\n description: \"Save time by messaging multiple clients at once.\",\n },\n {\n icon: CalendarMinus,\n title: \"Scheduled messages\",\n description: \"Plan ahead and schedule messages easily.\",\n },\n {\n icon: MessageSquareHeart,\n title: \"Unified messaging\",\n description: \"Combine email, SMS, and internal messages.\",\n },\n];\n\nfunction FeatureCard({ icon: Icon, title, description }: Feature) {\n return (\n <Card className=\"bg-background text-foreground h-auto gap-0 border-none py-4\">\n <CardHeader className=\"gap-3 px-4\">\n <Icon className=\"size-6\" strokeWidth={1.5} />\n <CardTitle className=\"text-sm font-bold\">{title}</CardTitle>\n <CardDescription className=\"text-xs\">{description}</CardDescription>\n </CardHeader>\n </Card>\n );\n}\n\nexport function PortalProUpgradeScreen({\n onJoinWaitlist,\n waitlistLoading = false,\n className,\n}: PortalProUpgradeScreenProps) {\n return (\n <div\n className={cn(\n \"relative flex h-full w-full flex-col overflow-hidden md:flex-row\",\n className,\n )}\n >\n {/* Pastel gradient background */}\n <div className=\"pointer-events-none absolute inset-0 rounded-xl bg-linear-[33deg,rgba(8,148,255,0.2)_4.5%,rgba(201,89,221,0.2)_24.5%,rgba(255,46,84,0.2)_54.6%,rgba(255,144,4,0.2)_69.2%,rgba(255,255,255,0.2)_76.4%] blur-[125px]\" />\n\n {/* Hero collage - absolute, behind everything, fades right and bottom */}\n <img\n src=\"https://ik.imagekit.io/fluid/980191006/images/HW87RR/hero-collage_O6DCdkFib.png\"\n alt=\"People using the app with push notifications\"\n className=\"pointer-events-none absolute top-0 left-0 h-full w-full mask-b-from-20% mask-b-to-30% object-contain object-left-top md:w-4/5 md:mask-r-from-50% md:mask-r-to-70% md:mask-b-from-60% md:mask-b-to-80%\"\n />\n\n {/* Left Section - Logo & CTA */}\n <div className=\"relative flex w-full shrink-0 flex-col items-center justify-end px-8 pt-40 md:w-1/2 md:pt-0 md:pb-10\">\n <div className=\"flex flex-col items-center gap-4\">\n <img\n src=\"https://ik.imagekit.io/fluid/980191006/images/ZYT78J/co-founder-badge_NcorZNlUY.png\"\n alt=\"Coming Soon - Co-Founder\"\n className=\"h-auto w-120 object-contain\"\n />\n {/* TODO: This is currently hidden until we actually have a way to waitlist someone */}\n <Button\n className=\"hidden w-full md:w-auto\"\n size=\"xl\"\n onClick={onJoinWaitlist}\n disabled={waitlistLoading}\n >\n {waitlistLoading ? \"Joining...\" : \"Join Waitlist\"}\n </Button>\n </div>\n </div>\n\n {/* Right Section - Content */}\n <div className=\"relative flex min-w-0 flex-1 flex-col gap-6 overflow-y-auto px-8 pt-8 md:pt-16 md:pr-5 md:pb-5 md:pl-10\">\n <div className=\"flex shrink-0 flex-col gap-2.5\">\n <h1 className=\"text-3xl leading-tight font-semibold tracking-tight text-black md:text-5xl\">\n It's like having an\n <br />\n assistant in your pocket\n </h1>\n <p className=\"text-sm leading-5 font-medium text-black\">\n Upgrade your business\n </p>\n </div>\n <div className=\"flex flex-col gap-4 pb-4\">\n {FEATURES.map((feature) => (\n <FeatureCard key={feature.title} {...feature} />\n ))}\n </div>\n </div>\n </div>\n );\n}\n","import type { WidgetPropertySchema } from \"../registries/property-schema-types\";\nimport { PortalProUpgradeScreen } from \"@fluid-app/portal-pro-upgrade-ui\";\n\nexport const upgradeScreenPropertySchema = {\n widgetType: \"UpgradeScreen\",\n displayName: \"Upgrade Screen\",\n fields: [],\n} as const satisfies WidgetPropertySchema;\n\nexport function UpgradeScreen(): React.JSX.Element {\n return (\n <PortalProUpgradeScreen\n onJoinWaitlist={() => {\n // TODO: Wire up to waitlist API\n }}\n />\n );\n}\n"],"mappings":";;;;;;;AAGA,SAAgB,GAAG,GAAG,QAAsB;AAC1C,QAAO,QAAQ,KAAK,OAAO,CAAC;;;;ACyB9B,MAAM,WAAsB;CAC1B;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACd;CACD;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACd;CACD;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACd;CACD;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACd;CACD;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACd;CACF;AAED,SAAS,YAAY,EAAE,MAAM,MAAM,OAAO,eAAwB;AAChE,QACE,oBAAC,MAAD;EAAM,WAAU;YACd,qBAAC,YAAD;GAAY,WAAU;aAAtB;IACE,oBAAC,MAAD;KAAM,WAAU;KAAS,aAAa;KAAO,CAAA;IAC7C,oBAAC,WAAD;KAAW,WAAU;eAAqB;KAAkB,CAAA;IAC5D,oBAAC,iBAAD;KAAiB,WAAU;eAAW;KAA8B,CAAA;IACzD;;EACR,CAAA;;AAIX,SAAgB,uBAAuB,EACrC,gBACA,kBAAkB,OAClB,aAC8B;AAC9B,QACE,qBAAC,OAAD;EACE,WAAW,GACT,oEACA,UACD;YAJH;GAOE,oBAAC,OAAD,EAAK,WAAU,sNAAuN,CAAA;GAGtO,oBAAC,OAAD;IACE,KAAI;IACJ,KAAI;IACJ,WAAU;IACV,CAAA;GAGF,oBAAC,OAAD;IAAK,WAAU;cACb,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,OAAD;MACE,KAAI;MACJ,KAAI;MACJ,WAAU;MACV,CAAA,EAEF,oBAAC,QAAD;MACE,WAAU;MACV,MAAK;MACL,SAAS;MACT,UAAU;gBAET,kBAAkB,eAAe;MAC3B,CAAA,CACL;;IACF,CAAA;GAGN,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,qBAAC,MAAD;MAAI,WAAU;gBAAd;OAA2F;OAEzF,oBAAC,MAAD,EAAM,CAAA;;OAEH;SACL,oBAAC,KAAD;MAAG,WAAU;gBAA2C;MAEpD,CAAA,CACA;QACN,oBAAC,OAAD;KAAK,WAAU;eACZ,SAAS,KAAK,YACb,oBAAC,aAAD,EAAiC,GAAI,SAAW,EAA9B,QAAQ,MAAsB,CAChD;KACE,CAAA,CACF;;GACF;;;;;;;;;AC9HV,MAAa,8BAA8B;CACzC,YAAY;CACZ,aAAa;CACb,QAAQ,EAAE;CACX;AAED,SAAgB,gBAAmC;AACjD,QACE,oBAAC,wBAAD,EACE,sBAAsB,IAGtB,CAAA"}
1
+ {"version":3,"file":"UpgradeScreen-DMxxZjj_.mjs","names":[],"sources":["../../pro-upgrade-ui/src/lib/cn.ts","../../pro-upgrade-ui/src/components/PortalProUpgradeScreen.tsx","../src/screens/UpgradeScreen.tsx"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import {\n Sparkles,\n Phone,\n MessagesSquare,\n CalendarMinus,\n MessageSquareHeart,\n type LucideIcon,\n} from \"lucide-react\";\nimport {\n Button,\n Card,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@fluid-app/ui-primitives\";\nimport { cn } from \"../lib/cn\";\n\nexport interface PortalProUpgradeScreenProps {\n onJoinWaitlist: () => void;\n waitlistLoading?: boolean;\n className?: string;\n}\n\ninterface Feature {\n icon: LucideIcon;\n title: string;\n description: string;\n}\n\nconst FEATURES: Feature[] = [\n {\n icon: Sparkles,\n title: \"Generative AI\",\n description: \"Get quick drafts and creative messaging help.\",\n },\n {\n icon: Phone,\n title: \"Business phone number\",\n description: \"Look professional with a dedicated client line.\",\n },\n {\n icon: MessagesSquare,\n title: \"Mass Messaging\",\n description: \"Save time by messaging multiple clients at once.\",\n },\n {\n icon: CalendarMinus,\n title: \"Scheduled messages\",\n description: \"Plan ahead and schedule messages easily.\",\n },\n {\n icon: MessageSquareHeart,\n title: \"Unified messaging\",\n description: \"Combine email, SMS, and internal messages.\",\n },\n];\n\nfunction FeatureCard({ icon: Icon, title, description }: Feature) {\n return (\n <Card className=\"bg-background text-foreground h-auto gap-0 border-none py-4\">\n <CardHeader className=\"gap-3 px-4\">\n <Icon className=\"size-6\" strokeWidth={1.5} />\n <CardTitle className=\"text-sm font-bold\">{title}</CardTitle>\n <CardDescription className=\"text-xs\">{description}</CardDescription>\n </CardHeader>\n </Card>\n );\n}\n\nexport function PortalProUpgradeScreen({\n onJoinWaitlist,\n waitlistLoading = false,\n className,\n}: PortalProUpgradeScreenProps) {\n return (\n <div\n className={cn(\n \"relative flex h-full w-full flex-col overflow-hidden md:flex-row\",\n className,\n )}\n >\n {/* Pastel gradient background */}\n <div className=\"pointer-events-none absolute inset-0 rounded-xl bg-linear-[33deg,rgba(8,148,255,0.2)_4.5%,rgba(201,89,221,0.2)_24.5%,rgba(255,46,84,0.2)_54.6%,rgba(255,144,4,0.2)_69.2%,rgba(255,255,255,0.2)_76.4%] blur-[125px]\" />\n\n {/* Hero collage - absolute, behind everything, fades right and bottom */}\n <img\n src=\"https://ik.imagekit.io/fluid/980191006/images/HW87RR/hero-collage_O6DCdkFib.png\"\n alt=\"People using the app with push notifications\"\n className=\"pointer-events-none absolute top-0 left-0 h-full w-full mask-b-from-20% mask-b-to-30% object-contain object-left-top md:w-4/5 md:mask-r-from-50% md:mask-r-to-70% md:mask-b-from-60% md:mask-b-to-80%\"\n />\n\n {/* Left Section - Logo & CTA */}\n <div className=\"relative flex w-full shrink-0 flex-col items-center justify-end px-8 pt-40 md:w-1/2 md:pt-0 md:pb-10\">\n <div className=\"flex flex-col items-center gap-4\">\n <img\n src=\"https://ik.imagekit.io/fluid/980191006/images/ZYT78J/co-founder-badge_NcorZNlUY.png\"\n alt=\"Coming Soon - Co-Founder\"\n className=\"h-auto w-120 object-contain\"\n />\n {/* TODO: This is currently hidden until we actually have a way to waitlist someone */}\n <Button\n className=\"hidden w-full md:w-auto\"\n size=\"xl\"\n onClick={onJoinWaitlist}\n disabled={waitlistLoading}\n >\n {waitlistLoading ? \"Joining...\" : \"Join Waitlist\"}\n </Button>\n </div>\n </div>\n\n {/* Right Section - Content */}\n <div className=\"relative flex min-w-0 flex-1 flex-col gap-6 overflow-y-auto px-8 pt-8 md:pt-16 md:pr-5 md:pb-5 md:pl-10\">\n <div className=\"flex shrink-0 flex-col gap-2.5\">\n <h1 className=\"text-3xl leading-tight font-semibold tracking-tight text-black md:text-5xl\">\n It's like having an\n <br />\n assistant in your pocket\n </h1>\n <p className=\"text-sm leading-5 font-medium text-black\">\n Upgrade your business\n </p>\n </div>\n <div className=\"flex flex-col gap-4 pb-4\">\n {FEATURES.map((feature) => (\n <FeatureCard key={feature.title} {...feature} />\n ))}\n </div>\n </div>\n </div>\n );\n}\n","import type { WidgetPropertySchema } from \"../registries/property-schema-types\";\nimport { PortalProUpgradeScreen } from \"@fluid-app/portal-pro-upgrade-ui\";\n\nexport const upgradeScreenPropertySchema = {\n widgetType: \"UpgradeScreen\",\n displayName: \"Upgrade Screen\",\n fields: [],\n} as const satisfies WidgetPropertySchema;\n\nexport function UpgradeScreen(): React.JSX.Element {\n return (\n <PortalProUpgradeScreen\n onJoinWaitlist={() => {\n // TODO: Wire up to waitlist API\n }}\n />\n );\n}\n"],"mappings":";;;;;;;AAGA,SAAgB,GAAG,GAAG,QAAsB;AAC1C,QAAO,QAAQ,KAAK,OAAO,CAAC;;;;ACyB9B,MAAM,WAAsB;CAC1B;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACd;CACD;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACd;CACD;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACd;CACD;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACd;CACD;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACd;CACF;AAED,SAAS,YAAY,EAAE,MAAM,MAAM,OAAO,eAAwB;AAChE,QACE,oBAAC,MAAD;EAAM,WAAU;YACd,qBAAC,YAAD;GAAY,WAAU;aAAtB;IACE,oBAAC,MAAD;KAAM,WAAU;KAAS,aAAa;KAAO,CAAA;IAC7C,oBAAC,WAAD;KAAW,WAAU;eAAqB;KAAkB,CAAA;IAC5D,oBAAC,iBAAD;KAAiB,WAAU;eAAW;KAA8B,CAAA;IACzD;;EACR,CAAA;;AAIX,SAAgB,uBAAuB,EACrC,gBACA,kBAAkB,OAClB,aAC8B;AAC9B,QACE,qBAAC,OAAD;EACE,WAAW,GACT,oEACA,UACD;YAJH;GAOE,oBAAC,OAAD,EAAK,WAAU,sNAAuN,CAAA;GAGtO,oBAAC,OAAD;IACE,KAAI;IACJ,KAAI;IACJ,WAAU;IACV,CAAA;GAGF,oBAAC,OAAD;IAAK,WAAU;cACb,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,OAAD;MACE,KAAI;MACJ,KAAI;MACJ,WAAU;MACV,CAAA,EAEF,oBAAC,QAAD;MACE,WAAU;MACV,MAAK;MACL,SAAS;MACT,UAAU;gBAET,kBAAkB,eAAe;MAC3B,CAAA,CACL;;IACF,CAAA;GAGN,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,qBAAC,MAAD;MAAI,WAAU;gBAAd;OAA2F;OAEzF,oBAAC,MAAD,EAAM,CAAA;;OAEH;SACL,oBAAC,KAAD;MAAG,WAAU;gBAA2C;MAEpD,CAAA,CACA;QACN,oBAAC,OAAD;KAAK,WAAU;eACZ,SAAS,KAAK,YACb,oBAAC,aAAD,EAAiC,GAAI,SAAW,EAA9B,QAAQ,MAAsB,CAChD;KACE,CAAA,CACF;;GACF;;;;;;;;;AC9HV,MAAa,8BAA8B;CACzC,YAAY;CACZ,aAAa;CACb,QAAQ,EAAE;CACX;AAED,SAAgB,gBAAmC;AACjD,QACE,oBAAC,wBAAD,EACE,sBAAsB,IAGtB,CAAA"}
@@ -133,4 +133,4 @@ Object.defineProperty(exports, "upgradeScreenPropertySchema", {
133
133
  }
134
134
  });
135
135
 
136
- //# sourceMappingURL=UpgradeScreen-BaclFXEh.cjs.map
136
+ //# sourceMappingURL=UpgradeScreen-QhhBuHXE.cjs.map