@confidencesystemsinc/sdk 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/components/playbook/confidence-playbook.d.ts +12 -5
  2. package/dist/components/playbook/playbook-header.d.ts +3 -1
  3. package/dist/components/playbook-button/ConfidencePlaybookButton.d.ts +2 -1
  4. package/dist/components/task/confidence-task.d.ts +10 -3
  5. package/dist/components/task/task-buttons.d.ts +2 -1
  6. package/dist/components/task/task-expanded-content.d.ts +3 -0
  7. package/dist/components/ui/button.d.ts +1 -0
  8. package/dist/confidence_logo.png +0 -0
  9. package/dist/constants/settings.constants.d.ts +2 -2
  10. package/dist/context/confidence-context.d.ts +10 -0
  11. package/dist/hooks/task/useTaskDetails.d.ts +173 -0
  12. package/dist/hooks/usePlaybookExpandedTasks.d.ts +4 -0
  13. package/dist/hooks/useTaskButtons.d.ts +2 -1
  14. package/dist/index.cjs +15 -15
  15. package/dist/index.js +3596 -3336
  16. package/dist/services/task-details.service.d.ts +4 -0
  17. package/dist/stories/confidence-task.stories.d.ts +50 -0
  18. package/dist/theme.css +1 -1
  19. package/package.json +3 -2
  20. package/src/components/badge.tsx +116 -0
  21. package/src/components/initiate-playbook-modal/InitiatePlaybookModal.tsx +53 -0
  22. package/src/components/playbook/confidence-playbook.tsx +309 -0
  23. package/src/components/playbook/playbook-header.tsx +34 -0
  24. package/src/components/playbook-button/ConfidencePlaybookButton.tsx +79 -0
  25. package/src/components/task/confidence-task.tsx +297 -0
  26. package/src/components/task/task-buttons.tsx +35 -0
  27. package/src/components/task/task-dropdown-badge.tsx +121 -0
  28. package/src/components/task/task-expanded-content.tsx +46 -0
  29. package/src/components/task/task-left-panel.tsx +60 -0
  30. package/src/components/task/task-status-badge.tsx +23 -0
  31. package/src/components/ui/button.tsx +272 -0
  32. package/src/components/ui/header.tsx +12 -0
  33. package/src/components/ui/input.tsx +39 -0
  34. package/src/components/ui/modal.tsx +88 -0
  35. package/src/components/ui/ui-wrapper.tsx +7 -0
  36. package/src/constants/settings.constants.ts +4 -0
  37. package/src/context/confidence-context.tsx +25 -0
  38. package/src/hooks/task/useCompleteTask.ts +32 -0
  39. package/src/hooks/task/useStartTask.ts +35 -0
  40. package/src/hooks/task/useTaskDetails.ts +42 -0
  41. package/src/hooks/usePlaybook.ts +54 -0
  42. package/src/hooks/usePlaybookActions.ts +69 -0
  43. package/src/hooks/usePlaybookExpandedTasks.ts +35 -0
  44. package/src/hooks/useTaskButtons.ts +47 -0
  45. package/src/index.ts +7 -0
  46. package/src/services/complete-task.service.ts +25 -0
  47. package/src/services/initiate-playbook.service.ts +26 -0
  48. package/src/services/start-task.services.ts +27 -0
  49. package/src/services/task-details.service.ts +17 -0
  50. package/src/stories/confidence-playbook.stories.tsx +124 -0
  51. package/src/stories/confidence-task.stories.tsx +63 -0
  52. package/src/stories/initiate-playbook-modal.stories.tsx +31 -0
  53. package/src/stories/modal.stories.tsx +50 -0
  54. package/src/task-description.css +629 -0
  55. package/src/theme.css +11 -0
  56. package/src/types/playbook.types.ts +22 -0
  57. package/src/types/task.types.ts +20 -0
  58. package/src/utils/cn.ts +6 -0
  59. package/src/vite-env.d.ts +1 -0
  60. /package/dist/hooks/{task-events → task}/useCompleteTask.d.ts +0 -0
  61. /package/dist/hooks/{task-events → task}/useStartTask.d.ts +0 -0
@@ -0,0 +1,272 @@
1
+ import {
2
+ ReactNode,
3
+ ButtonHTMLAttributes,
4
+ CSSProperties,
5
+ forwardRef,
6
+ } from "react";
7
+ import { twMerge } from "tailwind-merge";
8
+ /*
9
+ DOCS: https://tailwindcss.com/docs/content-configuration#class-detection-in-depth
10
+ */
11
+
12
+ const ButtonBase =
13
+ "whitespace-nowrap font-normal flex items-center justify-center";
14
+ const ButtonFocus =
15
+ "focus:outline focus:outline-3 focus:outline-primary-500/[.3]";
16
+ const ButtonDisabled = "disabled:opacity-15 disabled:pointer-events-none";
17
+
18
+ const isLight = (color: string) => {
19
+ return Boolean(color === "grayLight" || color === "warning");
20
+ };
21
+
22
+ const handleNoneColor = (fn: () => string, color?: string) => {
23
+ if (!color || color === "none") {
24
+ return "";
25
+ }
26
+ return fn();
27
+ };
28
+
29
+ const buttonColorBase = (color: string, active: boolean) =>
30
+ handleNoneColor(() => {
31
+ const useColor = color === "gray400" ? "gray" : color;
32
+ const textColor = isLight(useColor)
33
+ ? "active:text-black"
34
+ : "active:text-white";
35
+ if (color === "primary") {
36
+ return active
37
+ ? `bg-${useColor}-800 text-white`
38
+ : `hover:bg-${useColor}-800 active:bg-${useColor}-800 hover:text-white ${textColor}`;
39
+ } else if (useColor === "grayDark") {
40
+ return active
41
+ ? "bg-gray-800 text-white"
42
+ : `hover:bg-gray-800 active:bg-gray-800 hover:text-white ${textColor}`;
43
+ } else if (useColor === "grayLight") {
44
+ return active
45
+ ? "bg-gray-50 text-black"
46
+ : `hover:bg-gray-50 active:bg-gray-50 hover:text-black ${textColor}`;
47
+ } else if (color === "gray400") {
48
+ return active
49
+ ? `bg-${useColor}-500 text-white`
50
+ : `hover:bg-${useColor}-500 active:bg-${useColor}-500 hover:text-white ${textColor}`;
51
+ }
52
+ return active
53
+ ? `bg-${useColor}-700 text-white`
54
+ : `hover:bg-${useColor}-600 active:bg-${useColor}-700 hover:text-white ${textColor}`;
55
+ }, color);
56
+
57
+ const buttonColorSolid = (color: string) =>
58
+ handleNoneColor(() => {
59
+ const textColor = isLight(color) ? "text-black" : "text-white";
60
+ if (color === "primary") {
61
+ return `bg-primary-600 ${textColor}`;
62
+ } else if (color === "gray400") {
63
+ return `bg-gray-400 ${textColor}`;
64
+ }
65
+ return `bg-${color}-500 ${textColor}`;
66
+ }, color);
67
+
68
+ const buttonColorLink = (color: string) => {
69
+ const classes: Record<string, string> = {
70
+ dark: "text-dark hover:text-dark",
71
+ yellow: "text-yellow hover:text-yellow",
72
+ gray: "text-gray-500 hover:text-gray-800",
73
+ primary: "text-primary-600 hover:text-primary-800",
74
+ secondary: "text-secondary-500 hover:text-secondary-800",
75
+ success: "text-success-500 hover:text-success-800",
76
+ info: "text-info-500 hover:text-info-800",
77
+ warning: "text-warning-500 hover:text-warning-800",
78
+ danger: "text-danger-500 hover:text-danger-800",
79
+ steel: "text-steel-500 hover:text-steel-800",
80
+ orange: "text-orange-500 hover:text-orange-800",
81
+ purple: "text-purple-500 hover:text-purple-800",
82
+ "fiserv-orange": "text-fiserv-orange-500 hover:text-fiserv-orange-800",
83
+ grayDark: "text-gray-800 hover:text-gray-900",
84
+ grayLight: "text-gray-50 hover:text-gray-400",
85
+ gray400: "text-gray-400 hover:text-gray-700",
86
+ };
87
+ return (color && classes[color]) || "";
88
+ };
89
+
90
+ const buttonColorLinkHoverSolid = (color: string, active: boolean) => {
91
+ const classes: Record<string, string> = {
92
+ dark: active
93
+ ? "bg-dark text-white"
94
+ : "text-dark hover:bg-dark hover:text-white",
95
+ yellow: active
96
+ ? "bg-yellow text-white"
97
+ : "text-yellow hover:bg-yellow hover:text-white",
98
+ gray: active
99
+ ? "bg-gray-500 text-white"
100
+ : "text-gray-500 hover:bg-gray-500 hover:text-white",
101
+ primary: active
102
+ ? "bg-primary-800 text-white"
103
+ : "text-primary-600 hover:bg-primary-800 hover:text-white",
104
+ secondary: active
105
+ ? "bg-secondary-500 text-white"
106
+ : "text-secondary-500 hover:bg-secondary-500 hover:text-white",
107
+ success: active
108
+ ? "bg-success-500 text-white"
109
+ : "text-success-500 hover:bg-success-500 hover:text-white",
110
+ info: active
111
+ ? "bg-info-500 text-white"
112
+ : "text-info-500 hover:bg-info-500 hover:text-white",
113
+ warning: active
114
+ ? "bg-warning-500 text-white"
115
+ : "text-warning-500 hover:bg-warning-500 hover:text-white",
116
+ danger: active
117
+ ? "bg-danger-500 text-white"
118
+ : "text-danger-500 hover:bg-danger-500 hover:text-white",
119
+ steel: active
120
+ ? "bg-steel-500 text-white"
121
+ : "text-steel-500 hover:bg-steel-500 hover:text-white",
122
+ orange: active
123
+ ? "bg-orange-500 text-white"
124
+ : "text-orange-500 hover:bg-orange-500 hover:text-white",
125
+ purple: active
126
+ ? "bg-purple-500 text-white"
127
+ : "text-purple-500 hover:bg-purple-500 hover:text-white",
128
+ "fiserv-orange": active
129
+ ? "bg-fiserv-orange-500 text-white"
130
+ : "text-fiserv-orange-500 hover:bg-fiserv-orange-500 hover:text-white",
131
+ grayDark: active
132
+ ? "bg-gray-900 text-white"
133
+ : "text-gray-800 hover:bg-gray-900 hover:text-white",
134
+ grayLight: active
135
+ ? "bg-gray-400 text-white"
136
+ : "text-gray-50 hover:bg-gray-400 hover:text-white",
137
+ gray400: active
138
+ ? "bg-gray-500 text-white"
139
+ : "text-gray-400 hover:bg-gray-500 hover:text-white",
140
+ };
141
+ return (color && classes[color]) || "";
142
+ };
143
+
144
+ const buttonColorOutline = (color: string, active: boolean) => {
145
+ if (!color || color === "none") {
146
+ return "";
147
+ } else if (color === "primary") {
148
+ return `border border-primary-600 ${active ? "" : "text-primary-600"}`;
149
+ } else if (color === "grayDark") {
150
+ return `border border-gray-800 ${active ? "" : "text-gray-800"}`;
151
+ } else if (color === "grayLight") {
152
+ return `border border-gray-50 ${active ? "" : "text-gray-400"}`;
153
+ } else if (color === "gray400") {
154
+ return `border border-gray-400 ${active ? "" : "text-gray-400"}`;
155
+ }
156
+
157
+ return `border border-${color}-500 ${active ? "" : `text-${color}-500`}`;
158
+ };
159
+
160
+ export type ButtonCategoryType =
161
+ | "solid"
162
+ | "link"
163
+ | "linkHoverSolid"
164
+ | "outline";
165
+
166
+ const ButtonCategoryFn = (
167
+ color: string,
168
+ active: boolean,
169
+ ): { [key in ButtonCategoryType]: string } => {
170
+ return {
171
+ solid: buttonColorSolid(color),
172
+ link: buttonColorLink(color),
173
+ linkHoverSolid: buttonColorLinkHoverSolid(color, active),
174
+ outline: buttonColorOutline(color, active),
175
+ };
176
+ };
177
+
178
+ export const ButtonSize = {
179
+ none: "",
180
+ default: "min-w-fit h-[40px] text-base px-3",
181
+ base: "w-full md:min-w-fit h-[40px] text-base px-3",
182
+ small: "min-w-[106px] h-[30px] text-sm px-2",
183
+ normal: "min-w-[140px] h-[38px] text-base px-3",
184
+ large: "min-w-[160px] h-[40px] text-lg px-3",
185
+ NormalBlock: "w-full h-[40px] text-lg px-3",
186
+ box: "w-[38px] h-[38px]",
187
+ smallCollapse: "h-[30px] text-sm px-2",
188
+ normalCollapse: "h-[38px] text-base px-3",
189
+ } as const;
190
+
191
+ export type ButtonSizeType = keyof typeof ButtonSize;
192
+
193
+ export const ButtonRounded = {
194
+ none: "",
195
+ default: "rounded",
196
+ pill: "rounded-pill",
197
+ } as const;
198
+
199
+ export type ButtonRoundedType = keyof typeof ButtonRounded;
200
+
201
+ export interface ButtonProps
202
+ extends ButtonHTMLAttributes<Omit<HTMLButtonElement, "type">> {
203
+ active?: boolean;
204
+ children: ReactNode;
205
+ color?: string;
206
+ size?: ButtonSizeType;
207
+ category?: "solid" | "link" | "outline" | "linkHoverSolid";
208
+ type?: "button" | "submit";
209
+ rounded?: ButtonRoundedType;
210
+ className?: string;
211
+ useFilterStyle?: boolean;
212
+ usePaginationStyle?: boolean;
213
+ dataCy?: string;
214
+ testingIdKey?: string;
215
+ style?: CSSProperties;
216
+ primaryColor?: string; // for custom primary color
217
+ }
218
+
219
+ const button = (
220
+ {
221
+ active = false,
222
+ children,
223
+ color = "primary",
224
+ size = "normal",
225
+ category = "solid",
226
+ className,
227
+ type = "button",
228
+ rounded = "default",
229
+ style = {},
230
+ primaryColor,
231
+ ...props
232
+ }: ButtonProps,
233
+ ref: React.Ref<HTMLButtonElement>,
234
+ ) => {
235
+ const disabledClasses = ButtonDisabled;
236
+ const colorBaseClasses = !/link/.test(category)
237
+ ? buttonColorBase(color, active)
238
+ : null;
239
+ const focusClasses = ButtonFocus; // for accessibility we always need focus outlines
240
+ const sizeClasses = ButtonSize[size];
241
+ const categoryClasses = ButtonCategoryFn(color, active)?.[category];
242
+ const colorClasses = twMerge(colorBaseClasses, categoryClasses);
243
+ const roundedClasses = ButtonRounded[rounded];
244
+ return (
245
+ <button
246
+ {...props}
247
+ style={{
248
+ ...style,
249
+ transition:
250
+ "color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out",
251
+ backgroundColor: primaryColor ? primaryColor + "!important" : undefined,
252
+ }}
253
+ type={type}
254
+ ref={ref}
255
+ className={twMerge(
256
+ ButtonBase,
257
+ focusClasses,
258
+ disabledClasses,
259
+ sizeClasses,
260
+ colorClasses,
261
+ roundedClasses,
262
+ className,
263
+ "cursor-pointer",
264
+ )}
265
+ >
266
+ {children}
267
+ </button>
268
+ );
269
+ };
270
+
271
+ const Button = forwardRef(button);
272
+ export default Button;
@@ -0,0 +1,12 @@
1
+ export const Header = () => {
2
+ return (
3
+ <div
4
+ className="sticky top-0 z-[4]"
5
+ style={{ background: "rgb(0, 123, 255)" }}
6
+ >
7
+ <div className="container">
8
+ <div className="flex h-[56px] items-center"></div>
9
+ </div>
10
+ </div>
11
+ );
12
+ };
@@ -0,0 +1,39 @@
1
+ import { Description, Field, Input, Label } from "@headlessui/react";
2
+ import { cn } from "../../utils/cn";
3
+
4
+ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
5
+ type?: string; // Optional type prop, default is "text"
6
+ label: string; // Required label prop for the input field
7
+ description?: string; // Optional description prop for additional information
8
+ }
9
+ export const InputField = ({
10
+ type = "text",
11
+ className,
12
+ label,
13
+ description,
14
+ ...props
15
+ }: InputProps & {
16
+ className?: string;
17
+ }) => {
18
+ return (
19
+ <div className="w-full max-w-md px-4">
20
+ <Field>
21
+ <Label className="font-medium text-gray-900">{label}</Label>
22
+ {description && (
23
+ <Description className="text-sm text-secondary">
24
+ {description}
25
+ </Description>
26
+ )}
27
+ <Input
28
+ type={type}
29
+ className={cn(
30
+ "mt-3 block w-full rounded-lg border border-gray-200 px-3 py-1.5 text-sm/6 text-gray-800",
31
+ "focus:not-data-focus:outline-none data-focus:outline-2 data-focus:-outline-offset-2 data-focus:outline-primary-500",
32
+ className,
33
+ )}
34
+ {...props}
35
+ />
36
+ </Field>
37
+ </div>
38
+ );
39
+ };
@@ -0,0 +1,88 @@
1
+ import {
2
+ Dialog,
3
+ DialogBackdrop,
4
+ DialogPanel,
5
+ DialogTitle,
6
+ } from "@headlessui/react";
7
+ import React from "react";
8
+ import Button from "./button";
9
+
10
+ export const Modal = ({
11
+ isOpen,
12
+ close,
13
+ title,
14
+ dismissOptions,
15
+ confirmOptions,
16
+ children,
17
+ }: {
18
+ isOpen: boolean;
19
+ close: () => void;
20
+ title: string;
21
+ children: React.ReactNode;
22
+ dismissOptions?: {
23
+ label: string;
24
+ onClick: () => void;
25
+ };
26
+ confirmOptions?: {
27
+ label: string;
28
+ onClick: () => void;
29
+ disabled?: boolean;
30
+ };
31
+ }) => {
32
+ return (
33
+ <Dialog
34
+ open={isOpen}
35
+ onClose={close}
36
+ className="relative z-50 transition duration-300 ease-out data-closed:opacity-0 confidence-ui"
37
+ transition
38
+ >
39
+ {/* The backdrop, rendered as a fixed sibling to the panel container */}
40
+ <DialogBackdrop className="fixed inset-0 bg-black/30" />
41
+
42
+ {/* Full-screen container to center the panel */}
43
+ <div className="fixed inset-0 flex w-screen items-center justify-center p-4">
44
+ {/* The actual dialog panel */}
45
+ <DialogPanel className="max-w-lg divide-y divide-gray-100 bg-white rounded-sm shadow-xl">
46
+ <DialogTitle className=" text-gray-900 p-4">{title}</DialogTitle>
47
+ <div className="p-4">{children}</div>
48
+ {dismissOptions || confirmOptions ? (
49
+ <div className="flex gap-4 justify-end p-4">
50
+ {dismissOptions ? (
51
+ <Button
52
+ onClick={() => {
53
+ dismissOptions.onClick();
54
+ close();
55
+ }}
56
+ size="small"
57
+ color="secondary"
58
+ >
59
+ {dismissOptions.label}
60
+ </Button>
61
+ ) : (
62
+ <></>
63
+ )}
64
+
65
+ {confirmOptions ? (
66
+ <Button
67
+ disabled={confirmOptions.disabled}
68
+ onClick={() => {
69
+ confirmOptions.onClick();
70
+ close();
71
+ }}
72
+ size="small"
73
+ color="primary"
74
+ >
75
+ {confirmOptions.label}
76
+ </Button>
77
+ ) : (
78
+ <></>
79
+ )}
80
+ </div>
81
+ ) : (
82
+ <></>
83
+ )}
84
+ </DialogPanel>
85
+ </div>
86
+ </Dialog>
87
+ );
88
+ };
@@ -0,0 +1,7 @@
1
+ export const ConfidenceUIWrapper = ({
2
+ children,
3
+ }: {
4
+ children: React.ReactNode;
5
+ }) => {
6
+ return <div className="confidence-ui">{children}</div>;
7
+ };
@@ -0,0 +1,4 @@
1
+ const BASE_ENDPOINT = "https://marketplace.confidence.com/api";
2
+
3
+ export const CONFIDENCE_API_ENDPOINT = BASE_ENDPOINT + "/confidence";
4
+ export const DROPBOX_API_ENDPOINT = BASE_ENDPOINT + "/dropbox/prod";
@@ -0,0 +1,25 @@
1
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2
+ import { createContext, useContext, useState } from "react";
3
+
4
+ const Context = createContext<{
5
+ queryClient: QueryClient;
6
+ } | null>(null);
7
+
8
+ export const ConfidenceProvider = Context.Provider;
9
+
10
+ export const useConfidenceContext = () => useContext(Context)!;
11
+
12
+ export const ConfidenceContext = ({
13
+ children,
14
+ }: {
15
+ children: React.ReactNode;
16
+ }) => {
17
+ const [queryClient] = useState(new QueryClient());
18
+ console.log("Confidence Playbook Context Initialized");
19
+
20
+ return (
21
+ <Context.Provider value={{ queryClient }}>
22
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
23
+ </Context.Provider>
24
+ );
25
+ };
@@ -0,0 +1,32 @@
1
+ import { useMutation } from "@tanstack/react-query";
2
+ import { completeTask } from "../../services/complete-task.service";
3
+ import { useConfidenceContext } from "../../context/confidence-context";
4
+
5
+ export const useCompleteTask = () => {
6
+ const { queryClient } = useConfidenceContext();
7
+
8
+ const completeTaskMutation = useMutation(
9
+ {
10
+ mutationFn: async (taskInstanceId: string) => {
11
+ return completeTask(taskInstanceId);
12
+ },
13
+ },
14
+ queryClient,
15
+ );
16
+
17
+ const completeTaskHandler = async (taskInstanceId: string) => {
18
+ return completeTaskMutation.mutateAsync(taskInstanceId, {
19
+ onSuccess: () => {
20
+ console.log("Task completed successfully");
21
+ },
22
+ onError: (error) => {
23
+ console.error("Error completing task:", error);
24
+ },
25
+ });
26
+ };
27
+
28
+ return {
29
+ completeTask: completeTaskHandler,
30
+ ...completeTaskMutation,
31
+ };
32
+ };
@@ -0,0 +1,35 @@
1
+ import { useMutation } from "@tanstack/react-query";
2
+ import { startTask } from "../../services/start-task.services";
3
+ import { useConfidenceContext } from "../../context/confidence-context";
4
+
5
+ export const useStartTask = () => {
6
+ const { queryClient } = useConfidenceContext();
7
+
8
+ const startTaskMutation = useMutation(
9
+ {
10
+ mutationFn: async (taskInstanceId: string) => {
11
+ const res = await startTask(taskInstanceId);
12
+ console.log(res);
13
+
14
+ return res;
15
+ },
16
+ },
17
+ queryClient,
18
+ );
19
+
20
+ const startTaskHandler = (taskInstanceId: string) => {
21
+ return startTaskMutation.mutateAsync(taskInstanceId, {
22
+ onSuccess: () => {
23
+ console.log("Task started successfully");
24
+ },
25
+ onError: (error) => {
26
+ console.error("Error starting task:", error);
27
+ },
28
+ });
29
+ };
30
+
31
+ return {
32
+ startTask: startTaskHandler,
33
+ ...startTaskMutation,
34
+ };
35
+ };
@@ -0,0 +1,42 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { getTaskDetails } from "../../services/task-details.service";
3
+ import { useConfidenceContext } from "../../context/confidence-context";
4
+
5
+ export const useTaskDetails = ({
6
+ taskId,
7
+ playbookId,
8
+ enabled,
9
+ }: {
10
+ taskId: number | string;
11
+ playbookId: string | number;
12
+ enabled: boolean;
13
+ }) => {
14
+ const { queryClient } = useConfidenceContext();
15
+ const queryFn = async () =>
16
+ getTaskDetails({
17
+ taskId: +taskId,
18
+ templateId: playbookId.toString(),
19
+ });
20
+
21
+ const queryKey = ["taskDetails", +taskId, playbookId];
22
+ const query = useQuery(
23
+ {
24
+ queryKey,
25
+ queryFn,
26
+ enabled,
27
+ },
28
+ queryClient,
29
+ );
30
+
31
+ const prefetchQuery = () => {
32
+ queryClient.prefetchQuery({
33
+ queryFn,
34
+ queryKey,
35
+ });
36
+ };
37
+ return {
38
+ taskDetails: query.data,
39
+ prefetchQuery,
40
+ ...query,
41
+ };
42
+ };
@@ -0,0 +1,54 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { useConfidenceContext } from "../context/confidence-context";
3
+ import { Playbook } from "../types/playbook.types";
4
+ import { usePlaybookActions } from "./usePlaybookActions";
5
+ import { CONFIDENCE_API_ENDPOINT } from "../constants/settings.constants";
6
+
7
+ export const usePlaybook = (playbookInstanceId: number | string) => {
8
+ const { queryClient } = useConfidenceContext();
9
+ const query = useQuery(
10
+ {
11
+ queryFn: async () => {
12
+ const headers = new Headers();
13
+
14
+ const response = await fetch(
15
+ `${CONFIDENCE_API_ENDPOINT}/playbook-details`,
16
+ {
17
+ method: "POST",
18
+ cache: "no-store",
19
+ headers,
20
+ body: JSON.stringify({
21
+ playbookInstanceId: +playbookInstanceId,
22
+ start: 0,
23
+ limit: 50,
24
+ }),
25
+ },
26
+ );
27
+
28
+ if (!response.ok) {
29
+ throw new Error("Network response was not ok");
30
+ }
31
+
32
+ const data = await response.json();
33
+ if (data.error) {
34
+ throw new Error(data.error);
35
+ }
36
+
37
+ return data;
38
+ },
39
+ queryKey: ["playbook", +playbookInstanceId],
40
+ },
41
+ queryClient,
42
+ );
43
+
44
+ const actions = usePlaybookActions(playbookInstanceId, {
45
+ quickComplete: query.data?.quickComplete,
46
+ });
47
+
48
+ return {
49
+ playbook: query.data as Playbook,
50
+ isLoading: query.isLoading,
51
+ error: query.error,
52
+ actions,
53
+ };
54
+ };
@@ -0,0 +1,69 @@
1
+ import { useConfidenceContext } from "../context/confidence-context";
2
+ import { Playbook } from "../types/playbook.types";
3
+ import { Task, TASK_STATUS } from "../types/task.types";
4
+ import { useCompleteTask } from "./task/useCompleteTask";
5
+ import { useStartTask } from "./task/useStartTask";
6
+
7
+ export const usePlaybookActions = (
8
+ playbookInstanceId: number | string,
9
+ options: {
10
+ quickComplete?: boolean;
11
+ },
12
+ ) => {
13
+ const { queryClient } = useConfidenceContext();
14
+
15
+ const { completeTask } = useCompleteTask();
16
+ const { startTask } = useStartTask();
17
+
18
+ const completeTaskHandler = async (
19
+ taskInstanceId: number,
20
+ sequenceOrder: number,
21
+ ) => {
22
+ const { status } = await completeTask(taskInstanceId.toString());
23
+ if (!status) return;
24
+
25
+ queryClient.setQueryData(
26
+ ["playbook", +playbookInstanceId],
27
+ (oldData: Playbook) => {
28
+ if (!oldData) return oldData;
29
+ const updatedTasks = oldData.tasks.map((task: Task) => {
30
+ if (
31
+ task.sequenceOrder === sequenceOrder + 1 &&
32
+ options.quickComplete
33
+ ) {
34
+ return { ...task, workflowStatus: TASK_STATUS.IN_PROGRESS };
35
+ }
36
+ if (task.taskInstanceId === taskInstanceId) {
37
+ return { ...task, workflowStatus: status };
38
+ }
39
+ return task;
40
+ });
41
+
42
+ return { ...oldData, tasks: updatedTasks };
43
+ },
44
+ );
45
+ };
46
+
47
+ const startTaskHandler = async (taskInstanceId: number) => {
48
+ const { status } = await startTask(taskInstanceId.toString());
49
+ if (!status) return;
50
+ queryClient.setQueryData(
51
+ ["playbook", +playbookInstanceId],
52
+ (oldData: Playbook) => {
53
+ if (!oldData) return oldData;
54
+ const updatedTasks = oldData.tasks.map((task: Task) => {
55
+ if (task.taskInstanceId === taskInstanceId) {
56
+ return { ...task, workflowStatus: status };
57
+ }
58
+ return task;
59
+ });
60
+ return { ...oldData, tasks: updatedTasks };
61
+ },
62
+ );
63
+ };
64
+
65
+ return {
66
+ completeTask: completeTaskHandler,
67
+ startTask: startTaskHandler,
68
+ };
69
+ };