@fluid-app/portal-sdk 0.1.340 → 0.1.341
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.
- package/dist/{AddressAutocompleteInput-4OS-oYis.mjs → AddressAutocompleteInput-BQ7HCDqS.mjs} +17 -17
- package/dist/AddressAutocompleteInput-BQ7HCDqS.mjs.map +1 -0
- package/dist/{AddressAutocompleteInput-CZITNL-n.cjs → AddressAutocompleteInput-C0ns8N-l.cjs} +17 -17
- package/dist/AddressAutocompleteInput-C0ns8N-l.cjs.map +1 -0
- package/dist/{AppDownloadScreen-BYjlFEVq.cjs → AppDownloadScreen-DqqUBpS-.cjs} +5 -5
- package/dist/AppDownloadScreen-DqqUBpS-.cjs.map +1 -0
- package/dist/{AppDownloadScreen-BkPXQ6HD.mjs → AppDownloadScreen-ZUoc8X2D.mjs} +5 -5
- package/dist/AppDownloadScreen-ZUoc8X2D.mjs.map +1 -0
- package/dist/{CardWidget-Jv6HiiK-.cjs → CardWidget--S5FBquC.cjs} +2 -2
- package/dist/{CardWidget-Jv6HiiK-.cjs.map → CardWidget--S5FBquC.cjs.map} +1 -1
- package/dist/{CardWidget-C9UbLrzZ.mjs → CardWidget-ChfurL_z.mjs} +2 -2
- package/dist/{CardWidget-C9UbLrzZ.mjs.map → CardWidget-ChfurL_z.mjs.map} +1 -1
- package/dist/{CardWidget-Wj7aNAAe.cjs → CardWidget-DsfLoyup.cjs} +2 -2
- package/dist/{ContactsScreen-BDOyYGyR.cjs → ContactsScreen-B3Ena0O7.cjs} +2 -2
- package/dist/{ContactsScreen-6x_Y2HBg.cjs → ContactsScreen-CiegGc50.cjs} +18 -18
- package/dist/ContactsScreen-CiegGc50.cjs.map +1 -0
- package/dist/{ContactsScreen-CU5_SYVK.mjs → ContactsScreen-Cwyt5qIf.mjs} +18 -18
- package/dist/ContactsScreen-Cwyt5qIf.mjs.map +1 -0
- package/dist/{ContainerWidget-B0WqVKej.mjs → ContainerWidget-BHWPtBmF.mjs} +2 -2
- package/dist/{ContainerWidget-B0WqVKej.mjs.map → ContainerWidget-BHWPtBmF.mjs.map} +1 -1
- package/dist/{ContainerWidget-BJys0vji.cjs → ContainerWidget-Cf_D4TAi.cjs} +2 -2
- package/dist/{ContainerWidget-BJys0vji.cjs.map → ContainerWidget-Cf_D4TAi.cjs.map} +1 -1
- package/dist/{ContainerWidget-BPoDy9wl.cjs → ContainerWidget-CyDVJu83.cjs} +3 -3
- package/dist/{EmbedWidget-BjLKHqCD.cjs → EmbedWidget-B1gWxgsn.cjs} +2 -2
- package/dist/EmbedWidget-B1gWxgsn.cjs.map +1 -0
- package/dist/{EmbedWidget-DwYcDOSr.mjs → EmbedWidget-JVbp9wAL.mjs} +2 -2
- package/dist/EmbedWidget-JVbp9wAL.mjs.map +1 -0
- package/dist/{FluidProvider-CYgSHMUd.cjs → FluidProvider-BFU7ermL.cjs} +23 -23
- package/dist/{FluidProvider-CYgSHMUd.cjs.map → FluidProvider-BFU7ermL.cjs.map} +1 -1
- package/dist/{FluidProvider-rLNjrHI9.mjs → FluidProvider-BqJ-t2bW.mjs} +23 -23
- package/dist/{FluidProvider-rLNjrHI9.mjs.map → FluidProvider-BqJ-t2bW.mjs.map} +1 -1
- package/dist/{LayoutWidget-COFyne-B.cjs → LayoutWidget-3G-w-YLz.cjs} +2 -2
- package/dist/{LayoutWidget-_f70Meea.cjs → LayoutWidget-CG-dWz_c.cjs} +2 -2
- package/dist/{LayoutWidget-_f70Meea.cjs.map → LayoutWidget-CG-dWz_c.cjs.map} +1 -1
- package/dist/{LayoutWidget-CUG5HXPH.mjs → LayoutWidget-CrZG6Ipw.mjs} +2 -2
- package/dist/{LayoutWidget-CUG5HXPH.mjs.map → LayoutWidget-CrZG6Ipw.mjs.map} +1 -1
- package/dist/{MessagingScreen-CKPKnmdA.cjs → MessagingScreen-7BBNidBx.cjs} +15 -15
- package/dist/{MessagingScreen-DEKQhSxk.mjs → MessagingScreen-BIWRCS3T.mjs} +15 -15
- package/dist/{MessagingScreen-C_VnN2yj.cjs → MessagingScreen-D_wVko-i.cjs} +2 -2
- package/dist/{MessagingScreen-C_VnN2yj.cjs.map → MessagingScreen-D_wVko-i.cjs.map} +1 -1
- package/dist/{MessagingScreen-Bn1jNjdF.mjs → MessagingScreen-ZwSPp6zu.mjs} +2 -2
- package/dist/{MessagingScreen-Bn1jNjdF.mjs.map → MessagingScreen-ZwSPp6zu.mjs.map} +1 -1
- package/dist/{MySiteScreen-BLI39L5d.cjs → MySiteScreen-17KRkcj7.cjs} +29 -29
- package/dist/MySiteScreen-17KRkcj7.cjs.map +1 -0
- package/dist/{MySiteScreen-h7lBk3gC.cjs → MySiteScreen-BdBnDI6s.cjs} +1 -1
- package/dist/{MySiteScreen-B5jblKuW.mjs → MySiteScreen-BlTSt7a0.mjs} +29 -29
- package/dist/MySiteScreen-BlTSt7a0.mjs.map +1 -0
- package/dist/{MySiteWidget-CAj-6hXM.cjs → MySiteWidget-23lGcDO1.cjs} +3 -3
- package/dist/MySiteWidget-23lGcDO1.cjs.map +1 -0
- package/dist/{MySiteWidget-D0ZQpY-e.mjs → MySiteWidget-BXO8rmnf.mjs} +3 -3
- package/dist/{MySiteWidget-D0ZQpY-e.mjs.map → MySiteWidget-BXO8rmnf.mjs.map} +1 -1
- package/dist/{NestedWidget-D1iDgWh8.cjs → NestedWidget-CZGOZbpZ.cjs} +5 -5
- package/dist/NestedWidget-CZGOZbpZ.cjs.map +1 -0
- package/dist/{NestedWidget-BISpn0HM.cjs → NestedWidget-Cv8h-u5K.cjs} +1 -1
- package/dist/{NestedWidget-DPC6Y52o.mjs → NestedWidget-DuNuNaeT.mjs} +5 -5
- package/dist/NestedWidget-DuNuNaeT.mjs.map +1 -0
- package/dist/{OrdersScreen-BOu-b93J.mjs → OrdersScreen-BkUvISL0.mjs} +15 -15
- package/dist/{OrdersScreen-BX3Tp0W6.mjs → OrdersScreen-DgriwQBo.mjs} +4 -4
- package/dist/OrdersScreen-DgriwQBo.mjs.map +1 -0
- package/dist/{OrdersScreen-C0-X_pGG.cjs → OrdersScreen-TGNvFpjj.cjs} +4 -4
- package/dist/OrdersScreen-TGNvFpjj.cjs.map +1 -0
- package/dist/{OrdersScreen-CyT1TpjL.cjs → OrdersScreen-t1EwXPZx.cjs} +15 -15
- package/dist/{ProfileScreen-B1OMNJrQ.mjs → ProfileScreen-Bw8W8nxK.mjs} +4 -4
- package/dist/{ProfileScreen-B1OMNJrQ.mjs.map → ProfileScreen-Bw8W8nxK.mjs.map} +1 -1
- package/dist/{ProfileScreen-DEYfahoM.mjs → ProfileScreen-CArZ7TAm.mjs} +16 -16
- package/dist/{ProfileScreen-Dshb7RM2.cjs → ProfileScreen-CP5ijd_t.cjs} +4 -4
- package/dist/{ProfileScreen-Dshb7RM2.cjs.map → ProfileScreen-CP5ijd_t.cjs.map} +1 -1
- package/dist/{ProfileScreen-BRbz69S3.cjs → ProfileScreen-DXlKK4gO.cjs} +16 -16
- package/dist/{RecentActivityWidget-CVQpo1jC.mjs → RecentActivityWidget-ClgOlTXl.mjs} +3 -3
- package/dist/RecentActivityWidget-ClgOlTXl.mjs.map +1 -0
- package/dist/{RecentActivityWidget-DnGybdc3.cjs → RecentActivityWidget-DZGKRR5x.cjs} +3 -3
- package/dist/RecentActivityWidget-DZGKRR5x.cjs.map +1 -0
- package/dist/{ScreenRenderer-BYiZunHL.cjs → ScreenRenderer-CLDJUinO.cjs} +2 -2
- package/dist/{ScreenRenderer-BYiZunHL.cjs.map → ScreenRenderer-CLDJUinO.cjs.map} +1 -1
- package/dist/{ScreenRenderer-1ZCNOyIb.mjs → ScreenRenderer-TobkTBMC.mjs} +2 -2
- package/dist/{ScreenRenderer-1ZCNOyIb.mjs.map → ScreenRenderer-TobkTBMC.mjs.map} +1 -1
- package/dist/{SearchSort-K0-ffUoD.cjs → SearchSort-CDuQPacI.cjs} +2 -2
- package/dist/{SearchSort-K0-ffUoD.cjs.map → SearchSort-CDuQPacI.cjs.map} +1 -1
- package/dist/{SearchSort-CObUt78n.mjs → SearchSort-CMUL0qt3.mjs} +2 -2
- package/dist/{SearchSort-CObUt78n.mjs.map → SearchSort-CMUL0qt3.mjs.map} +1 -1
- package/dist/{ShareablesScreen-aafzr3Vs.mjs → ShareablesScreen-B6Qs0xXn.mjs} +116 -116
- package/dist/ShareablesScreen-B6Qs0xXn.mjs.map +1 -0
- package/dist/{ShareablesScreen-COVW0ikb.cjs → ShareablesScreen-BAOW6RIK.cjs} +116 -116
- package/dist/ShareablesScreen-BAOW6RIK.cjs.map +1 -0
- package/dist/{ShareablesScreen-CNAem5rt.mjs → ShareablesScreen-C3K5ed-X.mjs} +2 -2
- package/dist/{ShareablesScreen-BZv1NXJV.cjs → ShareablesScreen-C3juoUMG.cjs} +2 -2
- package/dist/{ShopScreen-fcy9uJ3Z.cjs → ShopScreen-BjuLPVrk.cjs} +13 -13
- package/dist/ShopScreen-BjuLPVrk.cjs.map +1 -0
- package/dist/{ShopScreen-Bhn_bZ8O.mjs → ShopScreen-CtqHSaUW.mjs} +15 -15
- package/dist/{ShopScreen-CaEKFEGZ.cjs → ShopScreen-CwGh-q7J.cjs} +15 -15
- package/dist/{ShopScreen-dvruZcuz.mjs → ShopScreen-DNJ4Geoq.mjs} +13 -13
- package/dist/ShopScreen-DNJ4Geoq.mjs.map +1 -0
- package/dist/{ShopWidget-BZ48BUxp.cjs → ShopWidget-BAi2p8DP.cjs} +8 -8
- package/dist/ShopWidget-BAi2p8DP.cjs.map +1 -0
- package/dist/{ShopWidget-ZmhMoVmK.mjs → ShopWidget-D9-DhKP4.mjs} +8 -8
- package/dist/ShopWidget-D9-DhKP4.mjs.map +1 -0
- package/dist/{ShopWidget-DmlseAqM.cjs → ShopWidget-_T-ycRq1.cjs} +2 -2
- package/dist/{SubscriptionsScreen-BYQrZ2aR.mjs → SubscriptionsScreen-B55dK8Qb.mjs} +16 -16
- package/dist/{SubscriptionsScreen-CfxeuHVV.mjs → SubscriptionsScreen-CVKyyzbR.mjs} +23 -23
- package/dist/SubscriptionsScreen-CVKyyzbR.mjs.map +1 -0
- package/dist/{SubscriptionsScreen-Bupuf-0D.cjs → SubscriptionsScreen-Dz1PGO2z.cjs} +23 -23
- package/dist/SubscriptionsScreen-Dz1PGO2z.cjs.map +1 -0
- package/dist/{SubscriptionsScreen-BoSpOiOm.cjs → SubscriptionsScreen-TAXj2UXa.cjs} +16 -16
- package/dist/{TableWidget-Dyq8JXbd.cjs → TableWidget-C4F1IEKt.cjs} +1 -1
- package/dist/{TableWidget-fbsVsyqZ.mjs → TableWidget-CpHI9CGQ.mjs} +3 -3
- package/dist/TableWidget-CpHI9CGQ.mjs.map +1 -0
- package/dist/{TableWidget-wQkzw7Jg.cjs → TableWidget-RK0B52qT.cjs} +3 -3
- package/dist/TableWidget-RK0B52qT.cjs.map +1 -0
- package/dist/{ToDoWidget-BPh5ZLCT.cjs → ToDoWidget-B1uzfuI5.cjs} +2 -2
- package/dist/{ToDoWidget-1_JCx9X7.mjs → ToDoWidget-BaWksZpJ.mjs} +5 -5
- package/dist/{ToDoWidget-1_JCx9X7.mjs.map → ToDoWidget-BaWksZpJ.mjs.map} +1 -1
- package/dist/{ToDoWidget-1ZaXZTi4.cjs → ToDoWidget-C03kvBE-.cjs} +5 -5
- package/dist/{ToDoWidget-1ZaXZTi4.cjs.map → ToDoWidget-C03kvBE-.cjs.map} +1 -1
- package/dist/index.cjs +50 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +50 -50
- package/dist/index.mjs.map +1 -1
- package/dist/{task-composer-form-2z5Qk6US.cjs → task-composer-form-BEZGTBBZ.cjs} +2 -2
- package/dist/{task-composer-form-2z5Qk6US.cjs.map → task-composer-form-BEZGTBBZ.cjs.map} +1 -1
- package/dist/{task-composer-form-B3eR9kfb.mjs → task-composer-form-D_Pbl6qk.mjs} +2 -2
- package/dist/{task-composer-form-B3eR9kfb.mjs.map → task-composer-form-D_Pbl6qk.mjs.map} +1 -1
- package/package.json +15 -15
- package/dist/AddressAutocompleteInput-4OS-oYis.mjs.map +0 -1
- package/dist/AddressAutocompleteInput-CZITNL-n.cjs.map +0 -1
- package/dist/AppDownloadScreen-BYjlFEVq.cjs.map +0 -1
- package/dist/AppDownloadScreen-BkPXQ6HD.mjs.map +0 -1
- package/dist/ContactsScreen-6x_Y2HBg.cjs.map +0 -1
- package/dist/ContactsScreen-CU5_SYVK.mjs.map +0 -1
- package/dist/EmbedWidget-BjLKHqCD.cjs.map +0 -1
- package/dist/EmbedWidget-DwYcDOSr.mjs.map +0 -1
- package/dist/MySiteScreen-B5jblKuW.mjs.map +0 -1
- package/dist/MySiteScreen-BLI39L5d.cjs.map +0 -1
- package/dist/MySiteWidget-CAj-6hXM.cjs.map +0 -1
- package/dist/NestedWidget-D1iDgWh8.cjs.map +0 -1
- package/dist/NestedWidget-DPC6Y52o.mjs.map +0 -1
- package/dist/OrdersScreen-BX3Tp0W6.mjs.map +0 -1
- package/dist/OrdersScreen-C0-X_pGG.cjs.map +0 -1
- package/dist/RecentActivityWidget-CVQpo1jC.mjs.map +0 -1
- package/dist/RecentActivityWidget-DnGybdc3.cjs.map +0 -1
- package/dist/ShareablesScreen-COVW0ikb.cjs.map +0 -1
- package/dist/ShareablesScreen-aafzr3Vs.mjs.map +0 -1
- package/dist/ShopScreen-dvruZcuz.mjs.map +0 -1
- package/dist/ShopScreen-fcy9uJ3Z.cjs.map +0 -1
- package/dist/ShopWidget-BZ48BUxp.cjs.map +0 -1
- package/dist/ShopWidget-ZmhMoVmK.mjs.map +0 -1
- package/dist/SubscriptionsScreen-Bupuf-0D.cjs.map +0 -1
- package/dist/SubscriptionsScreen-CfxeuHVV.mjs.map +0 -1
- package/dist/TableWidget-fbsVsyqZ.mjs.map +0 -1
- package/dist/TableWidget-wQkzw7Jg.cjs.map +0 -1
|
@@ -325,7 +325,7 @@ function TaskComposerForm({ contactId, onDone, onClose, autoFocus = true }) {
|
|
|
325
325
|
},
|
|
326
326
|
placeholder: t("task_describe_placeholder"),
|
|
327
327
|
"aria-label": t("task_description_aria"),
|
|
328
|
-
className: "placeholder:text-muted-foreground/80 text-foreground flex-1 border-0 bg-transparent text-sm font-medium outline-none"
|
|
328
|
+
className: "placeholder:text-muted-foreground/80 text-foreground flex-1 border-0 bg-transparent text-sm font-medium outline-none pointer-coarse:text-base"
|
|
329
329
|
}),
|
|
330
330
|
onClose && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
331
331
|
type: "button",
|
|
@@ -442,4 +442,4 @@ Object.defineProperty(exports, "useTasksApi", {
|
|
|
442
442
|
}
|
|
443
443
|
});
|
|
444
444
|
|
|
445
|
-
//# sourceMappingURL=task-composer-form-
|
|
445
|
+
//# sourceMappingURL=task-composer-form-BEZGTBBZ.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-composer-form-2z5Qk6US.cjs","names":["createTranslationContext","X","cn"],"sources":["../../../platform/api-client-core/src/parse-api-errors.ts","../../../contacts/core/src/contacts-api-context.ts","../../../contacts/core/src/translation-api-context.ts","../../../contacts/core/src/parse-task-body.ts","../../../contacts/core/src/query-keys.ts","../../../contacts/core/src/hooks/use-infinite-contacts.ts","../../../contacts/core/src/iso-date.ts","../../../contacts/ui/src/portal/hooks/contacts/use-create-contact-task.ts","../../../contacts/ui/src/portal/components/tasks/task-composer-form.tsx"],"sourcesContent":["/**\n * Framework-agnostic API error parsing utilities.\n *\n * Extracts structured field-level errors from API responses and formats\n * them into human-readable messages. Works with ApiError from this package\n * as well as any error object that has `status`, `data`, and optional `message`.\n */\n\n/**\n * Converts snake_case or camelCase field names to Title Case\n */\nexport function formatFieldName(field: string): string {\n return field\n .replace(/_/g, \" \")\n .replace(/([A-Z])/g, \" $1\")\n .split(\" \")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\" \")\n .trim();\n}\n\nexport interface ParsedFieldError {\n field: string;\n messages: string[];\n}\n\n/**\n * Type guard to check if an error looks like an API error\n */\nexport function isApiLikeError(\n error: unknown,\n): error is { message: string | undefined; status: number; data: unknown } {\n if (!error || typeof error !== \"object\") {\n return false;\n }\n\n const err = error as Record<string, unknown>;\n return (\n typeof err.status === \"number\" &&\n \"data\" in err &&\n (typeof err.message === \"string\" || err.message === undefined)\n );\n}\n\n/**\n * Extracts field-level errors from API error data\n */\nexport function extractFieldErrors(data: unknown): ParsedFieldError[] {\n const errors: ParsedFieldError[] = [];\n\n if (!data || typeof data !== \"object\") {\n return errors;\n }\n\n const errorObj = data as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(errorObj)) {\n if (Array.isArray(value) && value.length > 0) {\n errors.push({\n field: formatFieldName(key),\n messages: value.map((item) =>\n typeof item === \"string\" ? item : JSON.stringify(item),\n ),\n });\n } else if (typeof value === \"string\" && value.length > 0) {\n errors.push({\n field: formatFieldName(key),\n messages: [value],\n });\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const nestedErrors = extractFieldErrors(value);\n nestedErrors.forEach((nestedError) => {\n errors.push({\n field: `${formatFieldName(key)} → ${nestedError.field}`,\n messages: nestedError.messages,\n });\n });\n }\n }\n\n return errors;\n}\n\n/**\n * Formats field errors into a readable description string\n */\nexport function formatErrorDescription(errors: ParsedFieldError[]): string {\n if (errors.length === 0) {\n return \"\";\n }\n\n if (errors.length === 1) {\n const err = errors[0];\n if (!err) return \"\";\n const message = err.messages[0] || \"is invalid\";\n return `${err.field} ${message}`;\n }\n\n if (errors.length <= 3) {\n return errors\n .map((e) => `${e.field} ${e.messages[0] || \"is invalid\"}`)\n .join(\"\\n\");\n }\n\n const shown = errors\n .slice(0, 3)\n .map((e) => `${e.field} ${e.messages[0] || \"is invalid\"}`)\n .join(\"\\n\");\n const remaining = errors.length - 3;\n return `${shown}\\n...and ${remaining} more ${remaining === 1 ? \"error\" : \"errors\"}`;\n}\n\n/**\n * Parses an error and returns a human-readable description string.\n *\n * Handles:\n * - API-like errors with structured field-level data\n * - API-like errors with a top-level message\n * - Standard Error instances\n * - Falls back to the provided fallback string\n *\n * @param error - The error to parse (ApiError, Error, or unknown)\n * @param fallback - Optional fallback description if error cannot be parsed\n * @returns A human-readable error description, or undefined if nothing could be extracted\n */\nexport function parseApiErrors(\n error: unknown,\n fallback?: string,\n): string | undefined {\n if (isApiLikeError(error)) {\n if (error.data) {\n const fieldErrors = extractFieldErrors(error.data);\n if (fieldErrors.length > 0) {\n return formatErrorDescription(fieldErrors);\n }\n }\n\n if (error.message) {\n return error.message;\n }\n } else if (error instanceof Error) {\n return error.message;\n }\n\n return fallback;\n}\n","import { createContext, useContext } from \"react\";\nimport type { ContactsApi } from \"./contacts-api\";\nimport type { NotesApi } from \"./notes-api\";\nimport type { TasksApi } from \"./tasks-api\";\nimport type { GroupsApi } from \"./groups-api\";\n\nexport interface ContactsDomainApi {\n contacts: ContactsApi;\n notes: NotesApi;\n tasks: TasksApi;\n groups?: GroupsApi;\n}\n\nconst ContactsApiContext = createContext<ContactsDomainApi | null>(null);\n\nexport const ContactsApiProvider = ContactsApiContext.Provider;\n\nexport function useContactsDomainApi(): ContactsDomainApi {\n const api = useContext(ContactsApiContext);\n if (!api) {\n throw new Error(\n \"useContactsDomainApi must be used within a ContactsApiProvider\",\n );\n }\n return api;\n}\n\nexport function useContactsCrud(): ContactsApi {\n return useContactsDomainApi().contacts;\n}\n\nexport function useNotesApi(): NotesApi {\n return useContactsDomainApi().notes;\n}\n\nexport function useTasksApi(): TasksApi {\n return useContactsDomainApi().tasks;\n}\n\n/** Returns GroupsApi if the provider supplies one, otherwise null. */\nexport function useGroupsApi(): GroupsApi | null {\n return useContactsDomainApi().groups ?? null;\n}\n","import type { Provider } from \"react\";\nimport { createTranslationContext } from \"@fluid-app/i18n/translation-api-context-factory\";\nimport type { TranslationApi } from \"@fluid-app/i18n/translation-api\";\nimport type { ContactsDict } from \"./translation-dictionary\";\n\nconst { Provider: ContactsProvider, useTranslation } =\n createTranslationContext<ContactsDict>(\"Contacts\");\n\nexport const ContactsTranslationProvider: Provider<TranslationApi<ContactsDict> | null> =\n ContactsProvider;\nexport const useContactsTranslation = useTranslation;\n","/**\n * Tasks store both a title (first line) and an optional body separated by a\n * blank line. This is the canonical convention used by the task editor and\n * by every consumer that displays tasks (contacts UI list, portal todo\n * widget). Drift across consumers would mean some surfaces show \"Title\\n\\n\n * body details\" verbatim while others split correctly — keep the delimiter\n * convention in one place.\n */\nexport function parseTaskBody(raw: string): { title: string; body: string } {\n const split = raw.indexOf(\"\\n\\n\");\n if (split >= 0) {\n return {\n title: raw.slice(0, split),\n body: raw.slice(split + 2),\n };\n }\n return { title: raw, body: \"\" };\n}\n","export const CONTACTS_QUERY_KEYS = {\n all: (prefix: string) => [prefix] as const,\n list: (prefix: string) =>\n [...CONTACTS_QUERY_KEYS.all(prefix), \"list\"] as const,\n detail: (prefix: string, id: string) =>\n [...CONTACTS_QUERY_KEYS.all(prefix), \"detail\", id] as const,\n} as const;\n\nexport const contactsKeys = {\n activities: (contactId: string) =>\n [\"portal-contacts\", \"activities\", contactId] as const,\n tasks: (contactId: string) =>\n [\"portal-contacts\", \"tasks\", contactId] as const,\n notes: (contactId: string) =>\n [\"portal-contacts\", \"notes\", contactId] as const,\n orders: (contactId: string) => [\"rep-contacts\", \"orders\", contactId] as const,\n subscriptionOrders: (contactId: string) =>\n [\"rep-contacts\", \"subscription-orders\", contactId] as const,\n groups: () => [\"portal-contacts\", \"groups\"] as const,\n} as const;\n","import { useInfiniteQuery } from \"@tanstack/react-query\";\nimport { useContactsCrud } from \"../contacts-api-context\";\nimport { CONTACTS_QUERY_KEYS } from \"../query-keys\";\n\nexport interface UseInfiniteContactsParams {\n search_query?: string;\n status?: string;\n sort_by?: string;\n sort_direction?: string;\n per_page?: number;\n tags?: string[];\n}\n\nexport function useInfiniteContacts(params: UseInfiniteContactsParams) {\n const api = useContactsCrud();\n return useInfiniteQuery({\n queryKey: [...CONTACTS_QUERY_KEYS.list(\"contacts\"), params],\n queryFn: ({ pageParam }) =>\n api.listContacts({\n ...params,\n page: pageParam,\n }),\n getNextPageParam: (lastPage) => {\n const currentPage = lastPage.meta.current_page;\n // Contacts API is page-number based; next_cursor and total_pages\n // are both used as \"has-next-page\" signals.\n if (currentPage == null) return undefined;\n if (lastPage.meta.next_cursor) return currentPage + 1;\n if (\n lastPage.meta.total_pages != null &&\n currentPage < lastPage.meta.total_pages\n ) {\n return currentPage + 1;\n }\n return undefined;\n },\n initialPageParam: 1,\n });\n}\n","/**\n * Format a date as YYYY-MM-DD in the renderer's local timezone, optionally\n * shifted by `offsetDays`. Use this for due-date inputs where \"today\" must\n * resolve to the user's local calendar date — never `toISOString().slice(0, 10)`,\n * which returns the UTC date and silently rolls over a day for users east/west\n * of UTC at the wrong hours.\n */\nexport function isoDate(offsetDays: number, now: Date = new Date()): string {\n const d = new Date(now);\n d.setDate(d.getDate() + offsetDays);\n const yyyy = d.getFullYear();\n const mm = String(d.getMonth() + 1).padStart(2, \"0\");\n const dd = String(d.getDate()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}`;\n}\n\n/**\n * Return a Date pinned to local midnight of the calendar day represented by\n * `input`. Use this whenever you need to compare two due dates by calendar day\n * (overdue / today / tomorrow / future).\n *\n * Why not just `new Date(input)`? `new Date(\"YYYY-MM-DD\")` parses the string\n * as UTC midnight, which lands on the *previous* calendar day in any UTC−\n * timezone — a task due \"May 1\" then reads as April 30 for a user in the\n * Americas, classifying it as \"Overdue\" all day. Parse the date components\n * directly so the calendar-day intent of the string is preserved.\n */\nexport function startOfLocalDay(input: Date | string): Date {\n if (typeof input === \"string\") {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(input);\n if (match?.[1] && match[2] && match[3]) {\n return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));\n }\n }\n const d = typeof input === \"string\" ? new Date(input) : input;\n return new Date(d.getFullYear(), d.getMonth(), d.getDate());\n}\n","\"use client\";\n\nimport { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { fluidToast } from \"@fluid-app/ui-primitives\";\nimport { parseApiErrors } from \"@fluid-app/api-client-core\";\nimport { useTasksApi } from \"@fluid-app/contacts-core/contacts-api-context\";\nimport { contactsKeys } from \"@fluid-app/contacts-core/query-keys\";\nimport type { CreateTaskInput } from \"@fluid-app/contacts-core/types\";\n\nexport type { CreateTaskInput };\n\nexport function useCreateContactTask(\n contactId: string,\n options?: { onSuccess?: () => void },\n) {\n const queryClient = useQueryClient();\n const api = useTasksApi();\n\n return useMutation({\n mutationFn: (input: CreateTaskInput) => api.createTask(contactId, input),\n onSuccess: () => {\n fluidToast({ title: \"Task created\", type: \"success\" });\n queryClient.invalidateQueries({\n queryKey: contactsKeys.tasks(contactId),\n });\n options?.onSuccess?.();\n },\n onError: (error) => {\n const description = parseApiErrors(error);\n fluidToast({\n title: \"Failed to create task\",\n type: \"error\",\n ...(description ? { description } : {}),\n });\n },\n });\n}\n","\"use client\";\n\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@fluid-app/ui-primitives\";\nimport { useContactsTranslation } from \"@fluid-app/contacts-core/translation-api-context\";\nimport { isoDate } from \"@fluid-app/contacts-core/iso-date\";\nimport { useCreateContactTask } from \"../../hooks/contacts/use-create-contact-task\";\n\nconst QUICK_DATES = [\n { key: \"today\", offsetDays: 0 },\n { key: \"tomorrow\", offsetDays: 1 },\n { key: \"next_week\", offsetDays: 7 },\n] as const;\n\nexport interface TaskComposerFormProps {\n contactId: string;\n /** Called after a successful create. */\n onDone?: () => void;\n /** When provided, an X button is rendered and Escape closes the form. */\n onClose?: () => void;\n /** Focus the body input on mount. Default true. */\n autoFocus?: boolean;\n}\n\nexport function TaskComposerForm({\n contactId,\n onDone,\n onClose,\n autoFocus = true,\n}: TaskComposerFormProps): React.JSX.Element {\n const { t } = useContactsTranslation();\n const [body, setBody] = useState(\"\");\n const [dueDate, setDueDate] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const createTask = useCreateContactTask(contactId, {\n onSuccess: () => {\n setBody(\"\");\n setDueDate(null);\n onDone?.();\n },\n });\n\n useEffect(() => {\n if (autoFocus) inputRef.current?.focus();\n }, [autoFocus]);\n\n // Compute the date strings once per mount. Re-running on every render would\n // re-evaluate `new Date()` and could flip \"today\" if the form happens to be\n // open across midnight — acceptable but unnecessary churn.\n const quickDates = useMemo(\n () =>\n QUICK_DATES.map((q) => ({\n key: q.key,\n iso: isoDate(q.offsetDays),\n })),\n [],\n );\n\n const quickLabels: Record<(typeof QUICK_DATES)[number][\"key\"], string> = {\n today: t(\"quick_today\"),\n tomorrow: t(\"quick_tomorrow\"),\n next_week: t(\"quick_next_week\"),\n };\n\n const submit = () => {\n const trimmed = body.trim();\n if (!trimmed || createTask.isPending) return;\n createTask.mutate({ body: trimmed, due_at: dueDate });\n };\n\n const canSubmit = body.trim().length > 0 && !createTask.isPending;\n\n return (\n <div className=\"border-border/50 focus-within:border-foreground/30 rounded-xl border p-3 transition-colors\">\n <div className=\"flex items-start gap-3\">\n <div\n className=\"border-muted-foreground/50 mt-1.5 size-5 shrink-0 rounded-full border-2\"\n aria-hidden=\"true\"\n />\n <input\n ref={inputRef}\n value={body}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n } else if (e.key === \"Escape\" && onClose) {\n e.preventDefault();\n onClose();\n }\n }}\n placeholder={t(\"task_describe_placeholder\")}\n aria-label={t(\"task_description_aria\")}\n className=\"placeholder:text-muted-foreground/80 text-foreground flex-1 border-0 bg-transparent text-sm font-medium outline-none\"\n />\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={t(\"task_discard_aria\")}\n className=\"text-muted-foreground hover:bg-muted hover:text-foreground -mt-0.5 -mr-0.5 flex size-7 shrink-0 items-center justify-center rounded-md transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n )}\n </div>\n\n {/* --row-indent aligns the chip row with the input text above (size-5\n circle + gap-3 = 2rem). The mobile Add button cancels it via\n -ml-(--row-indent) + w-[calc(100%+var(--row-indent))] so the two\n values stay locked to a single source. */}\n <div className=\"mt-3 flex flex-wrap items-center gap-1.5 pl-(--row-indent) [--row-indent:2rem]\">\n {quickDates.map((q) => {\n const isActive = dueDate === q.iso;\n return (\n <button\n key={q.key}\n type=\"button\"\n onClick={() => setDueDate(isActive ? null : q.iso)}\n aria-pressed={isActive}\n className={cn(\n \"shrink-0 rounded-full px-3 py-1 text-xs font-medium transition-colors\",\n isActive\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground hover:bg-muted/70\",\n )}\n >\n {quickLabels[q.key]}\n </button>\n );\n })}\n <button\n type=\"button\"\n onClick={submit}\n disabled={!canSubmit}\n className=\"bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground mt-1 -ml-(--row-indent) inline-flex h-10 w-[calc(100%+var(--row-indent))] items-center justify-center rounded-lg text-sm font-semibold transition-colors disabled:cursor-not-allowed sm:mt-0 sm:ml-auto sm:h-auto sm:w-auto sm:shrink-0 sm:gap-1.5 sm:rounded-full sm:px-4 sm:py-1.5 sm:text-xs\"\n >\n {createTask.isPending ? t(\"adding\") : t(\"add_task\")}\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"task-composer-form-BEZGTBBZ.cjs","names":["createTranslationContext","X","cn"],"sources":["../../../platform/api-client-core/src/parse-api-errors.ts","../../../contacts/core/src/contacts-api-context.ts","../../../contacts/core/src/translation-api-context.ts","../../../contacts/core/src/parse-task-body.ts","../../../contacts/core/src/query-keys.ts","../../../contacts/core/src/hooks/use-infinite-contacts.ts","../../../contacts/core/src/iso-date.ts","../../../contacts/ui/src/portal/hooks/contacts/use-create-contact-task.ts","../../../contacts/ui/src/portal/components/tasks/task-composer-form.tsx"],"sourcesContent":["/**\n * Framework-agnostic API error parsing utilities.\n *\n * Extracts structured field-level errors from API responses and formats\n * them into human-readable messages. Works with ApiError from this package\n * as well as any error object that has `status`, `data`, and optional `message`.\n */\n\n/**\n * Converts snake_case or camelCase field names to Title Case\n */\nexport function formatFieldName(field: string): string {\n return field\n .replace(/_/g, \" \")\n .replace(/([A-Z])/g, \" $1\")\n .split(\" \")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\" \")\n .trim();\n}\n\nexport interface ParsedFieldError {\n field: string;\n messages: string[];\n}\n\n/**\n * Type guard to check if an error looks like an API error\n */\nexport function isApiLikeError(\n error: unknown,\n): error is { message: string | undefined; status: number; data: unknown } {\n if (!error || typeof error !== \"object\") {\n return false;\n }\n\n const err = error as Record<string, unknown>;\n return (\n typeof err.status === \"number\" &&\n \"data\" in err &&\n (typeof err.message === \"string\" || err.message === undefined)\n );\n}\n\n/**\n * Extracts field-level errors from API error data\n */\nexport function extractFieldErrors(data: unknown): ParsedFieldError[] {\n const errors: ParsedFieldError[] = [];\n\n if (!data || typeof data !== \"object\") {\n return errors;\n }\n\n const errorObj = data as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(errorObj)) {\n if (Array.isArray(value) && value.length > 0) {\n errors.push({\n field: formatFieldName(key),\n messages: value.map((item) =>\n typeof item === \"string\" ? item : JSON.stringify(item),\n ),\n });\n } else if (typeof value === \"string\" && value.length > 0) {\n errors.push({\n field: formatFieldName(key),\n messages: [value],\n });\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const nestedErrors = extractFieldErrors(value);\n nestedErrors.forEach((nestedError) => {\n errors.push({\n field: `${formatFieldName(key)} → ${nestedError.field}`,\n messages: nestedError.messages,\n });\n });\n }\n }\n\n return errors;\n}\n\n/**\n * Formats field errors into a readable description string\n */\nexport function formatErrorDescription(errors: ParsedFieldError[]): string {\n if (errors.length === 0) {\n return \"\";\n }\n\n if (errors.length === 1) {\n const err = errors[0];\n if (!err) return \"\";\n const message = err.messages[0] || \"is invalid\";\n return `${err.field} ${message}`;\n }\n\n if (errors.length <= 3) {\n return errors\n .map((e) => `${e.field} ${e.messages[0] || \"is invalid\"}`)\n .join(\"\\n\");\n }\n\n const shown = errors\n .slice(0, 3)\n .map((e) => `${e.field} ${e.messages[0] || \"is invalid\"}`)\n .join(\"\\n\");\n const remaining = errors.length - 3;\n return `${shown}\\n...and ${remaining} more ${remaining === 1 ? \"error\" : \"errors\"}`;\n}\n\n/**\n * Parses an error and returns a human-readable description string.\n *\n * Handles:\n * - API-like errors with structured field-level data\n * - API-like errors with a top-level message\n * - Standard Error instances\n * - Falls back to the provided fallback string\n *\n * @param error - The error to parse (ApiError, Error, or unknown)\n * @param fallback - Optional fallback description if error cannot be parsed\n * @returns A human-readable error description, or undefined if nothing could be extracted\n */\nexport function parseApiErrors(\n error: unknown,\n fallback?: string,\n): string | undefined {\n if (isApiLikeError(error)) {\n if (error.data) {\n const fieldErrors = extractFieldErrors(error.data);\n if (fieldErrors.length > 0) {\n return formatErrorDescription(fieldErrors);\n }\n }\n\n if (error.message) {\n return error.message;\n }\n } else if (error instanceof Error) {\n return error.message;\n }\n\n return fallback;\n}\n","import { createContext, useContext } from \"react\";\nimport type { ContactsApi } from \"./contacts-api\";\nimport type { NotesApi } from \"./notes-api\";\nimport type { TasksApi } from \"./tasks-api\";\nimport type { GroupsApi } from \"./groups-api\";\n\nexport interface ContactsDomainApi {\n contacts: ContactsApi;\n notes: NotesApi;\n tasks: TasksApi;\n groups?: GroupsApi;\n}\n\nconst ContactsApiContext = createContext<ContactsDomainApi | null>(null);\n\nexport const ContactsApiProvider = ContactsApiContext.Provider;\n\nexport function useContactsDomainApi(): ContactsDomainApi {\n const api = useContext(ContactsApiContext);\n if (!api) {\n throw new Error(\n \"useContactsDomainApi must be used within a ContactsApiProvider\",\n );\n }\n return api;\n}\n\nexport function useContactsCrud(): ContactsApi {\n return useContactsDomainApi().contacts;\n}\n\nexport function useNotesApi(): NotesApi {\n return useContactsDomainApi().notes;\n}\n\nexport function useTasksApi(): TasksApi {\n return useContactsDomainApi().tasks;\n}\n\n/** Returns GroupsApi if the provider supplies one, otherwise null. */\nexport function useGroupsApi(): GroupsApi | null {\n return useContactsDomainApi().groups ?? null;\n}\n","import type { Provider } from \"react\";\nimport { createTranslationContext } from \"@fluid-app/i18n/translation-api-context-factory\";\nimport type { TranslationApi } from \"@fluid-app/i18n/translation-api\";\nimport type { ContactsDict } from \"./translation-dictionary\";\n\nconst { Provider: ContactsProvider, useTranslation } =\n createTranslationContext<ContactsDict>(\"Contacts\");\n\nexport const ContactsTranslationProvider: Provider<TranslationApi<ContactsDict> | null> =\n ContactsProvider;\nexport const useContactsTranslation = useTranslation;\n","/**\n * Tasks store both a title (first line) and an optional body separated by a\n * blank line. This is the canonical convention used by the task editor and\n * by every consumer that displays tasks (contacts UI list, portal todo\n * widget). Drift across consumers would mean some surfaces show \"Title\\n\\n\n * body details\" verbatim while others split correctly — keep the delimiter\n * convention in one place.\n */\nexport function parseTaskBody(raw: string): { title: string; body: string } {\n const split = raw.indexOf(\"\\n\\n\");\n if (split >= 0) {\n return {\n title: raw.slice(0, split),\n body: raw.slice(split + 2),\n };\n }\n return { title: raw, body: \"\" };\n}\n","export const CONTACTS_QUERY_KEYS = {\n all: (prefix: string) => [prefix] as const,\n list: (prefix: string) =>\n [...CONTACTS_QUERY_KEYS.all(prefix), \"list\"] as const,\n detail: (prefix: string, id: string) =>\n [...CONTACTS_QUERY_KEYS.all(prefix), \"detail\", id] as const,\n} as const;\n\nexport const contactsKeys = {\n activities: (contactId: string) =>\n [\"portal-contacts\", \"activities\", contactId] as const,\n tasks: (contactId: string) =>\n [\"portal-contacts\", \"tasks\", contactId] as const,\n notes: (contactId: string) =>\n [\"portal-contacts\", \"notes\", contactId] as const,\n orders: (contactId: string) => [\"rep-contacts\", \"orders\", contactId] as const,\n subscriptionOrders: (contactId: string) =>\n [\"rep-contacts\", \"subscription-orders\", contactId] as const,\n groups: () => [\"portal-contacts\", \"groups\"] as const,\n} as const;\n","import { useInfiniteQuery } from \"@tanstack/react-query\";\nimport { useContactsCrud } from \"../contacts-api-context\";\nimport { CONTACTS_QUERY_KEYS } from \"../query-keys\";\n\nexport interface UseInfiniteContactsParams {\n search_query?: string;\n status?: string;\n sort_by?: string;\n sort_direction?: string;\n per_page?: number;\n tags?: string[];\n}\n\nexport function useInfiniteContacts(params: UseInfiniteContactsParams) {\n const api = useContactsCrud();\n return useInfiniteQuery({\n queryKey: [...CONTACTS_QUERY_KEYS.list(\"contacts\"), params],\n queryFn: ({ pageParam }) =>\n api.listContacts({\n ...params,\n page: pageParam,\n }),\n getNextPageParam: (lastPage) => {\n const currentPage = lastPage.meta.current_page;\n // Contacts API is page-number based; next_cursor and total_pages\n // are both used as \"has-next-page\" signals.\n if (currentPage == null) return undefined;\n if (lastPage.meta.next_cursor) return currentPage + 1;\n if (\n lastPage.meta.total_pages != null &&\n currentPage < lastPage.meta.total_pages\n ) {\n return currentPage + 1;\n }\n return undefined;\n },\n initialPageParam: 1,\n });\n}\n","/**\n * Format a date as YYYY-MM-DD in the renderer's local timezone, optionally\n * shifted by `offsetDays`. Use this for due-date inputs where \"today\" must\n * resolve to the user's local calendar date — never `toISOString().slice(0, 10)`,\n * which returns the UTC date and silently rolls over a day for users east/west\n * of UTC at the wrong hours.\n */\nexport function isoDate(offsetDays: number, now: Date = new Date()): string {\n const d = new Date(now);\n d.setDate(d.getDate() + offsetDays);\n const yyyy = d.getFullYear();\n const mm = String(d.getMonth() + 1).padStart(2, \"0\");\n const dd = String(d.getDate()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}`;\n}\n\n/**\n * Return a Date pinned to local midnight of the calendar day represented by\n * `input`. Use this whenever you need to compare two due dates by calendar day\n * (overdue / today / tomorrow / future).\n *\n * Why not just `new Date(input)`? `new Date(\"YYYY-MM-DD\")` parses the string\n * as UTC midnight, which lands on the *previous* calendar day in any UTC−\n * timezone — a task due \"May 1\" then reads as April 30 for a user in the\n * Americas, classifying it as \"Overdue\" all day. Parse the date components\n * directly so the calendar-day intent of the string is preserved.\n */\nexport function startOfLocalDay(input: Date | string): Date {\n if (typeof input === \"string\") {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(input);\n if (match?.[1] && match[2] && match[3]) {\n return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));\n }\n }\n const d = typeof input === \"string\" ? new Date(input) : input;\n return new Date(d.getFullYear(), d.getMonth(), d.getDate());\n}\n","\"use client\";\n\nimport { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { fluidToast } from \"@fluid-app/ui-primitives\";\nimport { parseApiErrors } from \"@fluid-app/api-client-core\";\nimport { useTasksApi } from \"@fluid-app/contacts-core/contacts-api-context\";\nimport { contactsKeys } from \"@fluid-app/contacts-core/query-keys\";\nimport type { CreateTaskInput } from \"@fluid-app/contacts-core/types\";\n\nexport type { CreateTaskInput };\n\nexport function useCreateContactTask(\n contactId: string,\n options?: { onSuccess?: () => void },\n) {\n const queryClient = useQueryClient();\n const api = useTasksApi();\n\n return useMutation({\n mutationFn: (input: CreateTaskInput) => api.createTask(contactId, input),\n onSuccess: () => {\n fluidToast({ title: \"Task created\", type: \"success\" });\n queryClient.invalidateQueries({\n queryKey: contactsKeys.tasks(contactId),\n });\n options?.onSuccess?.();\n },\n onError: (error) => {\n const description = parseApiErrors(error);\n fluidToast({\n title: \"Failed to create task\",\n type: \"error\",\n ...(description ? { description } : {}),\n });\n },\n });\n}\n","\"use client\";\n\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@fluid-app/ui-primitives\";\nimport { useContactsTranslation } from \"@fluid-app/contacts-core/translation-api-context\";\nimport { isoDate } from \"@fluid-app/contacts-core/iso-date\";\nimport { useCreateContactTask } from \"../../hooks/contacts/use-create-contact-task\";\n\nconst QUICK_DATES = [\n { key: \"today\", offsetDays: 0 },\n { key: \"tomorrow\", offsetDays: 1 },\n { key: \"next_week\", offsetDays: 7 },\n] as const;\n\nexport interface TaskComposerFormProps {\n contactId: string;\n /** Called after a successful create. */\n onDone?: () => void;\n /** When provided, an X button is rendered and Escape closes the form. */\n onClose?: () => void;\n /** Focus the body input on mount. Default true. */\n autoFocus?: boolean;\n}\n\nexport function TaskComposerForm({\n contactId,\n onDone,\n onClose,\n autoFocus = true,\n}: TaskComposerFormProps): React.JSX.Element {\n const { t } = useContactsTranslation();\n const [body, setBody] = useState(\"\");\n const [dueDate, setDueDate] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const createTask = useCreateContactTask(contactId, {\n onSuccess: () => {\n setBody(\"\");\n setDueDate(null);\n onDone?.();\n },\n });\n\n useEffect(() => {\n if (autoFocus) inputRef.current?.focus();\n }, [autoFocus]);\n\n // Compute the date strings once per mount. Re-running on every render would\n // re-evaluate `new Date()` and could flip \"today\" if the form happens to be\n // open across midnight — acceptable but unnecessary churn.\n const quickDates = useMemo(\n () =>\n QUICK_DATES.map((q) => ({\n key: q.key,\n iso: isoDate(q.offsetDays),\n })),\n [],\n );\n\n const quickLabels: Record<(typeof QUICK_DATES)[number][\"key\"], string> = {\n today: t(\"quick_today\"),\n tomorrow: t(\"quick_tomorrow\"),\n next_week: t(\"quick_next_week\"),\n };\n\n const submit = () => {\n const trimmed = body.trim();\n if (!trimmed || createTask.isPending) return;\n createTask.mutate({ body: trimmed, due_at: dueDate });\n };\n\n const canSubmit = body.trim().length > 0 && !createTask.isPending;\n\n return (\n <div className=\"border-border/50 focus-within:border-foreground/30 rounded-xl border p-3 transition-colors\">\n <div className=\"flex items-start gap-3\">\n <div\n className=\"border-muted-foreground/50 mt-1.5 size-5 shrink-0 rounded-full border-2\"\n aria-hidden=\"true\"\n />\n <input\n ref={inputRef}\n value={body}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n } else if (e.key === \"Escape\" && onClose) {\n e.preventDefault();\n onClose();\n }\n }}\n placeholder={t(\"task_describe_placeholder\")}\n aria-label={t(\"task_description_aria\")}\n className=\"placeholder:text-muted-foreground/80 text-foreground flex-1 border-0 bg-transparent text-sm font-medium outline-none pointer-coarse:text-base\"\n />\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={t(\"task_discard_aria\")}\n className=\"text-muted-foreground hover:bg-muted hover:text-foreground -mt-0.5 -mr-0.5 flex size-7 shrink-0 items-center justify-center rounded-md transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n )}\n </div>\n\n {/* --row-indent aligns the chip row with the input text above (size-5\n circle + gap-3 = 2rem). The mobile Add button cancels it via\n -ml-(--row-indent) + w-[calc(100%+var(--row-indent))] so the two\n values stay locked to a single source. */}\n <div className=\"mt-3 flex flex-wrap items-center gap-1.5 pl-(--row-indent) [--row-indent:2rem]\">\n {quickDates.map((q) => {\n const isActive = dueDate === q.iso;\n return (\n <button\n key={q.key}\n type=\"button\"\n onClick={() => setDueDate(isActive ? null : q.iso)}\n aria-pressed={isActive}\n className={cn(\n \"shrink-0 rounded-full px-3 py-1 text-xs font-medium transition-colors\",\n isActive\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground hover:bg-muted/70\",\n )}\n >\n {quickLabels[q.key]}\n </button>\n );\n })}\n <button\n type=\"button\"\n onClick={submit}\n disabled={!canSubmit}\n className=\"bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground mt-1 -ml-(--row-indent) inline-flex h-10 w-[calc(100%+var(--row-indent))] items-center justify-center rounded-lg text-sm font-semibold transition-colors disabled:cursor-not-allowed sm:mt-0 sm:ml-auto sm:h-auto sm:w-auto sm:shrink-0 sm:gap-1.5 sm:rounded-full sm:px-4 sm:py-1.5 sm:text-xs\"\n >\n {createTask.isPending ? t(\"adding\") : t(\"add_task\")}\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -323,7 +323,7 @@ function TaskComposerForm({ contactId, onDone, onClose, autoFocus = true }) {
|
|
|
323
323
|
},
|
|
324
324
|
placeholder: t("task_describe_placeholder"),
|
|
325
325
|
"aria-label": t("task_description_aria"),
|
|
326
|
-
className: "placeholder:text-muted-foreground/80 text-foreground flex-1 border-0 bg-transparent text-sm font-medium outline-none"
|
|
326
|
+
className: "placeholder:text-muted-foreground/80 text-foreground flex-1 border-0 bg-transparent text-sm font-medium outline-none pointer-coarse:text-base"
|
|
327
327
|
}),
|
|
328
328
|
onClose && /* @__PURE__ */ jsx("button", {
|
|
329
329
|
type: "button",
|
|
@@ -357,4 +357,4 @@ function TaskComposerForm({ contactId, onDone, onClose, autoFocus = true }) {
|
|
|
357
357
|
//#endregion
|
|
358
358
|
export { contactsKeys as a, useContactsTranslation as c, useGroupsApi as d, useNotesApi as f, CONTACTS_QUERY_KEYS as i, ContactsApiProvider as l, parseApiErrors as m, startOfLocalDay as n, parseTaskBody as o, useTasksApi as p, useInfiniteContacts as r, ContactsTranslationProvider as s, TaskComposerForm as t, useContactsCrud as u };
|
|
359
359
|
|
|
360
|
-
//# sourceMappingURL=task-composer-form-
|
|
360
|
+
//# sourceMappingURL=task-composer-form-D_Pbl6qk.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-composer-form-B3eR9kfb.mjs","names":[],"sources":["../../../platform/api-client-core/src/parse-api-errors.ts","../../../contacts/core/src/contacts-api-context.ts","../../../contacts/core/src/translation-api-context.ts","../../../contacts/core/src/parse-task-body.ts","../../../contacts/core/src/query-keys.ts","../../../contacts/core/src/hooks/use-infinite-contacts.ts","../../../contacts/core/src/iso-date.ts","../../../contacts/ui/src/portal/hooks/contacts/use-create-contact-task.ts","../../../contacts/ui/src/portal/components/tasks/task-composer-form.tsx"],"sourcesContent":["/**\n * Framework-agnostic API error parsing utilities.\n *\n * Extracts structured field-level errors from API responses and formats\n * them into human-readable messages. Works with ApiError from this package\n * as well as any error object that has `status`, `data`, and optional `message`.\n */\n\n/**\n * Converts snake_case or camelCase field names to Title Case\n */\nexport function formatFieldName(field: string): string {\n return field\n .replace(/_/g, \" \")\n .replace(/([A-Z])/g, \" $1\")\n .split(\" \")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\" \")\n .trim();\n}\n\nexport interface ParsedFieldError {\n field: string;\n messages: string[];\n}\n\n/**\n * Type guard to check if an error looks like an API error\n */\nexport function isApiLikeError(\n error: unknown,\n): error is { message: string | undefined; status: number; data: unknown } {\n if (!error || typeof error !== \"object\") {\n return false;\n }\n\n const err = error as Record<string, unknown>;\n return (\n typeof err.status === \"number\" &&\n \"data\" in err &&\n (typeof err.message === \"string\" || err.message === undefined)\n );\n}\n\n/**\n * Extracts field-level errors from API error data\n */\nexport function extractFieldErrors(data: unknown): ParsedFieldError[] {\n const errors: ParsedFieldError[] = [];\n\n if (!data || typeof data !== \"object\") {\n return errors;\n }\n\n const errorObj = data as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(errorObj)) {\n if (Array.isArray(value) && value.length > 0) {\n errors.push({\n field: formatFieldName(key),\n messages: value.map((item) =>\n typeof item === \"string\" ? item : JSON.stringify(item),\n ),\n });\n } else if (typeof value === \"string\" && value.length > 0) {\n errors.push({\n field: formatFieldName(key),\n messages: [value],\n });\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const nestedErrors = extractFieldErrors(value);\n nestedErrors.forEach((nestedError) => {\n errors.push({\n field: `${formatFieldName(key)} → ${nestedError.field}`,\n messages: nestedError.messages,\n });\n });\n }\n }\n\n return errors;\n}\n\n/**\n * Formats field errors into a readable description string\n */\nexport function formatErrorDescription(errors: ParsedFieldError[]): string {\n if (errors.length === 0) {\n return \"\";\n }\n\n if (errors.length === 1) {\n const err = errors[0];\n if (!err) return \"\";\n const message = err.messages[0] || \"is invalid\";\n return `${err.field} ${message}`;\n }\n\n if (errors.length <= 3) {\n return errors\n .map((e) => `${e.field} ${e.messages[0] || \"is invalid\"}`)\n .join(\"\\n\");\n }\n\n const shown = errors\n .slice(0, 3)\n .map((e) => `${e.field} ${e.messages[0] || \"is invalid\"}`)\n .join(\"\\n\");\n const remaining = errors.length - 3;\n return `${shown}\\n...and ${remaining} more ${remaining === 1 ? \"error\" : \"errors\"}`;\n}\n\n/**\n * Parses an error and returns a human-readable description string.\n *\n * Handles:\n * - API-like errors with structured field-level data\n * - API-like errors with a top-level message\n * - Standard Error instances\n * - Falls back to the provided fallback string\n *\n * @param error - The error to parse (ApiError, Error, or unknown)\n * @param fallback - Optional fallback description if error cannot be parsed\n * @returns A human-readable error description, or undefined if nothing could be extracted\n */\nexport function parseApiErrors(\n error: unknown,\n fallback?: string,\n): string | undefined {\n if (isApiLikeError(error)) {\n if (error.data) {\n const fieldErrors = extractFieldErrors(error.data);\n if (fieldErrors.length > 0) {\n return formatErrorDescription(fieldErrors);\n }\n }\n\n if (error.message) {\n return error.message;\n }\n } else if (error instanceof Error) {\n return error.message;\n }\n\n return fallback;\n}\n","import { createContext, useContext } from \"react\";\nimport type { ContactsApi } from \"./contacts-api\";\nimport type { NotesApi } from \"./notes-api\";\nimport type { TasksApi } from \"./tasks-api\";\nimport type { GroupsApi } from \"./groups-api\";\n\nexport interface ContactsDomainApi {\n contacts: ContactsApi;\n notes: NotesApi;\n tasks: TasksApi;\n groups?: GroupsApi;\n}\n\nconst ContactsApiContext = createContext<ContactsDomainApi | null>(null);\n\nexport const ContactsApiProvider = ContactsApiContext.Provider;\n\nexport function useContactsDomainApi(): ContactsDomainApi {\n const api = useContext(ContactsApiContext);\n if (!api) {\n throw new Error(\n \"useContactsDomainApi must be used within a ContactsApiProvider\",\n );\n }\n return api;\n}\n\nexport function useContactsCrud(): ContactsApi {\n return useContactsDomainApi().contacts;\n}\n\nexport function useNotesApi(): NotesApi {\n return useContactsDomainApi().notes;\n}\n\nexport function useTasksApi(): TasksApi {\n return useContactsDomainApi().tasks;\n}\n\n/** Returns GroupsApi if the provider supplies one, otherwise null. */\nexport function useGroupsApi(): GroupsApi | null {\n return useContactsDomainApi().groups ?? null;\n}\n","import type { Provider } from \"react\";\nimport { createTranslationContext } from \"@fluid-app/i18n/translation-api-context-factory\";\nimport type { TranslationApi } from \"@fluid-app/i18n/translation-api\";\nimport type { ContactsDict } from \"./translation-dictionary\";\n\nconst { Provider: ContactsProvider, useTranslation } =\n createTranslationContext<ContactsDict>(\"Contacts\");\n\nexport const ContactsTranslationProvider: Provider<TranslationApi<ContactsDict> | null> =\n ContactsProvider;\nexport const useContactsTranslation = useTranslation;\n","/**\n * Tasks store both a title (first line) and an optional body separated by a\n * blank line. This is the canonical convention used by the task editor and\n * by every consumer that displays tasks (contacts UI list, portal todo\n * widget). Drift across consumers would mean some surfaces show \"Title\\n\\n\n * body details\" verbatim while others split correctly — keep the delimiter\n * convention in one place.\n */\nexport function parseTaskBody(raw: string): { title: string; body: string } {\n const split = raw.indexOf(\"\\n\\n\");\n if (split >= 0) {\n return {\n title: raw.slice(0, split),\n body: raw.slice(split + 2),\n };\n }\n return { title: raw, body: \"\" };\n}\n","export const CONTACTS_QUERY_KEYS = {\n all: (prefix: string) => [prefix] as const,\n list: (prefix: string) =>\n [...CONTACTS_QUERY_KEYS.all(prefix), \"list\"] as const,\n detail: (prefix: string, id: string) =>\n [...CONTACTS_QUERY_KEYS.all(prefix), \"detail\", id] as const,\n} as const;\n\nexport const contactsKeys = {\n activities: (contactId: string) =>\n [\"portal-contacts\", \"activities\", contactId] as const,\n tasks: (contactId: string) =>\n [\"portal-contacts\", \"tasks\", contactId] as const,\n notes: (contactId: string) =>\n [\"portal-contacts\", \"notes\", contactId] as const,\n orders: (contactId: string) => [\"rep-contacts\", \"orders\", contactId] as const,\n subscriptionOrders: (contactId: string) =>\n [\"rep-contacts\", \"subscription-orders\", contactId] as const,\n groups: () => [\"portal-contacts\", \"groups\"] as const,\n} as const;\n","import { useInfiniteQuery } from \"@tanstack/react-query\";\nimport { useContactsCrud } from \"../contacts-api-context\";\nimport { CONTACTS_QUERY_KEYS } from \"../query-keys\";\n\nexport interface UseInfiniteContactsParams {\n search_query?: string;\n status?: string;\n sort_by?: string;\n sort_direction?: string;\n per_page?: number;\n tags?: string[];\n}\n\nexport function useInfiniteContacts(params: UseInfiniteContactsParams) {\n const api = useContactsCrud();\n return useInfiniteQuery({\n queryKey: [...CONTACTS_QUERY_KEYS.list(\"contacts\"), params],\n queryFn: ({ pageParam }) =>\n api.listContacts({\n ...params,\n page: pageParam,\n }),\n getNextPageParam: (lastPage) => {\n const currentPage = lastPage.meta.current_page;\n // Contacts API is page-number based; next_cursor and total_pages\n // are both used as \"has-next-page\" signals.\n if (currentPage == null) return undefined;\n if (lastPage.meta.next_cursor) return currentPage + 1;\n if (\n lastPage.meta.total_pages != null &&\n currentPage < lastPage.meta.total_pages\n ) {\n return currentPage + 1;\n }\n return undefined;\n },\n initialPageParam: 1,\n });\n}\n","/**\n * Format a date as YYYY-MM-DD in the renderer's local timezone, optionally\n * shifted by `offsetDays`. Use this for due-date inputs where \"today\" must\n * resolve to the user's local calendar date — never `toISOString().slice(0, 10)`,\n * which returns the UTC date and silently rolls over a day for users east/west\n * of UTC at the wrong hours.\n */\nexport function isoDate(offsetDays: number, now: Date = new Date()): string {\n const d = new Date(now);\n d.setDate(d.getDate() + offsetDays);\n const yyyy = d.getFullYear();\n const mm = String(d.getMonth() + 1).padStart(2, \"0\");\n const dd = String(d.getDate()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}`;\n}\n\n/**\n * Return a Date pinned to local midnight of the calendar day represented by\n * `input`. Use this whenever you need to compare two due dates by calendar day\n * (overdue / today / tomorrow / future).\n *\n * Why not just `new Date(input)`? `new Date(\"YYYY-MM-DD\")` parses the string\n * as UTC midnight, which lands on the *previous* calendar day in any UTC−\n * timezone — a task due \"May 1\" then reads as April 30 for a user in the\n * Americas, classifying it as \"Overdue\" all day. Parse the date components\n * directly so the calendar-day intent of the string is preserved.\n */\nexport function startOfLocalDay(input: Date | string): Date {\n if (typeof input === \"string\") {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(input);\n if (match?.[1] && match[2] && match[3]) {\n return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));\n }\n }\n const d = typeof input === \"string\" ? new Date(input) : input;\n return new Date(d.getFullYear(), d.getMonth(), d.getDate());\n}\n","\"use client\";\n\nimport { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { fluidToast } from \"@fluid-app/ui-primitives\";\nimport { parseApiErrors } from \"@fluid-app/api-client-core\";\nimport { useTasksApi } from \"@fluid-app/contacts-core/contacts-api-context\";\nimport { contactsKeys } from \"@fluid-app/contacts-core/query-keys\";\nimport type { CreateTaskInput } from \"@fluid-app/contacts-core/types\";\n\nexport type { CreateTaskInput };\n\nexport function useCreateContactTask(\n contactId: string,\n options?: { onSuccess?: () => void },\n) {\n const queryClient = useQueryClient();\n const api = useTasksApi();\n\n return useMutation({\n mutationFn: (input: CreateTaskInput) => api.createTask(contactId, input),\n onSuccess: () => {\n fluidToast({ title: \"Task created\", type: \"success\" });\n queryClient.invalidateQueries({\n queryKey: contactsKeys.tasks(contactId),\n });\n options?.onSuccess?.();\n },\n onError: (error) => {\n const description = parseApiErrors(error);\n fluidToast({\n title: \"Failed to create task\",\n type: \"error\",\n ...(description ? { description } : {}),\n });\n },\n });\n}\n","\"use client\";\n\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@fluid-app/ui-primitives\";\nimport { useContactsTranslation } from \"@fluid-app/contacts-core/translation-api-context\";\nimport { isoDate } from \"@fluid-app/contacts-core/iso-date\";\nimport { useCreateContactTask } from \"../../hooks/contacts/use-create-contact-task\";\n\nconst QUICK_DATES = [\n { key: \"today\", offsetDays: 0 },\n { key: \"tomorrow\", offsetDays: 1 },\n { key: \"next_week\", offsetDays: 7 },\n] as const;\n\nexport interface TaskComposerFormProps {\n contactId: string;\n /** Called after a successful create. */\n onDone?: () => void;\n /** When provided, an X button is rendered and Escape closes the form. */\n onClose?: () => void;\n /** Focus the body input on mount. Default true. */\n autoFocus?: boolean;\n}\n\nexport function TaskComposerForm({\n contactId,\n onDone,\n onClose,\n autoFocus = true,\n}: TaskComposerFormProps): React.JSX.Element {\n const { t } = useContactsTranslation();\n const [body, setBody] = useState(\"\");\n const [dueDate, setDueDate] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const createTask = useCreateContactTask(contactId, {\n onSuccess: () => {\n setBody(\"\");\n setDueDate(null);\n onDone?.();\n },\n });\n\n useEffect(() => {\n if (autoFocus) inputRef.current?.focus();\n }, [autoFocus]);\n\n // Compute the date strings once per mount. Re-running on every render would\n // re-evaluate `new Date()` and could flip \"today\" if the form happens to be\n // open across midnight — acceptable but unnecessary churn.\n const quickDates = useMemo(\n () =>\n QUICK_DATES.map((q) => ({\n key: q.key,\n iso: isoDate(q.offsetDays),\n })),\n [],\n );\n\n const quickLabels: Record<(typeof QUICK_DATES)[number][\"key\"], string> = {\n today: t(\"quick_today\"),\n tomorrow: t(\"quick_tomorrow\"),\n next_week: t(\"quick_next_week\"),\n };\n\n const submit = () => {\n const trimmed = body.trim();\n if (!trimmed || createTask.isPending) return;\n createTask.mutate({ body: trimmed, due_at: dueDate });\n };\n\n const canSubmit = body.trim().length > 0 && !createTask.isPending;\n\n return (\n <div className=\"border-border/50 focus-within:border-foreground/30 rounded-xl border p-3 transition-colors\">\n <div className=\"flex items-start gap-3\">\n <div\n className=\"border-muted-foreground/50 mt-1.5 size-5 shrink-0 rounded-full border-2\"\n aria-hidden=\"true\"\n />\n <input\n ref={inputRef}\n value={body}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n } else if (e.key === \"Escape\" && onClose) {\n e.preventDefault();\n onClose();\n }\n }}\n placeholder={t(\"task_describe_placeholder\")}\n aria-label={t(\"task_description_aria\")}\n className=\"placeholder:text-muted-foreground/80 text-foreground flex-1 border-0 bg-transparent text-sm font-medium outline-none\"\n />\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={t(\"task_discard_aria\")}\n className=\"text-muted-foreground hover:bg-muted hover:text-foreground -mt-0.5 -mr-0.5 flex size-7 shrink-0 items-center justify-center rounded-md transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n )}\n </div>\n\n {/* --row-indent aligns the chip row with the input text above (size-5\n circle + gap-3 = 2rem). The mobile Add button cancels it via\n -ml-(--row-indent) + w-[calc(100%+var(--row-indent))] so the two\n values stay locked to a single source. */}\n <div className=\"mt-3 flex flex-wrap items-center gap-1.5 pl-(--row-indent) [--row-indent:2rem]\">\n {quickDates.map((q) => {\n const isActive = dueDate === q.iso;\n return (\n <button\n key={q.key}\n type=\"button\"\n onClick={() => setDueDate(isActive ? null : q.iso)}\n aria-pressed={isActive}\n className={cn(\n \"shrink-0 rounded-full px-3 py-1 text-xs font-medium transition-colors\",\n isActive\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground hover:bg-muted/70\",\n )}\n >\n {quickLabels[q.key]}\n </button>\n );\n })}\n <button\n type=\"button\"\n onClick={submit}\n disabled={!canSubmit}\n className=\"bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground mt-1 -ml-(--row-indent) inline-flex h-10 w-[calc(100%+var(--row-indent))] items-center justify-center rounded-lg text-sm font-semibold transition-colors disabled:cursor-not-allowed sm:mt-0 sm:ml-auto sm:h-auto sm:w-auto sm:shrink-0 sm:gap-1.5 sm:rounded-full sm:px-4 sm:py-1.5 sm:text-xs\"\n >\n {createTask.isPending ? t(\"adding\") : t(\"add_task\")}\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"task-composer-form-D_Pbl6qk.mjs","names":[],"sources":["../../../platform/api-client-core/src/parse-api-errors.ts","../../../contacts/core/src/contacts-api-context.ts","../../../contacts/core/src/translation-api-context.ts","../../../contacts/core/src/parse-task-body.ts","../../../contacts/core/src/query-keys.ts","../../../contacts/core/src/hooks/use-infinite-contacts.ts","../../../contacts/core/src/iso-date.ts","../../../contacts/ui/src/portal/hooks/contacts/use-create-contact-task.ts","../../../contacts/ui/src/portal/components/tasks/task-composer-form.tsx"],"sourcesContent":["/**\n * Framework-agnostic API error parsing utilities.\n *\n * Extracts structured field-level errors from API responses and formats\n * them into human-readable messages. Works with ApiError from this package\n * as well as any error object that has `status`, `data`, and optional `message`.\n */\n\n/**\n * Converts snake_case or camelCase field names to Title Case\n */\nexport function formatFieldName(field: string): string {\n return field\n .replace(/_/g, \" \")\n .replace(/([A-Z])/g, \" $1\")\n .split(\" \")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\" \")\n .trim();\n}\n\nexport interface ParsedFieldError {\n field: string;\n messages: string[];\n}\n\n/**\n * Type guard to check if an error looks like an API error\n */\nexport function isApiLikeError(\n error: unknown,\n): error is { message: string | undefined; status: number; data: unknown } {\n if (!error || typeof error !== \"object\") {\n return false;\n }\n\n const err = error as Record<string, unknown>;\n return (\n typeof err.status === \"number\" &&\n \"data\" in err &&\n (typeof err.message === \"string\" || err.message === undefined)\n );\n}\n\n/**\n * Extracts field-level errors from API error data\n */\nexport function extractFieldErrors(data: unknown): ParsedFieldError[] {\n const errors: ParsedFieldError[] = [];\n\n if (!data || typeof data !== \"object\") {\n return errors;\n }\n\n const errorObj = data as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(errorObj)) {\n if (Array.isArray(value) && value.length > 0) {\n errors.push({\n field: formatFieldName(key),\n messages: value.map((item) =>\n typeof item === \"string\" ? item : JSON.stringify(item),\n ),\n });\n } else if (typeof value === \"string\" && value.length > 0) {\n errors.push({\n field: formatFieldName(key),\n messages: [value],\n });\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const nestedErrors = extractFieldErrors(value);\n nestedErrors.forEach((nestedError) => {\n errors.push({\n field: `${formatFieldName(key)} → ${nestedError.field}`,\n messages: nestedError.messages,\n });\n });\n }\n }\n\n return errors;\n}\n\n/**\n * Formats field errors into a readable description string\n */\nexport function formatErrorDescription(errors: ParsedFieldError[]): string {\n if (errors.length === 0) {\n return \"\";\n }\n\n if (errors.length === 1) {\n const err = errors[0];\n if (!err) return \"\";\n const message = err.messages[0] || \"is invalid\";\n return `${err.field} ${message}`;\n }\n\n if (errors.length <= 3) {\n return errors\n .map((e) => `${e.field} ${e.messages[0] || \"is invalid\"}`)\n .join(\"\\n\");\n }\n\n const shown = errors\n .slice(0, 3)\n .map((e) => `${e.field} ${e.messages[0] || \"is invalid\"}`)\n .join(\"\\n\");\n const remaining = errors.length - 3;\n return `${shown}\\n...and ${remaining} more ${remaining === 1 ? \"error\" : \"errors\"}`;\n}\n\n/**\n * Parses an error and returns a human-readable description string.\n *\n * Handles:\n * - API-like errors with structured field-level data\n * - API-like errors with a top-level message\n * - Standard Error instances\n * - Falls back to the provided fallback string\n *\n * @param error - The error to parse (ApiError, Error, or unknown)\n * @param fallback - Optional fallback description if error cannot be parsed\n * @returns A human-readable error description, or undefined if nothing could be extracted\n */\nexport function parseApiErrors(\n error: unknown,\n fallback?: string,\n): string | undefined {\n if (isApiLikeError(error)) {\n if (error.data) {\n const fieldErrors = extractFieldErrors(error.data);\n if (fieldErrors.length > 0) {\n return formatErrorDescription(fieldErrors);\n }\n }\n\n if (error.message) {\n return error.message;\n }\n } else if (error instanceof Error) {\n return error.message;\n }\n\n return fallback;\n}\n","import { createContext, useContext } from \"react\";\nimport type { ContactsApi } from \"./contacts-api\";\nimport type { NotesApi } from \"./notes-api\";\nimport type { TasksApi } from \"./tasks-api\";\nimport type { GroupsApi } from \"./groups-api\";\n\nexport interface ContactsDomainApi {\n contacts: ContactsApi;\n notes: NotesApi;\n tasks: TasksApi;\n groups?: GroupsApi;\n}\n\nconst ContactsApiContext = createContext<ContactsDomainApi | null>(null);\n\nexport const ContactsApiProvider = ContactsApiContext.Provider;\n\nexport function useContactsDomainApi(): ContactsDomainApi {\n const api = useContext(ContactsApiContext);\n if (!api) {\n throw new Error(\n \"useContactsDomainApi must be used within a ContactsApiProvider\",\n );\n }\n return api;\n}\n\nexport function useContactsCrud(): ContactsApi {\n return useContactsDomainApi().contacts;\n}\n\nexport function useNotesApi(): NotesApi {\n return useContactsDomainApi().notes;\n}\n\nexport function useTasksApi(): TasksApi {\n return useContactsDomainApi().tasks;\n}\n\n/** Returns GroupsApi if the provider supplies one, otherwise null. */\nexport function useGroupsApi(): GroupsApi | null {\n return useContactsDomainApi().groups ?? null;\n}\n","import type { Provider } from \"react\";\nimport { createTranslationContext } from \"@fluid-app/i18n/translation-api-context-factory\";\nimport type { TranslationApi } from \"@fluid-app/i18n/translation-api\";\nimport type { ContactsDict } from \"./translation-dictionary\";\n\nconst { Provider: ContactsProvider, useTranslation } =\n createTranslationContext<ContactsDict>(\"Contacts\");\n\nexport const ContactsTranslationProvider: Provider<TranslationApi<ContactsDict> | null> =\n ContactsProvider;\nexport const useContactsTranslation = useTranslation;\n","/**\n * Tasks store both a title (first line) and an optional body separated by a\n * blank line. This is the canonical convention used by the task editor and\n * by every consumer that displays tasks (contacts UI list, portal todo\n * widget). Drift across consumers would mean some surfaces show \"Title\\n\\n\n * body details\" verbatim while others split correctly — keep the delimiter\n * convention in one place.\n */\nexport function parseTaskBody(raw: string): { title: string; body: string } {\n const split = raw.indexOf(\"\\n\\n\");\n if (split >= 0) {\n return {\n title: raw.slice(0, split),\n body: raw.slice(split + 2),\n };\n }\n return { title: raw, body: \"\" };\n}\n","export const CONTACTS_QUERY_KEYS = {\n all: (prefix: string) => [prefix] as const,\n list: (prefix: string) =>\n [...CONTACTS_QUERY_KEYS.all(prefix), \"list\"] as const,\n detail: (prefix: string, id: string) =>\n [...CONTACTS_QUERY_KEYS.all(prefix), \"detail\", id] as const,\n} as const;\n\nexport const contactsKeys = {\n activities: (contactId: string) =>\n [\"portal-contacts\", \"activities\", contactId] as const,\n tasks: (contactId: string) =>\n [\"portal-contacts\", \"tasks\", contactId] as const,\n notes: (contactId: string) =>\n [\"portal-contacts\", \"notes\", contactId] as const,\n orders: (contactId: string) => [\"rep-contacts\", \"orders\", contactId] as const,\n subscriptionOrders: (contactId: string) =>\n [\"rep-contacts\", \"subscription-orders\", contactId] as const,\n groups: () => [\"portal-contacts\", \"groups\"] as const,\n} as const;\n","import { useInfiniteQuery } from \"@tanstack/react-query\";\nimport { useContactsCrud } from \"../contacts-api-context\";\nimport { CONTACTS_QUERY_KEYS } from \"../query-keys\";\n\nexport interface UseInfiniteContactsParams {\n search_query?: string;\n status?: string;\n sort_by?: string;\n sort_direction?: string;\n per_page?: number;\n tags?: string[];\n}\n\nexport function useInfiniteContacts(params: UseInfiniteContactsParams) {\n const api = useContactsCrud();\n return useInfiniteQuery({\n queryKey: [...CONTACTS_QUERY_KEYS.list(\"contacts\"), params],\n queryFn: ({ pageParam }) =>\n api.listContacts({\n ...params,\n page: pageParam,\n }),\n getNextPageParam: (lastPage) => {\n const currentPage = lastPage.meta.current_page;\n // Contacts API is page-number based; next_cursor and total_pages\n // are both used as \"has-next-page\" signals.\n if (currentPage == null) return undefined;\n if (lastPage.meta.next_cursor) return currentPage + 1;\n if (\n lastPage.meta.total_pages != null &&\n currentPage < lastPage.meta.total_pages\n ) {\n return currentPage + 1;\n }\n return undefined;\n },\n initialPageParam: 1,\n });\n}\n","/**\n * Format a date as YYYY-MM-DD in the renderer's local timezone, optionally\n * shifted by `offsetDays`. Use this for due-date inputs where \"today\" must\n * resolve to the user's local calendar date — never `toISOString().slice(0, 10)`,\n * which returns the UTC date and silently rolls over a day for users east/west\n * of UTC at the wrong hours.\n */\nexport function isoDate(offsetDays: number, now: Date = new Date()): string {\n const d = new Date(now);\n d.setDate(d.getDate() + offsetDays);\n const yyyy = d.getFullYear();\n const mm = String(d.getMonth() + 1).padStart(2, \"0\");\n const dd = String(d.getDate()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}`;\n}\n\n/**\n * Return a Date pinned to local midnight of the calendar day represented by\n * `input`. Use this whenever you need to compare two due dates by calendar day\n * (overdue / today / tomorrow / future).\n *\n * Why not just `new Date(input)`? `new Date(\"YYYY-MM-DD\")` parses the string\n * as UTC midnight, which lands on the *previous* calendar day in any UTC−\n * timezone — a task due \"May 1\" then reads as April 30 for a user in the\n * Americas, classifying it as \"Overdue\" all day. Parse the date components\n * directly so the calendar-day intent of the string is preserved.\n */\nexport function startOfLocalDay(input: Date | string): Date {\n if (typeof input === \"string\") {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(input);\n if (match?.[1] && match[2] && match[3]) {\n return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));\n }\n }\n const d = typeof input === \"string\" ? new Date(input) : input;\n return new Date(d.getFullYear(), d.getMonth(), d.getDate());\n}\n","\"use client\";\n\nimport { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { fluidToast } from \"@fluid-app/ui-primitives\";\nimport { parseApiErrors } from \"@fluid-app/api-client-core\";\nimport { useTasksApi } from \"@fluid-app/contacts-core/contacts-api-context\";\nimport { contactsKeys } from \"@fluid-app/contacts-core/query-keys\";\nimport type { CreateTaskInput } from \"@fluid-app/contacts-core/types\";\n\nexport type { CreateTaskInput };\n\nexport function useCreateContactTask(\n contactId: string,\n options?: { onSuccess?: () => void },\n) {\n const queryClient = useQueryClient();\n const api = useTasksApi();\n\n return useMutation({\n mutationFn: (input: CreateTaskInput) => api.createTask(contactId, input),\n onSuccess: () => {\n fluidToast({ title: \"Task created\", type: \"success\" });\n queryClient.invalidateQueries({\n queryKey: contactsKeys.tasks(contactId),\n });\n options?.onSuccess?.();\n },\n onError: (error) => {\n const description = parseApiErrors(error);\n fluidToast({\n title: \"Failed to create task\",\n type: \"error\",\n ...(description ? { description } : {}),\n });\n },\n });\n}\n","\"use client\";\n\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@fluid-app/ui-primitives\";\nimport { useContactsTranslation } from \"@fluid-app/contacts-core/translation-api-context\";\nimport { isoDate } from \"@fluid-app/contacts-core/iso-date\";\nimport { useCreateContactTask } from \"../../hooks/contacts/use-create-contact-task\";\n\nconst QUICK_DATES = [\n { key: \"today\", offsetDays: 0 },\n { key: \"tomorrow\", offsetDays: 1 },\n { key: \"next_week\", offsetDays: 7 },\n] as const;\n\nexport interface TaskComposerFormProps {\n contactId: string;\n /** Called after a successful create. */\n onDone?: () => void;\n /** When provided, an X button is rendered and Escape closes the form. */\n onClose?: () => void;\n /** Focus the body input on mount. Default true. */\n autoFocus?: boolean;\n}\n\nexport function TaskComposerForm({\n contactId,\n onDone,\n onClose,\n autoFocus = true,\n}: TaskComposerFormProps): React.JSX.Element {\n const { t } = useContactsTranslation();\n const [body, setBody] = useState(\"\");\n const [dueDate, setDueDate] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const createTask = useCreateContactTask(contactId, {\n onSuccess: () => {\n setBody(\"\");\n setDueDate(null);\n onDone?.();\n },\n });\n\n useEffect(() => {\n if (autoFocus) inputRef.current?.focus();\n }, [autoFocus]);\n\n // Compute the date strings once per mount. Re-running on every render would\n // re-evaluate `new Date()` and could flip \"today\" if the form happens to be\n // open across midnight — acceptable but unnecessary churn.\n const quickDates = useMemo(\n () =>\n QUICK_DATES.map((q) => ({\n key: q.key,\n iso: isoDate(q.offsetDays),\n })),\n [],\n );\n\n const quickLabels: Record<(typeof QUICK_DATES)[number][\"key\"], string> = {\n today: t(\"quick_today\"),\n tomorrow: t(\"quick_tomorrow\"),\n next_week: t(\"quick_next_week\"),\n };\n\n const submit = () => {\n const trimmed = body.trim();\n if (!trimmed || createTask.isPending) return;\n createTask.mutate({ body: trimmed, due_at: dueDate });\n };\n\n const canSubmit = body.trim().length > 0 && !createTask.isPending;\n\n return (\n <div className=\"border-border/50 focus-within:border-foreground/30 rounded-xl border p-3 transition-colors\">\n <div className=\"flex items-start gap-3\">\n <div\n className=\"border-muted-foreground/50 mt-1.5 size-5 shrink-0 rounded-full border-2\"\n aria-hidden=\"true\"\n />\n <input\n ref={inputRef}\n value={body}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n } else if (e.key === \"Escape\" && onClose) {\n e.preventDefault();\n onClose();\n }\n }}\n placeholder={t(\"task_describe_placeholder\")}\n aria-label={t(\"task_description_aria\")}\n className=\"placeholder:text-muted-foreground/80 text-foreground flex-1 border-0 bg-transparent text-sm font-medium outline-none pointer-coarse:text-base\"\n />\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={t(\"task_discard_aria\")}\n className=\"text-muted-foreground hover:bg-muted hover:text-foreground -mt-0.5 -mr-0.5 flex size-7 shrink-0 items-center justify-center rounded-md transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n )}\n </div>\n\n {/* --row-indent aligns the chip row with the input text above (size-5\n circle + gap-3 = 2rem). The mobile Add button cancels it via\n -ml-(--row-indent) + w-[calc(100%+var(--row-indent))] so the two\n values stay locked to a single source. */}\n <div className=\"mt-3 flex flex-wrap items-center gap-1.5 pl-(--row-indent) [--row-indent:2rem]\">\n {quickDates.map((q) => {\n const isActive = dueDate === q.iso;\n return (\n <button\n key={q.key}\n type=\"button\"\n onClick={() => setDueDate(isActive ? null : q.iso)}\n aria-pressed={isActive}\n className={cn(\n \"shrink-0 rounded-full px-3 py-1 text-xs font-medium transition-colors\",\n isActive\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground hover:bg-muted/70\",\n )}\n >\n {quickLabels[q.key]}\n </button>\n );\n })}\n <button\n type=\"button\"\n onClick={submit}\n disabled={!canSubmit}\n className=\"bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground mt-1 -ml-(--row-indent) inline-flex h-10 w-[calc(100%+var(--row-indent))] items-center justify-center rounded-lg text-sm font-semibold transition-colors disabled:cursor-not-allowed sm:mt-0 sm:ml-auto sm:h-auto sm:w-auto sm:shrink-0 sm:gap-1.5 sm:rounded-full sm:px-4 sm:py-1.5 sm:text-xs\"\n >\n {createTask.isPending ? t(\"adding\") : t(\"add_task\")}\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluid-app/portal-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.341",
|
|
4
4
|
"description": "SDK for building custom Fluid portals",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -73,53 +73,53 @@
|
|
|
73
73
|
"zod": "4.3.5",
|
|
74
74
|
"@fluid-app/address-autocomplete": "0.1.0",
|
|
75
75
|
"@fluid-app/api-client-core": "0.1.0",
|
|
76
|
-
"@fluid-app/cart-ui": "0.1.22",
|
|
77
|
-
"@fluid-app/auth": "0.1.0",
|
|
78
76
|
"@fluid-app/company-switcher-core": "0.1.0",
|
|
77
|
+
"@fluid-app/cart-ui": "0.1.23",
|
|
78
|
+
"@fluid-app/auth": "0.1.0",
|
|
79
79
|
"@fluid-app/company-switcher-ui": "0.1.0",
|
|
80
|
-
"@fluid-app/contacts-core": "0.1.0",
|
|
81
80
|
"@fluid-app/contacts-ui": "0.1.0",
|
|
81
|
+
"@fluid-app/contacts-core": "0.1.0",
|
|
82
82
|
"@fluid-app/file-picker-api-client": "0.1.0",
|
|
83
83
|
"@fluid-app/file-picker-core": "0.1.0",
|
|
84
|
-
"@fluid-app/fluid-pay-ui": "0.1.0",
|
|
85
84
|
"@fluid-app/fluid-pay-core": "0.1.0",
|
|
85
|
+
"@fluid-app/fluid-pay-ui": "0.1.0",
|
|
86
86
|
"@fluid-app/fluidos-api-client": "0.1.0",
|
|
87
87
|
"@fluid-app/messaging-api-client": "0.1.0",
|
|
88
|
-
"@fluid-app/i18n": "0.1.0",
|
|
89
88
|
"@fluid-app/messaging-core": "0.1.0",
|
|
90
89
|
"@fluid-app/messaging-ui": "0.1.0",
|
|
90
|
+
"@fluid-app/i18n": "0.1.0",
|
|
91
91
|
"@fluid-app/mysite-core": "0.1.0",
|
|
92
92
|
"@fluid-app/mysite-ui": "0.1.0",
|
|
93
93
|
"@fluid-app/orders-core": "0.1.0",
|
|
94
94
|
"@fluid-app/orders-ui": "0.1.0",
|
|
95
|
-
"@fluid-app/portal-app-download-ui": "0.1.0",
|
|
96
95
|
"@fluid-app/permissions": "0.1.0",
|
|
97
96
|
"@fluid-app/portal-core": "0.1.23",
|
|
98
97
|
"@fluid-app/portal-preview": "0.1.0",
|
|
99
|
-
"@fluid-app/portal-
|
|
98
|
+
"@fluid-app/portal-app-download-ui": "0.1.0",
|
|
100
99
|
"@fluid-app/portal-pro-upgrade-ui": "0.1.0",
|
|
101
|
-
"@fluid-app/portal-
|
|
100
|
+
"@fluid-app/portal-react": "0.1.0",
|
|
102
101
|
"@fluid-app/portal-tenant-api-client": "0.1.0",
|
|
102
|
+
"@fluid-app/portal-tenant-contacts-api-client": "0.1.0",
|
|
103
103
|
"@fluid-app/portal-tenant-content-api-client": "0.1.0",
|
|
104
104
|
"@fluid-app/portal-tenant-mysite-api-client": "0.1.0",
|
|
105
105
|
"@fluid-app/portal-tenant-pay-api-client": "0.1.0",
|
|
106
106
|
"@fluid-app/portal-tenant-store-api-client": "0.1.0",
|
|
107
|
-
"@fluid-app/portal-widgets": "0.1.22",
|
|
108
107
|
"@fluid-app/products-api-client": "0.1.0",
|
|
109
108
|
"@fluid-app/products-core": "0.1.0",
|
|
110
109
|
"@fluid-app/profile-core": "0.1.0",
|
|
111
|
-
"@fluid-app/query-persister": "0.1.0",
|
|
112
110
|
"@fluid-app/profile-ui": "0.1.0",
|
|
111
|
+
"@fluid-app/portal-widgets": "0.1.22",
|
|
113
112
|
"@fluid-app/shareables-core": "0.1.0",
|
|
113
|
+
"@fluid-app/query-persister": "0.1.0",
|
|
114
114
|
"@fluid-app/shareables-ui": "0.1.0",
|
|
115
|
+
"@fluid-app/shop-core": "0.1.0",
|
|
115
116
|
"@fluid-app/shop-ui": "0.1.0",
|
|
116
117
|
"@fluid-app/store-api-client": "0.1.0",
|
|
117
|
-
"@fluid-app/store-core": "0.1.0",
|
|
118
|
-
"@fluid-app/shop-core": "0.1.0",
|
|
119
118
|
"@fluid-app/subscriptions-core": "0.1.0",
|
|
120
119
|
"@fluid-app/subscriptions-ui": "0.1.0",
|
|
121
|
-
"@fluid-app/
|
|
122
|
-
"@fluid-app/ui-primitives": "0.1.13"
|
|
120
|
+
"@fluid-app/store-core": "0.1.0",
|
|
121
|
+
"@fluid-app/ui-primitives": "0.1.13",
|
|
122
|
+
"@fluid-app/typescript-config": "0.0.0"
|
|
123
123
|
},
|
|
124
124
|
"peerDependencies": {
|
|
125
125
|
"@hookform/resolvers": "^5.2.2",
|