UNPKG

onairos

Version:

The Onairos Library is a collection of functions that enable Applications to connect and communicate data with Onairos Identities via User Authorization. Integration for developers is seamless, simple and effective for all applications. LLM SDK capabiliti

434 lines (399 loc) 14.3 kB
import { useState, useRef, useEffect } from 'react'; import AuthButtons from '../components/AuthButtons.jsx'; import IndividualConnection from './IndividualConnection.js'; import SecuritySetup from '../components/SecuritySetup.js'; import UniversalOnboarding from '../components/UniversalOnboarding.jsx'; import SignUp from '../components/SignUp.js'; export default function Overlay({ setOthentConnected, dataRequester, NoAccount, NoModel, activeModels, avatar, setAvatar, traits, setTraits, requestData, handleConnectionSelection, changeGranted, granted, allowSubmit, rejectDataRequest, sendDataRequest, isAuthenticated, onClose, onLoginSuccess, setOthentUser, setHashedOthentSub, setEncryptedPin, accountInfo }) { const [loginError, setLoginError] = useState(null); const [loading, setLoading] = useState(false); const overlayRef = useRef(null); // Manages Checkboxes // Maintain the `isChecked` state for all checkboxes in the parent const [checkedStates, setCheckedStates] = useState({}); const handleCheckboxChange = (key, isChecked) => { console.log(key, " is being changed check to:", isChecked) setCheckedStates((prevState) => ({ ...prevState, [key]: isChecked, })); // Call the `changeGranted` function accordingly changeGranted(isChecked ? 1 : -1); // Call the `handleConnectionSelection` function const product = requestData[key]; handleConnectionSelection( dataRequester, key, product.index, product.type, product.reward, isChecked ); console.log(product, " Selection registered") handleConnectionSelection(dataRequester, key, product.index, product.type, product.reward, true) }; const [currentView, setCurrentView] = useState(() => { if (isAuthenticated) { if (accountInfo && accountInfo.models?.length > 0) { return 'datarequests'; } return 'onboarding'; } return 'login'; }); const [formData, setFormData] = useState({ username: '', password: '' }); const [loginCompleted, setLoginCompleted] = useState(false); const API_URL = 'https://api2.onairos.uk'; // Set dynamic viewport height useEffect(() => { const setVH = () => { const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); }; setVH(); window.addEventListener('resize', setVH); window.addEventListener('orientationchange', setVH); return () => { window.removeEventListener('resize', setVH); window.removeEventListener('orientationchange', setVH); }; }, []); const handleClose = () => { onClose(); }; // Handle click outside useEffect(() => { const handleClickOutside = (event) => { if (overlayRef.current && !overlayRef.current.contains(event.target)) { handleClose?.(); } }; document.addEventListener('mousedown', handleClickOutside); document.addEventListener('touchstart', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('touchstart', handleClickOutside); }; }, [handleClose]); const handleInputChange = (e) => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; const handleOnairosLogin = async (e) => { e.preventDefault(); try { setLoginError(null); const loginAttempt = { details: { username: formData.username, password: formData.password, }, }; const response = await fetch(`${API_URL}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(loginAttempt), }); const data = await response.json(); if (data.authentication === 'Accepted') { localStorage.setItem('onairosToken', data.token); localStorage.setItem('username', formData.username); await handleLoginSuccess(formData.username); } else { throw new Error('Invalid credentials'); } } catch (error) { console.error('Login failed:', error); setLoginError('Invalid username or password'); } }; const handleLoginSuccess = async (identifier, isEmail = false) => { setLoading(true); try { const result = await onLoginSuccess(identifier, isEmail); setLoginError(null); } catch (error) { console.error('Login process failed:', error); setLoginError('Failed to complete login process'); } finally { setLoading(false); } }; const handleOnboardingComplete = () => { setCurrentView('security'); }; const handleSecurityComplete = (securityDetails) => { // Handle security setup completion if (securityDetails.method === 'othent') { setOthentUser(true); } else if (securityDetails.method === 'pin') { setEncryptedPin(securityDetails.value); } setCurrentView('datarequests'); }; const DataRequestsSection = ({ dataRequester, granted, allowSubmit, rejectDataRequest, sendDataRequest, activeModels, requestData, handleConnectionSelection, changeGranted, avatar, traits }) => { return ( <div className="flex flex-col h-full"> <div className="px-6"> <h1 className="text-lg font-semibold text-gray-900 mb-6"> Data Requests from {dataRequester} </h1> <div className="flex items-center justify-between mb-6"> <button className="bg-gray-500 hover:bg-gray-600 text-white font-bold py-2 px-8 rounded-full" onClick={rejectDataRequest} > Reject All </button> <button disabled={!allowSubmit || granted === 0} className={`${ allowSubmit && granted > 0 ? 'bg-blue-500 hover:bg-blue-600' : 'bg-gray-300 cursor-not-allowed' } text-white font-bold py-2 px-8 rounded-full`} onClick={sendDataRequest} > Confirm ({granted}) </button> </div> </div> <div className="flex-1 overflow-y-auto px-6"> {activeModels.length === 0 ? ( <div className="flex flex-col items-center justify-center py-8"> <img src="https://onairos.sirv.com/Images/OnairosWhite.png" alt="Onairos Logo" className="w-24 h-24 mb-4" /> <p className="text-center text-gray-800 font-medium"> Please connect <a href="https://onairos.uk/connections" className="text-blue-500 hover:underline">Onairos</a> Personality to send {dataRequester} your data </p> </div> ) : ( <div className="space-y-4"> {Object.keys(requestData) .sort((a, b) => { const aIsActive = activeModels.includes(requestData[a].type); const bIsActive = activeModels.includes(requestData[b].type); if (requestData[a].type === "Avatar") return 1; if (requestData[b].type === "Avatar") return -1; if (requestData[b].type === "Traits") return 1; if (requestData[a].type === "Traits") return -1; if (aIsActive && !bIsActive) return -1; if (bIsActive && !aIsActive) return 1; return 0; }) .map((key, index) => { const product = requestData[key]; const active = product.type === 'Personality' ? activeModels.includes(product.type) : product.type === 'Avatar' ? avatar : product.type === 'Traits' ? traits : false; return ( <IndividualConnection key={key} active={active} title={product.type} id={product} number={index} descriptions={product.descriptions} rewards={product.reward} size={key} isChecked={!!checkedStates[key]} // Pass the state from the parent onCheckboxChange={(isChecked) => handleCheckboxChange(key, isChecked)} // Handle the change /> ); })} </div> )} </div> </div> ); }; const renderContent = () => { switch (currentView) { case 'signup': return ( <SignUp onSignUpSuccess={handleLoginSuccess} setOthentUser={setOthentUser} setHashedOthentSub={setHashedOthentSub} setEncryptedPin={setEncryptedPin} /> ); case 'onboarding': return <UniversalOnboarding onComplete={handleOnboardingComplete} username={accountInfo?.username || formData.username} />; case 'security': return <SecuritySetup onComplete={handleSecurityComplete} />; case 'datarequests': return ( <DataRequestsSection dataRequester={dataRequester} granted={granted} allowSubmit={allowSubmit} rejectDataRequest={rejectDataRequest} sendDataRequest={sendDataRequest} activeModels={activeModels} requestData={requestData} handleConnectionSelection={handleConnectionSelection} changeGranted={changeGranted} avatar={avatar} traits={traits} /> ); default: return ( <div className="flex flex-col items-center justify-start max-w-sm mx-auto space-y-6 pt-4"> {loginError && ( <div className="w-full bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg"> {loginError} </div> )} <AuthButtons onLoginSuccess={handleLoginSuccess} setOthentUser={setOthentUser} setHashedOthentSub={setHashedOthentSub} setEncryptedPin={setEncryptedPin} /> <div className="w-full flex items-center justify-center space-x-4"> <hr className="flex-grow border-gray-300" /> <span className="text-gray-500">or</span> <hr className="flex-grow border-gray-300" /> </div> <form onSubmit={handleOnairosLogin} className="w-full space-y-4"> <input type="text" name="username" value={formData.username} onChange={handleInputChange} placeholder="Username" className="w-full px-4 py-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required /> <input type="password" name="password" value={formData.password} onChange={handleInputChange} placeholder="Password" className="w-full px-4 py-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required /> <button type="submit" className="w-full bg-blue-500 text-white font-semibold py-3 px-4 rounded-lg hover:bg-blue-600 transition-colors" > Sign In </button> </form> <button onClick={() => setCurrentView('signup')} className="w-full text-center text-blue-500 hover:text-blue-600" > Don't have an account? Sign up </button> </div> ); } }; useEffect(() => { if (isAuthenticated && accountInfo) { if (accountInfo.models?.length > 0) { setCurrentView('datarequests'); } else { setCurrentView('onboarding'); } } }, [isAuthenticated, accountInfo]); useEffect(() => { return () => { setLoginCompleted(false); }; }, []); useEffect(() => { }, [isAuthenticated, accountInfo]) // if (loading) { // return ( // <> // <div className="fixed inset-0 bg-black bg-opacity-50" /> // <div // ref={overlayRef} // className="fixed bottom-0 left-0 right-0 w-full bg-white rounded-t-3xl shadow-2xl transform transition-transform duration-300 ease-out flex items-center justify-center" // style={{ height: 'calc(var(--vh, 1vh) * 50)' }} // > // <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div> // </div> // </> // ); // } return ( <> <div className="fixed inset-0 bg-black bg-opacity-50" onClick={handleClose} style={{ touchAction: 'none' }} /> <div ref={overlayRef} className="fixed bottom-0 left-0 right-0 w-full bg-white rounded-t-3xl shadow-2xl transform transition-transform duration-300 ease-out flex flex-col" style={{ maxHeight: '60vh', minHeight: '45vh', height: 'auto', touchAction: 'none' }} > <div className="sticky top-0 bg-white z-10 px-6 pt-3 pb-2"> <div className="w-12 h-1.5 bg-gray-300 rounded-full mx-auto"></div> </div> <div className="flex-1 overflow-y-auto px-6 pb-8" style={{ touchAction: 'pan-y' }}> {renderContent()} </div> </div> </> ); }