| | import React, { useState, useEffect } from 'react'; |
| | import { useSelector, useDispatch } from 'react-redux'; |
| | import Arrow2 from './assets/dropdown-arrow.svg'; |
| | import ArrowLeft from './assets/arrow-left.svg'; |
| | import ArrowRight from './assets/arrow-right.svg'; |
| | import Trash from './assets/trash.svg'; |
| | import { |
| | selectPrompt, |
| | setPrompt, |
| | selectSourceDocs, |
| | } from './preferences/preferenceSlice'; |
| | import { Doc } from './preferences/preferenceApi'; |
| | type PromptProps = { |
| | prompts: { name: string; id: string; type: string }[]; |
| | selectedPrompt: { name: string; id: string; type: string }; |
| | onSelectPrompt: (name: string, id: string, type: string) => void; |
| | setPrompts: (prompts: { name: string; id: string; type: string }[]) => void; |
| | apiHost: string; |
| | }; |
| |
|
| | const Setting: React.FC = () => { |
| | const tabs = ['General', 'Prompts', 'Documents']; |
| | |
| |
|
| | const [activeTab, setActiveTab] = useState('General'); |
| | const [prompts, setPrompts] = useState< |
| | { name: string; id: string; type: string }[] |
| | >([]); |
| | const selectedPrompt = useSelector(selectPrompt); |
| | const [isAddPromptModalOpen, setAddPromptModalOpen] = useState(false); |
| | const documents = useSelector(selectSourceDocs); |
| | const [isAddDocumentModalOpen, setAddDocumentModalOpen] = useState(false); |
| |
|
| | const dispatch = useDispatch(); |
| |
|
| | const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; |
| | const [widgetScreenshot, setWidgetScreenshot] = useState<File | null>(null); |
| |
|
| | const updateWidgetScreenshot = (screenshot: File | null) => { |
| | setWidgetScreenshot(screenshot); |
| | }; |
| |
|
| | useEffect(() => { |
| | const fetchPrompts = async () => { |
| | try { |
| | const response = await fetch(`${apiHost}/api/get_prompts`); |
| | if (!response.ok) { |
| | throw new Error('Failed to fetch prompts'); |
| | } |
| | const promptsData = await response.json(); |
| | setPrompts(promptsData); |
| | } catch (error) { |
| | console.error(error); |
| | } |
| | }; |
| |
|
| | fetchPrompts(); |
| | }, []); |
| |
|
| | const onDeletePrompt = (name: string, id: string) => { |
| | setPrompts(prompts.filter((prompt) => prompt.id !== id)); |
| |
|
| | fetch(`${apiHost}/api/delete_prompt`, { |
| | method: 'POST', |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | }, |
| | |
| | body: JSON.stringify({ id: id }), |
| | }) |
| | .then((response) => { |
| | if (!response.ok) { |
| | throw new Error('Failed to delete prompt'); |
| | } |
| | }) |
| | .catch((error) => { |
| | console.error(error); |
| | }); |
| | }; |
| |
|
| | const handleDeleteClick = (index: number, doc: Doc) => { |
| | const docPath = 'indexes/' + 'local' + '/' + doc.name; |
| |
|
| | fetch(`${apiHost}/api/delete_old?path=${docPath}`, { |
| | method: 'GET', |
| | }) |
| | .then(() => { |
| | |
| | const imageElement = document.querySelector( |
| | `#img-${index}`, |
| | ) as HTMLElement; |
| | const parentElement = imageElement.parentNode as HTMLElement; |
| | parentElement.parentNode?.removeChild(parentElement); |
| | }) |
| | .catch((error) => console.error(error)); |
| | }; |
| |
|
| | return ( |
| | <div className="p-4 pt-20 md:p-12 wa"> |
| | <p className="text-2xl font-bold text-eerie-black dark:text-bright-gray">Settings</p> |
| | <div className="mt-6 flex flex-row items-center space-x-4 overflow-x-auto md:space-x-8 "> |
| | <div className="md:hidden"> |
| | <button |
| | onClick={() => scrollTabs(-1)} |
| | className="flex h-8 w-8 items-center justify-center rounded-full border-2 border-purple-30 transition-all hover:bg-gray-100" |
| | > |
| | <img src={ArrowLeft} alt="left-arrow" className="h-6 w-6" /> |
| | </button> |
| | </div> |
| | <div className="flex flex-nowrap space-x-4 overflow-x-auto md:space-x-8"> |
| | {tabs.map((tab, index) => ( |
| | <button |
| | key={index} |
| | onClick={() => setActiveTab(tab)} |
| | className={`h-9 rounded-3xl px-4 font-bold ${activeTab === tab |
| | ? 'bg-purple-3000 text-purple-30 dark:bg-dark-charcoal' |
| | : 'text-gray-6000' |
| | }`} |
| | > |
| | {tab} |
| | </button> |
| | ))} |
| | </div> |
| | <div className="md:hidden"> |
| | <button |
| | onClick={() => scrollTabs(1)} |
| | className="flex h-8 w-8 items-center justify-center rounded-full border-2 border-purple-30 hover:bg-gray-100" |
| | > |
| | <img src={ArrowRight} alt="right-arrow" className="h-6 w-6" /> |
| | </button> |
| | </div> |
| | </div> |
| | {renderActiveTab()} |
| | |
| | {/* {activeTab === 'Widgets' && ( |
| | <Widgets |
| | widgetScreenshot={widgetScreenshot} |
| | onWidgetScreenshotChange={updateWidgetScreenshot} |
| | /> |
| | )} */} |
| | </div> |
| | ); |
| |
|
| | function scrollTabs(direction: number) { |
| | const container = document.querySelector('.flex-nowrap'); |
| | if (container) { |
| | container.scrollLeft += direction * 100; |
| | } |
| | } |
| |
|
| | function renderActiveTab() { |
| | switch (activeTab) { |
| | case 'General': |
| | return <General />; |
| | case 'Prompts': |
| | return ( |
| | <Prompts |
| | prompts={prompts} |
| | selectedPrompt={selectedPrompt} |
| | onSelectPrompt={(name, id, type) => |
| | dispatch(setPrompt({ name: name, id: id, type: type })) |
| | } |
| | setPrompts={setPrompts} |
| | apiHost={apiHost} |
| | /> |
| | ); |
| | case 'Documents': |
| | return ( |
| | <Documents |
| | documents={documents} |
| | handleDeleteDocument={handleDeleteClick} |
| | /> |
| | ); |
| | case 'Widgets': |
| | return ( |
| | <Widgets |
| | widgetScreenshot={widgetScreenshot} // Add this line |
| | onWidgetScreenshotChange={updateWidgetScreenshot} // Add this line |
| | /> |
| | ); |
| | default: |
| | return null; |
| | } |
| | } |
| | }; |
| |
|
| | const General: React.FC = () => { |
| | const themes = ['Light', 'Dark']; |
| | const languages = ['English']; |
| | const [selectedTheme, setSelectedTheme] = useState(localStorage.getItem('selectedTheme') || themes[0]); |
| | const [selectedLanguage, setSelectedLanguage] = useState(languages[0]); |
| |
|
| | useEffect(() => { |
| | if (selectedTheme === 'Dark') { |
| | document.documentElement.classList.add('dark'); |
| | document.documentElement.classList.add('dark:bg-raisin-black'); |
| | } else { |
| | document.documentElement.classList.remove('dark'); |
| | } |
| | localStorage.setItem('selectedTheme', selectedTheme); |
| | }, [selectedTheme]); |
| |
|
| | return ( |
| | <div className="mt-[59px]"> |
| | <div className="mb-4"> |
| | <p className="font-bold text-jet dark:text-bright-gray">Select Theme</p> |
| | <Dropdown |
| | options={themes} |
| | selectedValue={selectedTheme} |
| | onSelect={setSelectedTheme} |
| | /> |
| | </div> |
| | <div> |
| | <p className="font-bold text-jet dark:text-bright-gray">Select Language</p> |
| | <Dropdown |
| | options={languages} |
| | selectedValue={selectedLanguage} |
| | onSelect={setSelectedLanguage} |
| | /> |
| | </div> |
| | </div> |
| | ); |
| | }; |
| |
|
| | export default Setting; |
| |
|
| | const Prompts: React.FC<PromptProps> = ({ |
| | prompts, |
| | selectedPrompt, |
| | onSelectPrompt, |
| | setPrompts, |
| | apiHost, |
| | }) => { |
| | const handleSelectPrompt = ({ |
| | name, |
| | id, |
| | type, |
| | }: { |
| | name: string; |
| | id: string; |
| | type: string; |
| | }) => { |
| | setNewPromptName(name); |
| | onSelectPrompt(name, id, type); |
| | }; |
| | const [newPromptName, setNewPromptName] = useState(selectedPrompt.name); |
| | const [newPromptContent, setNewPromptContent] = useState(''); |
| |
|
| | const handleAddPrompt = async () => { |
| | try { |
| | const response = await fetch(`${apiHost}/api/create_prompt`, { |
| | method: 'POST', |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | }, |
| | body: JSON.stringify({ |
| | name: newPromptName, |
| | content: newPromptContent, |
| | }), |
| | }); |
| | if (!response.ok) { |
| | throw new Error('Failed to add prompt'); |
| | } |
| | const newPrompt = await response.json(); |
| | if (setPrompts) { |
| | setPrompts([ |
| | ...prompts, |
| | { name: newPromptName, id: newPrompt.id, type: 'private' }, |
| | ]); |
| | } |
| | onSelectPrompt(newPromptName, newPrompt.id, newPromptContent); |
| | setNewPromptName(newPromptName); |
| | } catch (error) { |
| | console.error(error); |
| | } |
| | }; |
| |
|
| | const handleDeletePrompt = () => { |
| | setPrompts(prompts.filter((prompt) => prompt.id !== selectedPrompt.id)); |
| | console.log('selectedPrompt.id', selectedPrompt.id); |
| |
|
| | fetch(`${apiHost}/api/delete_prompt`, { |
| | method: 'POST', |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | }, |
| | body: JSON.stringify({ id: selectedPrompt.id }), |
| | }) |
| | .then((response) => { |
| | if (!response.ok) { |
| | throw new Error('Failed to delete prompt'); |
| | } |
| | |
| | if (prompts.length > 0) { |
| | onSelectPrompt(prompts[0].name, prompts[0].id, prompts[0].type); |
| | setNewPromptName(prompts[0].name); |
| | } |
| | }) |
| | .catch((error) => { |
| | console.error(error); |
| | }); |
| | }; |
| |
|
| | useEffect(() => { |
| | const fetchPromptContent = async () => { |
| | console.log('fetching prompt content'); |
| | try { |
| | const response = await fetch( |
| | `${apiHost}/api/get_single_prompt?id=${selectedPrompt.id}`, |
| | { |
| | method: 'GET', |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | }, |
| | }, |
| | ); |
| | if (!response.ok) { |
| | throw new Error('Failed to fetch prompt content'); |
| | } |
| | const promptContent = await response.json(); |
| | setNewPromptContent(promptContent.content); |
| | } catch (error) { |
| | console.error(error); |
| | } |
| | }; |
| |
|
| | fetchPromptContent(); |
| | }, [selectedPrompt]); |
| |
|
| | const handleSaveChanges = () => { |
| | fetch(`${apiHost}/api/update_prompt`, { |
| | method: 'POST', |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | }, |
| | body: JSON.stringify({ |
| | id: selectedPrompt.id, |
| | name: newPromptName, |
| | content: newPromptContent, |
| | }), |
| | }) |
| | .then((response) => { |
| | if (!response.ok) { |
| | throw new Error('Failed to update prompt'); |
| | } |
| | onSelectPrompt(newPromptName, selectedPrompt.id, selectedPrompt.type); |
| | setNewPromptName(newPromptName); |
| | }) |
| | .catch((error) => { |
| | console.error(error); |
| | }); |
| | }; |
| |
|
| | return ( |
| | <div className="mt-[59px]"> |
| | <div className="mb-4"> |
| | <p className="font-semibold dark:text-bright-gray">Active Prompt</p> |
| | <DropdownPrompt |
| | options={prompts} |
| | selectedValue={selectedPrompt.name} |
| | onSelect={handleSelectPrompt} |
| | /> |
| | </div> |
| | |
| | <div className="mb-4"> |
| | <p className='dark:text-bright-gray'>Prompt name </p>{' '} |
| | <p className="mb-2 text-xs italic text-eerie-black dark:text-bright-gray"> |
| | start by editing name |
| | </p> |
| | <input |
| | type="text" |
| | value={newPromptName} |
| | placeholder="Active Prompt Name" |
| | className="w-full rounded-lg border-2 p-2 dark:border-chinese-silver dark:bg-transparent dark:text-white" |
| | onChange={(e) => setNewPromptName(e.target.value)} |
| | /> |
| | </div> |
| | |
| | <div className="mb-4"> |
| | <p className="mb-2 dark:text-bright-gray">Prompt content</p> |
| | <textarea |
| | className="h-32 w-full rounded-lg border-2 p-2 dark:border-chinese-silver dark:text-white dark:bg-transparent" |
| | value={newPromptContent} |
| | onChange={(e) => setNewPromptContent(e.target.value)} |
| | placeholder="Active prompt contents" |
| | /> |
| | </div> |
| | |
| | <div className="flex justify-between"> |
| | <button |
| | className={`rounded-lg bg-green-500 px-4 py-2 font-bold text-white transition-all hover:bg-green-700 ${newPromptName === selectedPrompt.name |
| | ? 'cursor-not-allowed opacity-50' |
| | : '' |
| | }`} |
| | onClick={handleAddPrompt} |
| | disabled={newPromptName === selectedPrompt.name} |
| | > |
| | Add New Prompt |
| | </button> |
| | <button |
| | className={`rounded-lg bg-red-500 px-4 py-2 font-bold text-white transition-all hover:bg-red-700 ${selectedPrompt.type === 'public' |
| | ? 'cursor-not-allowed opacity-50' |
| | : '' |
| | }`} |
| | onClick={handleDeletePrompt} |
| | disabled={selectedPrompt.type === 'public'} |
| | > |
| | Delete Prompt |
| | </button> |
| | <button |
| | className={`rounded-lg bg-blue-500 px-4 py-2 font-bold text-white transition-all hover:bg-blue-700 ${selectedPrompt.type === 'public' |
| | ? 'cursor-not-allowed opacity-50' |
| | : '' |
| | }`} |
| | onClick={handleSaveChanges} |
| | disabled={selectedPrompt.type === 'public'} |
| | > |
| | Save Changes |
| | </button> |
| | </div> |
| | </div> |
| | ); |
| | }; |
| |
|
| | function DropdownPrompt({ |
| | options, |
| | selectedValue, |
| | onSelect, |
| | }: { |
| | options: { name: string; id: string; type: string }[]; |
| | selectedValue: string; |
| | onSelect: (value: { name: string; id: string; type: string }) => void; |
| | }) { |
| | const [isOpen, setIsOpen] = useState(false); |
| |
|
| | return ( |
| | <div className="relative mt-2 w-32"> |
| | <button |
| | onClick={() => setIsOpen(!isOpen)} |
| | className="flex w-full cursor-pointer items-center rounded-xl border-2 dark:border-chinese-silver bg-white p-3 dark:bg-transparent" |
| | > |
| | <span className="flex-1 overflow-hidden text-ellipsis dark:text-bright-gray"> |
| | {selectedValue} |
| | </span> |
| | <img |
| | src={Arrow2} |
| | alt="arrow" |
| | className={`transform ${isOpen ? 'rotate-180' : 'rotate-0' |
| | } h-3 w-3 transition-transform`} |
| | /> |
| | </button> |
| | {isOpen && ( |
| | <div className="absolute left-0 right-0 z-50 -mt-3 rounded-b-xl border-2 dark:border-chinese-silver bg-white dark:bg-dark-charcoal shadow-lg"> |
| | {options.map((option, index) => ( |
| | <div |
| | key={index} |
| | className="flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:hover:bg-purple-taupe dark:text-bright-gray " |
| | > |
| | <span |
| | onClick={() => { |
| | onSelect(option); |
| | setIsOpen(false); |
| | }} |
| | className="ml-2 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3" |
| | > |
| | {option.name} |
| | </span> |
| | </div> |
| | ))} |
| | </div> |
| | )} |
| | </div> |
| | ); |
| | } |
| |
|
| | function Dropdown({ |
| | options, |
| | selectedValue, |
| | onSelect, |
| | showDelete, |
| | onDelete, |
| | }: { |
| | options: string[]; |
| | selectedValue: string; |
| | onSelect: (value: string) => void; |
| | showDelete?: boolean; // optional |
| | onDelete?: (value: string) => void; // optional |
| | }) { |
| | const [isOpen, setIsOpen] = useState(false); |
| |
|
| | return ( |
| | <div className="relative mt-2 w-32"> |
| | <button |
| | onClick={() => setIsOpen(!isOpen)} |
| | className="flex w-full cursor-pointer items-center rounded-xl border-2 dark:border-chinese-silver bg-white p-3 dark:bg-transparent" |
| | > |
| | <span className="flex-1 overflow-hidden text-ellipsis dark:text-bright-gray"> |
| | {selectedValue} |
| | </span> |
| | <img |
| | src={Arrow2} |
| | alt="arrow" |
| | className={`transform ${isOpen ? 'rotate-180' : 'rotate-0' |
| | } h-3 w-3 transition-transform`} |
| | /> |
| | </button> |
| | {isOpen && ( |
| | <div className="absolute left-0 right-0 z-50 -mt-3 rounded-b-xl border-2 bg-white dark:border-chinese-silver dark:bg-dark-charcoal shadow-lg"> |
| | {options.map((option, index) => ( |
| | <div |
| | key={index} |
| | className="flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:hover:bg-purple-taupe" |
| | > |
| | <span |
| | onClick={() => { |
| | onSelect(option); |
| | setIsOpen(false); |
| | }} |
| | className="ml-2 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3 dark:text-light-gray" |
| | > |
| | {option} |
| | </span> |
| | {showDelete && onDelete && ( |
| | <button onClick={() => onDelete(option)} className="p-2"> |
| | {/* Icon or text for delete button */} |
| | Delete |
| | </button> |
| | )} |
| | </div> |
| | ))} |
| | </div> |
| | )} |
| | </div> |
| | ); |
| | } |
| |
|
| | type AddPromptModalProps = { |
| | newPromptName: string; |
| | onNewPromptNameChange: (name: string) => void; |
| | onAddPrompt: () => void; |
| | onClose: () => void; |
| | }; |
| |
|
| | const AddPromptModal: React.FC<AddPromptModalProps> = ({ |
| | newPromptName, |
| | onNewPromptNameChange, |
| | onAddPrompt, |
| | onClose, |
| | }) => { |
| | return ( |
| | <div className="fixed top-0 left-0 flex h-screen w-screen items-center justify-center bg-gray-900 bg-opacity-50"> |
| | <div className="rounded-3xl bg-white p-4"> |
| | <p className="mb-2 text-2xl font-bold text-jet">Add New Prompt</p> |
| | <input |
| | type="text" |
| | placeholder="Enter Prompt Name" |
| | value={newPromptName} |
| | onChange={(e) => onNewPromptNameChange(e.target.value)} |
| | className="mb-4 w-full rounded-3xl border-2 dark:border-chinese-silver p-2" |
| | /> |
| | <button |
| | onClick={onAddPrompt} |
| | className="rounded-3xl bg-purple-300 px-4 py-2 font-bold text-white transition-all hover:bg-purple-600" |
| | > |
| | Save |
| | </button> |
| | <button |
| | onClick={onClose} |
| | className="mt-4 rounded-3xl px-4 py-2 font-bold text-red-500" |
| | > |
| | Cancel |
| | </button> |
| | </div> |
| | </div> |
| | ); |
| | }; |
| |
|
| | type DocumentsProps = { |
| | documents: Doc[] | null; |
| | handleDeleteDocument: (index: number, document: Doc) => void; |
| | }; |
| |
|
| | const Documents: React.FC<DocumentsProps> = ({ |
| | documents, |
| | handleDeleteDocument, |
| | }) => { |
| | return ( |
| | <div className="mt-8"> |
| | <div className="flex flex-col"> |
| | {/* <h2 className="text-xl font-semibold">Documents</h2> */} |
| | |
| | <div className="mt-[27px] overflow-x-auto border dark:border-chinese-silver rounded-xl w-max"> |
| | <table className="block w-full table-auto content-center justify-center text-center dark:text-bright-gray" > |
| | <thead> |
| | <tr> |
| | <th className="border-r p-4 md:w-[244px]">Document Name</th> |
| | <th className="border-r px-4 py-2 w-[244px]">Vector Date</th> |
| | <th className="border-r px-4 py-2 w-[244px]">Type</th> |
| | <th className="px-4 py-2"></th> |
| | </tr> |
| | </thead> |
| | <tbody> |
| | {documents && |
| | documents.map((document, index) => ( |
| | <tr key={index}> |
| | <td className="border-r border-t px-4 py-2">{document.name}</td> |
| | <td className="border-r border-t px-4 py-2">{document.date}</td> |
| | <td className="border-r border-t px-4 py-2"> |
| | {document.location === 'remote' |
| | ? 'Pre-loaded' |
| | : 'Private'} |
| | </td> |
| | <td className="border-t px-4 py-2"> |
| | {document.location !== 'remote' && ( |
| | <img |
| | src={Trash} |
| | alt="Delete" |
| | className="h-4 w-4 cursor-pointer hover:opacity-50" |
| | id={`img-${index}`} |
| | onClick={(event) => { |
| | event.stopPropagation(); |
| | handleDeleteDocument(index, document); |
| | }} |
| | /> |
| | )} |
| | </td> |
| | </tr> |
| | ))} |
| | </tbody> |
| | </table> |
| | </div> |
| | {/* <button |
| | onClick={toggleAddDocumentModal} |
| | className="mt-10 w-32 rounded-lg bg-purple-300 px-4 py-2 font-bold text-white transition-all hover:bg-purple-600" |
| | > |
| | Add New |
| | </button> */} |
| | </div> |
| | |
| | {/* {isAddDocumentModalOpen && ( |
| | <AddDocumentModal |
| | newDocument={newDocument} |
| | onNewDocumentChange={setNewDocument} |
| | onAddDocument={addDocument} |
| | onClose={toggleAddDocumentModal} |
| | /> |
| | )} */} |
| | </div> |
| | ); |
| | }; |
| |
|
| | type Document = { |
| | name: string; |
| | vectorDate: string; |
| | vectorLocation: string; |
| | }; |
| |
|
| | |
| | type AddDocumentModalProps = { |
| | newDocument: Document; |
| | onNewDocumentChange: (document: Document) => void; |
| | onAddDocument: () => void; |
| | onClose: () => void; |
| | }; |
| |
|
| | const AddDocumentModal: React.FC<AddDocumentModalProps> = ({ |
| | newDocument, |
| | onNewDocumentChange, |
| | onAddDocument, |
| | onClose, |
| | }) => { |
| | return ( |
| | <div className="fixed top-0 left-0 flex h-screen w-screen items-center justify-center bg-gray-900 bg-opacity-50"> |
| | <div className="w-[50%] rounded-lg bg-white p-4"> |
| | <p className="mb-2 text-2xl font-bold text-jet">Add New Document</p> |
| | <input |
| | type="text" |
| | placeholder="Document Name" |
| | value={newDocument.name} |
| | onChange={(e) => |
| | onNewDocumentChange({ ...newDocument, name: e.target.value }) |
| | } |
| | className="mb-4 w-full rounded-lg border-2 p-2" |
| | /> |
| | <input |
| | type="text" |
| | placeholder="Vector Date" |
| | value={newDocument.vectorDate} |
| | onChange={(e) => |
| | onNewDocumentChange({ ...newDocument, vectorDate: e.target.value }) |
| | } |
| | className="mb-4 w-full rounded-lg border-2 p-2" |
| | /> |
| | <input |
| | type="text" |
| | placeholder="Vector Location" |
| | value={newDocument.vectorLocation} |
| | onChange={(e) => |
| | onNewDocumentChange({ |
| | ...newDocument, |
| | vectorLocation: e.target.value, |
| | }) |
| | } |
| | className="mb-4 w-full rounded-lg border-2 p-2" |
| | /> |
| | <button |
| | onClick={onAddDocument} |
| | className="rounded-lg bg-purple-300 px-4 py-2 font-bold text-white transition-all hover:bg-purple-600" |
| | > |
| | Save |
| | </button> |
| | <button |
| | onClick={onClose} |
| | className="mt-4 rounded-lg px-4 py-2 font-bold text-red-500" |
| | > |
| | Cancel |
| | </button> |
| | </div> |
| | </div> |
| | ); |
| | }; |
| |
|
| | const Widgets: React.FC<{ |
| | widgetScreenshot: File | null; |
| | onWidgetScreenshotChange: (screenshot: File | null) => void; |
| | }> = ({ widgetScreenshot, onWidgetScreenshotChange }) => { |
| | const widgetSources = ['Source 1', 'Source 2', 'Source 3']; |
| | const widgetMethods = ['Method 1', 'Method 2', 'Method 3']; |
| | const widgetTypes = ['Type 1', 'Type 2', 'Type 3']; |
| |
|
| | const [selectedWidgetSource, setSelectedWidgetSource] = useState( |
| | widgetSources[0], |
| | ); |
| | const [selectedWidgetMethod, setSelectedWidgetMethod] = useState( |
| | widgetMethods[0], |
| | ); |
| | const [selectedWidgetType, setSelectedWidgetType] = useState(widgetTypes[0]); |
| |
|
| | |
| | const [widgetCode, setWidgetCode] = useState<string>(''); |
| |
|
| | const handleScreenshotChange = ( |
| | event: React.ChangeEvent<HTMLInputElement>, |
| | ) => { |
| | const files = event.target.files; |
| |
|
| | if (files && files.length > 0) { |
| | const selectedScreenshot = files[0]; |
| | onWidgetScreenshotChange(selectedScreenshot); |
| | } |
| | }; |
| |
|
| | const handleCopyToClipboard = () => { |
| | |
| | const textArea = document.createElement('textarea'); |
| | textArea.value = widgetCode; |
| | document.body.appendChild(textArea); |
| |
|
| | |
| | textArea.select(); |
| | document.execCommand('copy'); |
| |
|
| | |
| | document.body.removeChild(textArea); |
| | }; |
| |
|
| | return ( |
| | <div> |
| | <div className="mt-[59px]"> |
| | <p className="font-bold text-jet">Widget Source</p> |
| | <Dropdown |
| | options={widgetSources} |
| | selectedValue={selectedWidgetSource} |
| | onSelect={setSelectedWidgetSource} |
| | /> |
| | </div> |
| | <div className="mt-5"> |
| | <p className="font-bold text-jet">Widget Method</p> |
| | <Dropdown |
| | options={widgetMethods} |
| | selectedValue={selectedWidgetMethod} |
| | onSelect={setSelectedWidgetMethod} |
| | /> |
| | </div> |
| | <div className="mt-5"> |
| | <p className="font-bold text-jet">Widget Type</p> |
| | <Dropdown |
| | options={widgetTypes} |
| | selectedValue={selectedWidgetType} |
| | onSelect={setSelectedWidgetType} |
| | /> |
| | </div> |
| | <div className="mt-6"> |
| | <p className="font-bold text-jet">Widget Code Snippet</p> |
| | <textarea |
| | rows={4} |
| | value={widgetCode} |
| | onChange={(e) => setWidgetCode(e.target.value)} |
| | className="mt-3 w-full rounded-lg border-2 p-2" |
| | /> |
| | </div> |
| | <div className="mt-1"> |
| | <button |
| | onClick={handleCopyToClipboard} |
| | className="rounded-lg bg-blue-400 px-2 py-2 font-bold text-white transition-all hover:bg-blue-600" |
| | > |
| | Copy |
| | </button> |
| | </div> |
| | |
| | <div className="mt-4"> |
| | <p className="text-lg font-semibold">Widget Screenshot</p> |
| | <input type="file" accept="image/*" onChange={handleScreenshotChange} /> |
| | </div> |
| | |
| | {widgetScreenshot && ( |
| | <div className="mt-4"> |
| | <img |
| | src={URL.createObjectURL(widgetScreenshot)} |
| | alt="Widget Screenshot" |
| | className="max-w-full rounded-lg border border-gray-300" |
| | /> |
| | </div> |
| | )} |
| | </div> |
| | ); |
| | }; |
| |
|