-
Notifications
You must be signed in to change notification settings - Fork 1
Validation #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Validation #2
Changes from 6 commits
8659c06
6b7c798
00f183b
d42d6e9
7dc6b27
5ef1c5c
7773d34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,36 +2,83 @@ import ChatBot, { Flow } from "react-chatbotify"; | |
|
||
import RcbPlugin from "./factory/RcbPluginFactory"; | ||
import { InputValidatorBlock } from "./types/InputValidatorBlock"; | ||
import { validateFile } from "./utils/validateFile"; | ||
|
||
const App = () => { | ||
// initialize example plugin | ||
const plugins = [RcbPlugin()]; | ||
|
||
// example flow for testing | ||
const flow: Flow = { | ||
start: { | ||
message: "Hey there, please enter your age!", | ||
path: "try_again", | ||
validateInput: (userInput: string) => { | ||
if (typeof userInput === "string" && !Number.isNaN(Number(userInput))) { | ||
return {success: true}; | ||
} | ||
return {success: false, promptContent: "Age must be a number!", promptDuration: 3000, promptType: "error", highlightTextArea: true}; | ||
} | ||
} as InputValidatorBlock, | ||
try_again : { | ||
message: "Nice, you passed the input validation!", | ||
path: "start", | ||
} | ||
} | ||
|
||
return ( | ||
<ChatBot | ||
id="chatbot-id" | ||
plugins={plugins} | ||
flow={flow} | ||
></ChatBot> | ||
); | ||
} | ||
|
||
export default App; | ||
// Initialize the plugin | ||
const plugins = [RcbPlugin()]; | ||
|
||
// Example flow for testing | ||
const flow: Flow = { | ||
start: { | ||
message: "Hey there! Please enter your age.", | ||
path: "age_validation", | ||
validateTextInput: (userInput?: string) => { | ||
if (userInput && !Number.isNaN(Number(userInput))) { | ||
return { success: true }; | ||
} | ||
return { | ||
success: false, | ||
promptContent: "Age must be a number!", | ||
promptDuration: 3000, | ||
promptType: "error", | ||
highlightTextArea: true, | ||
}; | ||
}, | ||
} as InputValidatorBlock, | ||
|
||
age_validation: { | ||
message: | ||
"Great! Now please upload a profile picture (JPEG or PNG) or provide a URL.", | ||
path: "file_upload_validation", | ||
chatDisabled: true, // Set to true if you want to disable text input | ||
validateTextInput: (userInput?: string) => { | ||
console.log("validateTextInput called with userInput:", userInput); | ||
|
||
if (userInput && userInput.trim().length > 0) { | ||
// Optionally, validate if the input is a valid URL | ||
// For simplicity, we'll accept any non-empty text | ||
return { success: true }; | ||
} | ||
|
||
return { | ||
success: false, | ||
promptContent: | ||
"Please provide a valid URL or upload a file.", | ||
promptDuration: 3000, | ||
promptType: "error", | ||
}; | ||
}, | ||
validateFileInput: (file?: File) => { | ||
return validateFile(file); // Validate file input | ||
}, | ||
file: async ({ files }) => { | ||
console.log("Files received:", files); | ||
|
||
if (files && files[0]) { | ||
const validationResult = validateFile(files[0]); | ||
if (!validationResult.success) { | ||
console.error(validationResult.promptContent); | ||
// Return early to prevent success | ||
return { success: false }; | ||
} | ||
console.log("File uploaded successfully:", files[0]); | ||
} else { | ||
console.error("No file provided."); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The validation for files were already performed in |
||
}, | ||
} as InputValidatorBlock, | ||
|
||
file_upload_validation: { | ||
message: | ||
"Thank you! Your input has been received. You passed the validation!", | ||
Comment on lines
+51
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can go in a single line. |
||
path: "start", | ||
}, | ||
}; | ||
|
||
return ( | ||
<ChatBot id="chatbot-id" plugins={plugins} flow={flow}></ChatBot> | ||
); | ||
}; | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react"; | |
import { | ||
useBotId, | ||
RcbUserSubmitTextEvent, | ||
RcbUserUploadFileEvent, | ||
useToasts, | ||
useFlow, | ||
useStyles, | ||
|
@@ -17,7 +18,7 @@ import { getValidator } from "../utils/getValidator"; | |
/** | ||
* Plugin hook that handles all the core logic. | ||
* | ||
* @param pluginConfig configurations for the plugin | ||
* @param pluginConfig Configurations for the plugin. | ||
|
||
*/ | ||
const useRcbPlugin = (pluginConfig?: PluginConfig) => { | ||
const { showToast } = useToasts(); | ||
|
@@ -31,67 +32,118 @@ const useRcbPlugin = (pluginConfig?: PluginConfig) => { | |
|
||
useEffect(() => { | ||
/** | ||
* Handles the user submitting input event. | ||
* Handles the user submitting text input event. | ||
* | ||
* @param event event emitted when user submits input | ||
* @param event Event emitted when user submits text input. | ||
*/ | ||
const handleUserSubmitText = (event: RcbUserSubmitTextEvent): void => { | ||
// gets validator and if no validator, return | ||
const validator = getValidator(event, getBotId(), getFlow()); | ||
const handleUserSubmitText = (event: Event): void => { | ||
const rcbEvent = event as RcbUserSubmitTextEvent; | ||
|
||
// Get validator and if no validator, return | ||
const validator = getValidator<string>( | ||
rcbEvent, | ||
getBotId(), | ||
getFlow(), | ||
"validateTextInput" | ||
); | ||
if (!validator) { | ||
return; | ||
} | ||
|
||
// gets and checks validation result | ||
// Get and check validation result | ||
const validationResult = validator( | ||
event.data.inputText | ||
rcbEvent.data.inputText | ||
) as ValidationResult; | ||
if (!validationResult?.success) { | ||
event.preventDefault(); | ||
} | ||
|
||
// if nothing to prompt, return | ||
// If nothing to prompt, return | ||
if (!validationResult.promptContent) { | ||
return; | ||
} | ||
|
||
// if this is the first plugin toast, preserve original styles for restoration later | ||
// Preserve original styles if this is the first plugin toast | ||
if (numPluginToasts === 0) { | ||
originalStyles.current = structuredClone(styles) | ||
originalStyles.current = structuredClone(styles); | ||
} | ||
const promptStyles = getPromptStyles( | ||
validationResult, | ||
mergedPluginConfig | ||
); | ||
|
||
// update toast with prompt styles | ||
// Update styles with prompt styles | ||
updateStyles(promptStyles); | ||
|
||
// shows prompt toast to user | ||
// Show prompt toast to user | ||
showToast( | ||
validationResult.promptContent, | ||
validationResult.promptDuration ?? 3000 | ||
); | ||
|
||
// increases number of plugin toasts by 1 | ||
// Increase number of plugin toasts by 1 | ||
setNumPluginToasts((prev) => prev + 1); | ||
}; | ||
|
||
/** | ||
* Handles the dismiss toast event. | ||
* Handles the user uploading a file event. | ||
* | ||
* @param event event emitted when toast is dismissed | ||
* @param event Event emitted when user uploads a file. | ||
*/ | ||
// useRcbPlugin.ts | ||
|
||
const handleUserUploadFile = (event: Event): void => { | ||
const rcbEvent = event as RcbUserUploadFileEvent; | ||
const file: File | undefined = rcbEvent.data?.files?.[0]; | ||
|
||
if (!file) { | ||
console.error("No file uploaded."); | ||
event.preventDefault(); | ||
return; | ||
|
||
} | ||
|
||
const validator = getValidator<File>( | ||
rcbEvent, | ||
getBotId(), | ||
getFlow(), | ||
"validateFileInput" | ||
); | ||
|
||
if (!validator) { | ||
console.error("Validator not found for file input."); | ||
return; | ||
} | ||
|
||
const validationResult = validator(file); | ||
|
||
if (!validationResult.success) { | ||
console.error("Validation failed:", validationResult); | ||
if (validationResult.promptContent) { | ||
showToast(validationResult.promptContent, validationResult.promptDuration ?? 3000); | ||
} | ||
event.preventDefault(); | ||
return; | ||
} | ||
|
||
console.log("Validation successful:", validationResult); | ||
}; | ||
|
||
/** | ||
* Handles the dismiss toast event. | ||
*/ | ||
const handleDismissToast = (): void => { | ||
setNumPluginToasts((prev) => prev - 1); | ||
}; | ||
|
||
// adds required events | ||
// Add required event listeners | ||
window.addEventListener("rcb-user-submit-text", handleUserSubmitText); | ||
window.addEventListener("rcb-user-upload-file", handleUserUploadFile); | ||
window.addEventListener("rcb-dismiss-toast", handleDismissToast); | ||
|
||
return () => { | ||
// Remove event listeners | ||
window.removeEventListener("rcb-user-submit-text", handleUserSubmitText); | ||
window.removeEventListener("rcb-user-upload-file", handleUserUploadFile); | ||
window.removeEventListener("rcb-dismiss-toast", handleDismissToast); | ||
}; | ||
}, [ | ||
|
@@ -101,28 +153,29 @@ const useRcbPlugin = (pluginConfig?: PluginConfig) => { | |
updateStyles, | ||
styles, | ||
mergedPluginConfig, | ||
numPluginToasts | ||
numPluginToasts, | ||
]); | ||
|
||
// restores original styles when plugin toasts are all dismissed | ||
// Restore original styles when all plugin toasts are dismissed | ||
useEffect(() => { | ||
if (numPluginToasts === 0) { | ||
setTimeout(() => { | ||
replaceStyles(originalStyles.current); | ||
}); | ||
} | ||
}, [numPluginToasts, replaceStyles, originalStyles]); | ||
}, [numPluginToasts, replaceStyles]); | ||
|
||
// initializes plugin metadata with plugin name | ||
// Initialize plugin metadata with plugin name | ||
const pluginMetaData: ReturnType<Plugin> = { | ||
name: "@rcb-plugins/input-validator" | ||
name: "@rcb-plugins/input-validator", | ||
}; | ||
|
||
// adds required events in settings if auto config is true | ||
// Add required events in settings if autoConfig is true | ||
if (mergedPluginConfig.autoConfig) { | ||
pluginMetaData.settings = { | ||
event: { | ||
rcbUserSubmitText: true, | ||
rcbUserUploadFile: true, | ||
rcbDismissToast: true, | ||
}, | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
// src/types/InputValidatorBlock.ts | ||
|
||
import { Block } from "react-chatbotify"; | ||
import { ValidationResult } from "./ValidationResult"; | ||
|
||
/** | ||
* Extends the Block from React ChatBotify to support inputValidator attribute. | ||
*/ | ||
export type InputValidatorBlock = Block & { | ||
validateInput: (userInput?: string) => ValidationResult; | ||
|
||
export type InputValidatorBlock = Omit<Block, "file"> & { | ||
file?: (params: { files?: FileList }) => void | Promise<void>; | ||
|
||
validateTextInput?: (userInput?: string) => ValidationResult; | ||
validateFileInput?: (file?: File) => ValidationResult; | ||
|
||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for
validateTextInput
here, sincechatDisabled
is already set to true.