Skip to content

Commit 8659c06

Browse files
committed
made some changes but image upload pending
1 parent 14edc9c commit 8659c06

File tree

5 files changed

+186
-69
lines changed

5 files changed

+186
-69
lines changed

package-lock.json

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/App.tsx

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,79 @@
1+
// src/App.tsx
2+
3+
import React from "react";
14
import ChatBot, { Flow } from "react-chatbotify";
25

36
import RcbPlugin from "./factory/RcbPluginFactory";
47
import { InputValidatorBlock } from "./types/InputValidatorBlock";
8+
import { ValidationResult } from "./types/ValidationResult";
59

610
const App = () => {
7-
// initialize example plugin
8-
const plugins = [RcbPlugin()];
9-
10-
// example flow for testing
11-
const flow: Flow = {
12-
start: {
13-
message: "Hey there, please enter your age!",
14-
path: "try_again",
15-
validateInput: (userInput: string) => {
16-
if (typeof userInput === "string" && !Number.isNaN(Number(userInput))) {
17-
return {success: true};
18-
}
19-
return {success: false, promptContent: "Age must be a number!", promptDuration: 3000, promptType: "error", highlightTextArea: true};
20-
}
21-
} as InputValidatorBlock,
22-
try_again : {
23-
message: "Nice, you passed the input validation!",
24-
path: "start",
25-
}
26-
}
27-
28-
return (
29-
<ChatBot
30-
id="chatbot-id"
31-
plugins={plugins}
32-
flow={flow}
33-
></ChatBot>
34-
);
35-
}
36-
37-
export default App;
11+
// Initialize the plugin
12+
const plugins = [RcbPlugin()];
13+
14+
// Define the validation function for file uploads
15+
const validateFile = (userInput?: File): ValidationResult => {
16+
if (!userInput) {
17+
return { success: false, promptContent: "No file selected.", promptType: "error" };
18+
}
19+
20+
const allowedTypes = ["image/jpeg", "image/png"];
21+
const maxSizeInBytes = 5 * 1024 * 1024; // 5MB
22+
23+
if (!allowedTypes.includes(userInput.type)) {
24+
return {
25+
success: false,
26+
promptContent: "Only JPEG and PNG images are allowed.",
27+
promptType: "error",
28+
};
29+
}
30+
31+
if (userInput.size > maxSizeInBytes) {
32+
return {
33+
success: false,
34+
promptContent: "File size must be less than 5MB.",
35+
promptType: "error",
36+
};
37+
}
38+
39+
return { success: true };
40+
};
41+
42+
// Define the flow
43+
const flow: Flow = {
44+
start: {
45+
message: "Hey there! Please enter your age.",
46+
path: "age_validation",
47+
validateInput: (userInput?: string) => {
48+
if (userInput && !Number.isNaN(Number(userInput))) {
49+
return { success: true };
50+
}
51+
return {
52+
success: false,
53+
promptContent: "Age must be a number!",
54+
promptDuration: 3000,
55+
promptType: "error",
56+
highlightTextArea: true,
57+
};
58+
},
59+
} as InputValidatorBlock,
60+
age_validation: {
61+
message: "Great! Now please upload a profile picture (JPEG or PNG).",
62+
path: "file_upload_validation",
63+
validateInput: validateFile,
64+
} as InputValidatorBlock,
65+
file_upload_validation: {
66+
message: "Thank you! Your profile picture has been uploaded successfully.",
67+
path: "end",
68+
},
69+
end: {
70+
message: "This is the end of the flow. Thank you!",
71+
},
72+
};
73+
74+
return (
75+
<ChatBot id="chatbot-id" plugins={plugins} flow={flow} />
76+
);
77+
};
78+
79+
export default App;

src/core/useRcbPlugin.ts

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
22
import {
33
useBotId,
44
RcbUserSubmitTextEvent,
5+
RcbUserUploadFileEvent,
56
useToasts,
67
useFlow,
78
useStyles,
@@ -17,7 +18,7 @@ import { getValidator } from "../utils/getValidator";
1718
/**
1819
* Plugin hook that handles all the core logic.
1920
*
20-
* @param pluginConfig configurations for the plugin
21+
* @param pluginConfig Configurations for the plugin.
2122
*/
2223
const useRcbPlugin = (pluginConfig?: PluginConfig) => {
2324
const { showToast } = useToasts();
@@ -31,67 +32,116 @@ const useRcbPlugin = (pluginConfig?: PluginConfig) => {
3132

3233
useEffect(() => {
3334
/**
34-
* Handles the user submitting input event.
35+
* Handles the user submitting text input event.
3536
*
36-
* @param event event emitted when user submits input
37+
* @param event Event emitted when user submits text input.
3738
*/
38-
const handleUserSubmitText = (event: RcbUserSubmitTextEvent): void => {
39-
// gets validator and if no validator, return
40-
const validator = getValidator(event, getBotId(), getFlow());
39+
const handleUserSubmitText = (event: Event): void => {
40+
const rcbEvent = event as RcbUserSubmitTextEvent;
41+
42+
// Get validator and if no validator, return
43+
const validator = getValidator(rcbEvent, getBotId(), getFlow());
4144
if (!validator) {
4245
return;
4346
}
4447

45-
// gets and checks validation result
48+
// Get and check validation result
4649
const validationResult = validator(
47-
event.data.inputText
50+
rcbEvent.data.inputText
4851
) as ValidationResult;
4952
if (!validationResult?.success) {
5053
event.preventDefault();
5154
}
5255

53-
// if nothing to prompt, return
56+
// If nothing to prompt, return
5457
if (!validationResult.promptContent) {
5558
return;
5659
}
5760

58-
// if this is the first plugin toast, preserve original styles for restoration later
61+
// Preserve original styles if this is the first plugin toast
5962
if (numPluginToasts === 0) {
60-
originalStyles.current = structuredClone(styles)
63+
originalStyles.current = structuredClone(styles);
6164
}
6265
const promptStyles = getPromptStyles(
6366
validationResult,
6467
mergedPluginConfig
6568
);
6669

67-
// update toast with prompt styles
70+
// Update styles with prompt styles
6871
updateStyles(promptStyles);
6972

70-
// shows prompt toast to user
73+
// Show prompt toast to user
7174
showToast(
7275
validationResult.promptContent,
7376
validationResult.promptDuration ?? 3000
7477
);
7578

76-
// increases number of plugin toasts by 1
79+
// Increase number of plugin toasts by 1
7780
setNumPluginToasts((prev) => prev + 1);
7881
};
7982

8083
/**
81-
* Handles the dismiss toast event.
84+
* Handles the user uploading a file event.
8285
*
83-
* @param event event emitted when toast is dismissed
86+
* @param event Event emitted when user uploads a file.
87+
*/
88+
const handleUserUploadFile = (event: Event): void => {
89+
const rcbEvent = event as RcbUserUploadFileEvent;
90+
const file: File = rcbEvent.data.files[0];
91+
92+
// Get validator and if no validator, return
93+
const validator = getValidator(rcbEvent, getBotId(), getFlow());
94+
if (!validator) {
95+
return;
96+
}
97+
98+
// Perform validation
99+
const validationResult = validator(file) as ValidationResult;
100+
if (!validationResult?.success) {
101+
event.preventDefault();
102+
}
103+
104+
// Show prompt if necessary
105+
if (validationResult.promptContent) {
106+
// Preserve original styles if this is the first plugin toast
107+
if (numPluginToasts === 0) {
108+
originalStyles.current = structuredClone(styles);
109+
}
110+
const promptStyles = getPromptStyles(
111+
validationResult,
112+
mergedPluginConfig
113+
);
114+
115+
// Update styles with prompt styles
116+
updateStyles(promptStyles);
117+
118+
// Show prompt toast to user
119+
showToast(
120+
validationResult.promptContent,
121+
validationResult.promptDuration ?? 3000
122+
);
123+
124+
// Increase number of plugin toasts by 1
125+
setNumPluginToasts((prev) => prev + 1);
126+
}
127+
};
128+
129+
/**
130+
* Handles the dismiss toast event.
84131
*/
85132
const handleDismissToast = (): void => {
86133
setNumPluginToasts((prev) => prev - 1);
87134
};
88135

89-
// adds required events
136+
// Add required event listeners
90137
window.addEventListener("rcb-user-submit-text", handleUserSubmitText);
138+
window.addEventListener("rcb-user-upload-file", handleUserUploadFile);
91139
window.addEventListener("rcb-dismiss-toast", handleDismissToast);
92140

93141
return () => {
142+
// Remove event listeners
94143
window.removeEventListener("rcb-user-submit-text", handleUserSubmitText);
144+
window.removeEventListener("rcb-user-upload-file", handleUserUploadFile);
95145
window.removeEventListener("rcb-dismiss-toast", handleDismissToast);
96146
};
97147
}, [
@@ -101,28 +151,29 @@ const useRcbPlugin = (pluginConfig?: PluginConfig) => {
101151
updateStyles,
102152
styles,
103153
mergedPluginConfig,
104-
numPluginToasts
154+
numPluginToasts,
105155
]);
106156

107-
// restores original styles when plugin toasts are all dismissed
157+
// Restore original styles when all plugin toasts are dismissed
108158
useEffect(() => {
109159
if (numPluginToasts === 0) {
110160
setTimeout(() => {
111161
replaceStyles(originalStyles.current);
112162
});
113163
}
114-
}, [numPluginToasts, replaceStyles, originalStyles]);
164+
}, [numPluginToasts, replaceStyles]);
115165

116-
// initializes plugin metadata with plugin name
166+
// Initialize plugin metadata with plugin name
117167
const pluginMetaData: ReturnType<Plugin> = {
118-
name: "@rcb-plugins/input-validator"
168+
name: "@rcb-plugins/input-validator",
119169
};
120170

121-
// adds required events in settings if auto config is true
171+
// Add required events in settings if autoConfig is true
122172
if (mergedPluginConfig.autoConfig) {
123173
pluginMetaData.settings = {
124174
event: {
125175
rcbUserSubmitText: true,
176+
rcbUserUploadFile: true,
126177
rcbDismissToast: true,
127178
},
128179
};

src/types/InputValidatorBlock.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
// src/types/InputValidatorBlock.ts
2+
13
import { Block } from "react-chatbotify";
24
import { ValidationResult } from "./ValidationResult";
35

46
/**
5-
* Extends the Block from React ChatBotify to support inputValidator attribute.
7+
* Extends the Block from React ChatBotify to support validateInput attribute.
68
*/
79
export type InputValidatorBlock = Block & {
8-
validateInput: (userInput?: string) => ValidationResult;
9-
};
10+
validateInput: (userInput?: string | File) => ValidationResult;
11+
};

src/utils/getValidator.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,50 @@
1-
import { Flow, RcbUserSubmitTextEvent } from "react-chatbotify";
1+
// src/utils/getValidator.ts
2+
3+
import { Flow, RcbUserSubmitTextEvent, RcbUserUploadFileEvent } from "react-chatbotify";
24
import { InputValidatorBlock } from "../types/InputValidatorBlock";
35

46
/**
5-
* Retrieves the validator function and returns null if not applicable.
7+
* Union type for user events that can be validated.
68
*/
7-
export const getValidator = (event: RcbUserSubmitTextEvent, currBotId: string | null, currFlow: Flow) => {
8-
if (currBotId !== event.detail.botId) {
9+
type RcbUserEvent = RcbUserSubmitTextEvent | RcbUserUploadFileEvent;
10+
11+
/**
12+
* Retrieves the validator function from the current flow block.
13+
*
14+
* @param event The event emitted by the user action (text submission or file upload).
15+
* @param currBotId The current bot ID.
16+
* @param currFlow The current flow object.
17+
* @returns The validator function if it exists, otherwise undefined.
18+
*/
19+
export const getValidator = (
20+
event: RcbUserEvent,
21+
currBotId: string | null,
22+
currFlow: Flow
23+
) => {
24+
if (!event.detail) {
925
return;
1026
}
11-
12-
if (!event.detail.currPath) {
27+
28+
const { botId, currPath } = event.detail;
29+
30+
if (currBotId !== botId) {
31+
return;
32+
}
33+
34+
if (!currPath) {
1335
return;
1436
}
1537

16-
const currBlock = currFlow[event.detail.currPath] as InputValidatorBlock;
38+
const currBlock = currFlow[currPath] as InputValidatorBlock;
1739
if (!currBlock) {
1840
return;
1941
}
2042

2143
const validator = currBlock.validateInput;
22-
const isValidatorFunction =
23-
validator && typeof validator === "function";
44+
const isValidatorFunction = validator && typeof validator === "function";
2445
if (!isValidatorFunction) {
2546
return;
2647
}
2748

2849
return validator;
29-
}
50+
};

0 commit comments

Comments
 (0)