diff --git a/BACKUPS/FIRST_PHASE/README_FIRST_PHASE.md b/BACKUPS/FIRST_PHASE/README_FIRST_PHASE.md new file mode 100644 index 000000000..e609fe1c9 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/README_FIRST_PHASE.md @@ -0,0 +1,42 @@ +# FIRST PHASE BACKUP + +**Date:** $(date) +**Status:** LOCKED - Working State + +## Features Included: +- ✅ Vendor Management System +- ✅ YFinance Integration +- ✅ Trading Dashboard with Tabs +- ✅ Market Data Fetching +- ✅ Analytics & Bulk Download +- ✅ Admin Tab with Code Viewer +- ✅ Error Boundaries +- ✅ Fault Isolation + +## Architecture: +- **Core System:** VendorManager, BaseVendorAdapter +- **Vendors:** ExampleVendorA, ExampleVendorB, YFinanceVendor +- **Components:** TradingDashboard, YFinanceComponents, AdminComponents +- **UI:** Responsive design with tabbed interface + +## Restore Instructions: +```bash +# To restore this phase: +rm -rf src/ +cp -r BACKUPS/FIRST_PHASE/src/ ./ +cp BACKUPS/FIRST_PHASE/package.json ./ +cp BACKUPS/FIRST_PHASE/vite.config.js ./ +cp BACKUPS/FIRST_PHASE/index.html ./ +``` + +## Key Files: +- src/core/VendorManager.js +- src/core/BaseVendorAdapter.js +- src/vendors/YFinanceVendor.js +- src/components/TradingDashboard.jsx +- src/components/YFinanceComponents.jsx +- src/components/AdminComponents.jsx +- src/App.jsx +- src/App.css + +**⚠️ DO NOT MODIFY THIS BACKUP ⚠️** \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/index.html b/BACKUPS/FIRST_PHASE/index.html new file mode 100644 index 000000000..452ca1f32 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + React App + + + +
+ + + + diff --git a/BACKUPS/FIRST_PHASE/package.json b/BACKUPS/FIRST_PHASE/package.json new file mode 100644 index 000000000..5e1b6b8c4 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/package.json @@ -0,0 +1,49 @@ +{ + "name": "codespaces-react", + "version": "0.1.0", + "private": true, + "type": "module", + "dependencies": { + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^14.4.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "web-vitals": "^3.1.0" + }, + "overrides": { + "@svgr/webpack": "^8.0.1", + "@adobe/css-tools": "^4.3.1", + "postcss": "^8.4.31" + }, + "scripts": { + "start": "BROWSER=none WDS_SOCKET_PORT=0 vite --port 3000", + "build": "vite build", + "preview": "vite preview", + "test": "vitest" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.7.0", + "jsdom": "^26.1.0", + "vite": "^6.3.6", + "vitest": "^3.0.7" + } +} diff --git a/BACKUPS/FIRST_PHASE/src/App.css b/BACKUPS/FIRST_PHASE/src/App.css new file mode 100644 index 000000000..f73ff0f34 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/App.css @@ -0,0 +1,387 @@ +.trading-dashboard { + max-width: 1200px; + margin: 0 auto; + padding: 20px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; +} + +.dashboard-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 2px solid #e0e0e0; +} + +.dashboard-header h1 { + margin: 0; + color: #333; + font-size: 2rem; +} + +.vendor-selector { + padding: 8px 16px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + min-width: 150px; +} + +.tab-navigation { + display: flex; + gap: 4px; + margin-bottom: 20px; + border-bottom: 1px solid #e0e0e0; +} + +.tab { + padding: 12px 24px; + border: none; + background: none; + cursor: pointer; + font-size: 14px; + font-weight: 500; + color: #666; + border-bottom: 3px solid transparent; + transition: all 0.2s ease; +} + +.tab:hover { + color: #333; + background-color: #f5f5f5; +} + +.tab.active { + color: #007acc; + border-bottom-color: #007acc; +} + +.dashboard-content { + min-height: 400px; +} + +.tab-content { + padding: 20px; + background: #f9f9f9; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.tab-content h2 { + margin-top: 0; + color: #333; +} + +.error-boundary { + padding: 20px; + background: #fff3cd; + border: 1px solid #ffeaa7; + border-radius: 8px; + text-align: center; +} + +.error-boundary h3 { + color: #856404; + margin-top: 0; +} + +.error-boundary button { + padding: 8px 16px; + background: #007acc; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + margin-top: 10px; +} + +.error-boundary button:hover { + background: #005a9e; +} + +.fetch-controls, .analytics-controls, .bulk-controls { + background: white; + padding: 20px; + border-radius: 8px; + border: 1px solid #e0e0e0; + margin-bottom: 20px; +} + +.control-row { + display: flex; + gap: 15px; + margin-bottom: 15px; + flex-wrap: wrap; +} + +.control-group { + flex: 1; + min-width: 150px; +} + +.control-group label { + display: block; + margin-bottom: 5px; + font-weight: 500; + font-size: 14px; +} + +.control-group input, .control-group select { + width: 100%; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.control-group input[type="checkbox"] { + width: auto; + margin-right: 8px; +} + +.fetch-btn { + background: #007acc; + color: white; + border: none; + padding: 12px 24px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + width: 100%; + margin-top: 10px; +} + +.fetch-btn:disabled { + background: #ccc; + cursor: not-allowed; +} + +.fetch-btn:hover:not(:disabled) { + background: #005a9e; +} + +.quote-display { + background: white; + padding: 20px; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.price-info { + display: flex; + align-items: center; + gap: 15px; + margin: 10px 0; +} + +.price { + font-size: 2rem; + font-weight: bold; + color: #333; +} + +.change.positive { + color: #28a745; +} + +.change.negative { + color: #dc3545; +} + +.historical-summary { + background: #f8f9fa; + padding: 15px; + border-radius: 6px; + margin-top: 15px; +} + +.historical-summary p { + margin: 5px 0; + font-size: 14px; +} + +.analytics-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; + margin-top: 20px; +} + +.metric { + background: white; + padding: 15px; + border-radius: 6px; + border: 1px solid #e0e0e0; + text-align: center; +} + +.metric label { + display: block; + font-size: 12px; + color: #666; + margin-bottom: 5px; +} + +.metric span { + font-size: 16px; + font-weight: bold; +} + +.volatility.high, .recommendation.sell { + color: #dc3545; +} + +.volatility.medium, .recommendation.hold { + color: #ffc107; +} + +.volatility.low, .recommendation.buy, .trend.up { + color: #28a745; +} + +.trend.down { + color: #dc3545; +} + +.bulk-downloader { + margin-top: 30px; + padding: 20px; + background: white; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.download-results { + margin-top: 15px; + padding: 10px; + background: #d4edda; + border: 1px solid #c3e6cb; + border-radius: 4px; + color: #155724; +} + +.admin-nav { + display: flex; + gap: 10px; + margin-bottom: 20px; +} + +.admin-btn { + padding: 8px 16px; + border: 1px solid #ddd; + background: white; + cursor: pointer; + border-radius: 4px; +} + +.admin-btn.active { + background: #007acc; + color: white; + border-color: #007acc; +} + +.code-viewer { + background: white; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.code-tabs { + display: flex; + border-bottom: 1px solid #e0e0e0; +} + +.code-tab { + padding: 12px 20px; + border: none; + background: none; + cursor: pointer; + border-bottom: 3px solid transparent; +} + +.code-tab.active { + background: #f8f9fa; + border-bottom-color: #007acc; +} + +.code-display { + padding: 20px; +} + +.code-block { + background: #f8f9fa; + padding: 15px; + border-radius: 6px; + overflow-x: auto; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 13px; + line-height: 1.4; + border: 1px solid #e9ecef; +} + +.vendor-status { + background: white; + padding: 20px; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.status-btn { + background: #28a745; + color: white; + border: none; + padding: 10px 20px; + border-radius: 4px; + cursor: pointer; + margin-bottom: 20px; +} + +.status-grid { + display: grid; + gap: 15px; +} + +.status-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background: #f8f9fa; + border-radius: 4px; +} + +.status-item label { + font-weight: 500; +} + +.connected { + color: #28a745; +} + +.disconnected { + color: #dc3545; +} + +.capabilities { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.capability { + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; +} + +.capability.enabled { + background: #d4edda; + color: #155724; +} + +.capability.disabled { + background: #f8d7da; + color: #721c24; +} \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/App.jsx b/BACKUPS/FIRST_PHASE/src/App.jsx new file mode 100644 index 000000000..f3a1bf21a --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/App.jsx @@ -0,0 +1,24 @@ +import React, { useEffect } from 'react'; +import './App.css'; +import TradingDashboard from './components/TradingDashboard'; +import VendorManager from './core/VendorManager'; +import ExampleVendorA from './vendors/ExampleVendorA'; +import ExampleVendorB from './vendors/ExampleVendorB'; +import YFinanceVendor from './vendors/YFinanceVendor'; + +function App() { + useEffect(() => { + // Register vendors on app startup + VendorManager.registerVendor('vendorA', new ExampleVendorA()); + VendorManager.registerVendor('vendorB', new ExampleVendorB()); + VendorManager.registerVendor('yfinance', new YFinanceVendor()); + }, []); + + return ( +
+ +
+ ); +} + +export default App; \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/App.test.jsx b/BACKUPS/FIRST_PHASE/src/App.test.jsx new file mode 100644 index 000000000..5e9824933 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/App.test.jsx @@ -0,0 +1,9 @@ +import { expect, test } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeDefined(); +}); diff --git a/BACKUPS/FIRST_PHASE/src/components/AdminComponents.jsx b/BACKUPS/FIRST_PHASE/src/components/AdminComponents.jsx new file mode 100644 index 000000000..c1dfce7d8 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/components/AdminComponents.jsx @@ -0,0 +1,291 @@ +import React, { useState } from 'react'; +import VendorManager from '../core/VendorManager'; + +export const CodeViewer = ({ vendor }) => { + const [activeCode, setActiveCode] = useState('fetch'); + + const codeExamples = { + fetch: { + title: 'Data Fetching Code', + code: `// Market Data Fetch +const fetchMarketData = async (symbol, interval = '1d') => { + const url = \`https://query1.finance.yahoo.com/v8/finance/chart/\${symbol}?interval=\${interval}&range=1d\`; + const response = await fetch(url); + const data = await response.json(); + return parseYahooResponse(data, symbol); +}; + +// Historical Data Fetch +const fetchHistoricalData = async (symbol, interval, period) => { + const url = \`https://query1.finance.yahoo.com/v8/finance/chart/\${symbol}?interval=\${interval}&range=\${period}\`; + const response = await fetch(url); + const data = await response.json(); + return parseHistoricalData(data, symbol); +}; + +// Bulk Download +const bulkDownload = async (symbols, intervals, options) => { + const results = {}; + const batchSize = options.batchSize || 10; + + for (let i = 0; i < symbols.length; i += batchSize) { + const batch = symbols.slice(i, i + batchSize); + + for (const symbol of batch) { + for (const interval of intervals) { + const data = await fetchHistoricalData(symbol, interval, options.period); + if (!results[symbol]) results[symbol] = {}; + results[symbol][interval] = data; + } + } + + // Rate limiting + if (i + batchSize < symbols.length) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + + return results; +};` + }, + parser: { + title: 'Data Parser Code', + code: `// Yahoo Response Parser +const parseYahooResponse = (data, symbol) => { + const chart = data.chart?.result?.[0]; + const meta = chart.meta; + const quote = chart.indicators?.quote?.[0]; + + return { + symbol, + price: meta.regularMarketPrice || 0, + change: (meta.regularMarketPrice - meta.previousClose) || 0, + changePercent: ((meta.regularMarketPrice - meta.previousClose) / meta.previousClose * 100) || 0, + volume: meta.regularMarketVolume || 0, + high: quote?.high?.[quote.high.length - 1] || meta.regularMarketDayHigh, + low: quote?.low?.[quote.low.length - 1] || meta.regularMarketDayLow, + open: quote?.open?.[0] || meta.regularMarketDayLow, + previousClose: meta.previousClose || 0, + timestamp: Date.now() + }; +}; + +// Historical Data Parser +const parseHistoricalData = (data, symbol) => { + const chart = data.chart?.result?.[0]; + const timestamps = chart.timestamp || []; + const quote = chart.indicators?.quote?.[0] || {}; + + return { + symbol, + data: timestamps.map((ts, i) => ({ + timestamp: new Date(ts * 1000).toISOString(), + open: quote.open?.[i] || null, + high: quote.high?.[i] || null, + low: quote.low?.[i] || null, + close: quote.close?.[i] || null, + volume: quote.volume?.[i] || null + })).filter(item => item.close !== null) + }; +};` + }, + analytics: { + title: 'Analytics Code', + code: `// Volatility Calculator +const calculateVolatility = (dailyData, weeklyData) => { + const dailyChange = Math.abs(dailyData.changePercent || 0); + const weeklyChange = Math.abs(weeklyData.changePercent || 0); + + if (dailyChange > 5 || weeklyChange > 15) return 'HIGH'; + if (dailyChange > 2 || weeklyChange > 8) return 'MEDIUM'; + return 'LOW'; +}; + +// Recommendation Generator +const generateRecommendation = (data) => { + const { changePercent, volume, pe } = data; + + let score = 0; + if (changePercent > 2) score += 1; + if (changePercent < -2) score -= 1; + if (volume > 1000000) score += 0.5; + if (pe && pe < 15) score += 0.5; + if (pe && pe > 30) score -= 0.5; + + if (score > 1) return 'BUY'; + if (score < -1) return 'SELL'; + return 'HOLD'; +}; + +// Analytics Fetch +const getAnalytics = async (symbol, timeframe = '1mo') => { + const [daily, weekly] = await Promise.all([ + getMarketData(symbol, '1d'), + getMarketData(symbol, '1wk') + ]); + + return { + symbol, + price: daily.data.price, + change: daily.data.change, + changePercent: daily.data.changePercent, + volume: daily.data.volume, + volatility: calculateVolatility(daily.data, weekly.data), + weeklyTrend: weekly.data.change > 0 ? 'UP' : 'DOWN', + recommendation: generateRecommendation(daily.data) + }; +};` + }, + vendor: { + title: 'Vendor Adapter Code', + code: `// YFinance Vendor Class +class YFinanceVendor extends BaseVendorAdapter { + constructor(config = {}) { + super('Yahoo Finance', { + baseUrl: 'https://query1.finance.yahoo.com/v8/finance/chart/', + intervals: ['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1wk', '1mo', '3mo'], + ...config + }); + this.cache = new Map(); + this.cacheTimeout = 30000; // 30 seconds + } + + getCapabilities() { + return { + marketData: true, + orderManagement: false, + portfolio: false, + analytics: true, + realtime: true + }; + } + + async connect() { + try { + const testResult = await this.getMarketData('AAPL'); + this.isConnected = testResult.success; + return { success: this.isConnected }; + } catch (error) { + return this.handleError(error, 'connect'); + } + } + + async getMarketData(symbol, interval = '1d') { + try { + const cacheKey = \`\${symbol}_\${interval}\`; + const cached = this.cache.get(cacheKey); + + if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { + return { success: true, data: cached.data }; + } + + const url = \`\${this.config.baseUrl}\${symbol}?interval=\${interval}&range=1d\`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(\`HTTP \${response.status}: \${response.statusText}\`); + } + + const data = await response.json(); + const result = this.parseYahooResponse(data, symbol); + + this.cache.set(cacheKey, { + data: result, + timestamp: Date.now() + }); + + return { success: true, data: result }; + } catch (error) { + return this.handleError(error, 'getMarketData'); + } + } +}` + } + }; + + return ( +
+
+ {Object.entries(codeExamples).map(([key, example]) => ( + + ))} +
+ +
+

{codeExamples[activeCode].title}

+
+          {codeExamples[activeCode].code}
+        
+
+
+ ); +}; + +export const VendorStatus = ({ vendor }) => { + const [status, setStatus] = useState(null); + + const checkStatus = async () => { + try { + const vendorInstance = VendorManager.getVendor(vendor); + const capabilities = VendorManager.getVendorCapabilities(vendor); + + setStatus({ + name: vendorInstance?.name || 'Unknown', + connected: vendorInstance?.isConnected || false, + capabilities, + cacheSize: vendorInstance?.cache?.size || 0 + }); + } catch (error) { + setStatus({ error: error.message }); + } + }; + + return ( +
+ + + {status && ( +
+ {status.error ? ( +
Error: {status.error}
+ ) : ( +
+
+ + {status.name} +
+
+ + + {status.connected ? 'Connected' : 'Disconnected'} + +
+
+ + {status.cacheSize} items +
+
+ +
+ {Object.entries(status.capabilities).map(([cap, enabled]) => ( + + {cap}: {enabled ? '✓' : '✗'} + + ))} +
+
+
+ )} +
+ )} +
+ ); +}; \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/components/ErrorBoundary.jsx b/BACKUPS/FIRST_PHASE/src/components/ErrorBoundary.jsx new file mode 100644 index 000000000..9028075a8 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/components/ErrorBoundary.jsx @@ -0,0 +1,34 @@ +import React from 'react'; + +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error) { + return { hasError: true, error }; + } + + componentDidCatch(error, errorInfo) { + console.error('ErrorBoundary caught an error:', error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ( +
+

Something went wrong

+

{this.props.fallbackMessage || 'This section is temporarily unavailable'}

+ +
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/components/TradingDashboard.jsx b/BACKUPS/FIRST_PHASE/src/components/TradingDashboard.jsx new file mode 100644 index 000000000..8b6db8b21 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/components/TradingDashboard.jsx @@ -0,0 +1,170 @@ +import React, { useState, useEffect } from 'react'; +import ErrorBoundary from './ErrorBoundary'; +import VendorManager from '../core/VendorManager'; +import { YFinanceMarketData, YFinanceAnalytics, YFinanceBulkDownloader } from './YFinanceComponents'; +import { CodeViewer, VendorStatus } from './AdminComponents'; + +const TradingDashboard = () => { + const [activeTab, setActiveTab] = useState('market'); + const [vendors, setVendors] = useState([]); + const [selectedVendor, setSelectedVendor] = useState(''); + + useEffect(() => { + setVendors(VendorManager.getAllVendors()); + if (VendorManager.getAllVendors().length > 0) { + setSelectedVendor(VendorManager.getAllVendors()[0]); + } + }, []); + + const tabs = [ + { id: 'market', label: 'Market Data', capability: 'marketData' }, + { id: 'orders', label: 'Orders', capability: 'orderManagement' }, + { id: 'portfolio', label: 'Portfolio', capability: 'portfolio' }, + { id: 'analytics', label: 'Analytics', capability: 'analytics' }, + { id: 'admin', label: 'Admin', capability: 'admin' } + ]; + + const getAvailableTabs = () => { + if (!selectedVendor) return []; + const capabilities = VendorManager.getVendorCapabilities(selectedVendor); + return tabs.filter(tab => tab.capability === 'admin' || capabilities[tab.capability]); + }; + + const renderTabContent = () => { + const availableTabs = getAvailableTabs(); + const currentTab = availableTabs.find(tab => tab.id === activeTab); + + if (!currentTab) { + return
Feature not available for selected vendor
; + } + + switch (activeTab) { + case 'market': + return ; + case 'orders': + return ; + case 'portfolio': + return ; + case 'analytics': + return ; + case 'admin': + return ; + default: + return
Select a tab
; + } + }; + + return ( +
+
+

Trading Dashboard

+ +
+ + + +
+ + {renderTabContent()} + +
+
+ ); +}; + +const MarketDataTab = ({ vendor }) => { + if (vendor === 'yfinance') { + return ( +
+

Market Data - Yahoo Finance

+ +
+ ); + } + return ( +
+

Market Data - {vendor}

+

Market data functionality for {vendor}

+
+ ); +}; + +const OrdersTab = ({ vendor }) => ( +
+

Order Management - {vendor}

+

Order management functionality for {vendor}

+
+); + +const PortfolioTab = ({ vendor }) => ( +
+

Portfolio - {vendor}

+

Portfolio functionality for {vendor}

+
+); + +const AnalyticsTab = ({ vendor }) => { + if (vendor === 'yfinance') { + return ( +
+

Analytics - Yahoo Finance

+ + +
+ ); + } + return ( +
+

Analytics - {vendor}

+

Analytics functionality for {vendor}

+
+ ); +}; + +const AdminTab = ({ vendor }) => { + const [activeAdmin, setActiveAdmin] = useState('code'); + + return ( +
+

Admin - {vendor}

+
+ + +
+ + {activeAdmin === 'code' && } + {activeAdmin === 'status' && } +
+ ); +}; + +export default TradingDashboard; \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/components/YFinanceComponents.jsx b/BACKUPS/FIRST_PHASE/src/components/YFinanceComponents.jsx new file mode 100644 index 000000000..a9ece5aec --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/components/YFinanceComponents.jsx @@ -0,0 +1,339 @@ +import React, { useState, useEffect } from 'react'; +import VendorManager from '../core/VendorManager'; + +export const YFinanceMarketData = ({ vendor }) => { + const [symbol, setSymbol] = useState('AAPL'); + const [assetType, setAssetType] = useState('stocks'); + const [interval, setInterval] = useState('1d'); + const [period, setPeriod] = useState('1mo'); + const [customStart, setCustomStart] = useState(''); + const [customEnd, setCustomEnd] = useState(''); + const [useCustomRange, setUseCustomRange] = useState(false); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const assetTypes = { + stocks: { label: 'Stocks', examples: 'AAPL, GOOGL, MSFT' }, + forex: { label: 'Forex', examples: 'EURUSD=X, GBPUSD=X, USDJPY=X' }, + crypto: { label: 'Crypto', examples: 'BTC-USD, ETH-USD, ADA-USD' }, + commodities: { label: 'Commodities', examples: 'GC=F, CL=F, SI=F' }, + indices: { label: 'Indices', examples: '^GSPC, ^DJI, ^IXIC' } + }; + + const intervals = ['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1wk', '1mo', '3mo']; + const periods = ['1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max']; + + const fetchData = async () => { + if (!symbol.trim()) return; + + setLoading(true); + setError(null); + + try { + const fetchParams = { + symbol: symbol.toUpperCase(), + interval, + period: useCustomRange ? null : period, + start: useCustomRange ? customStart : null, + end: useCustomRange ? customEnd : null + }; + + const result = useCustomRange || period !== '1d' + ? await VendorManager.executeVendorMethod(vendor, 'getHistoricalData', fetchParams.symbol, fetchParams.interval, fetchParams.period || '1mo') + : await VendorManager.executeVendorMethod(vendor, 'getMarketData', fetchParams.symbol, fetchParams.interval); + + if (result.success) { + setData(result.data); + } else { + setError(result.error); + } + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+
+ + +
+
+ + setSymbol(e.target.value)} + placeholder={assetTypes[assetType].examples} + onKeyPress={(e) => e.key === 'Enter' && fetchData()} + /> +
+
+ +
+
+ + +
+
+ +
+
+ + {useCustomRange ? ( +
+
+ + setCustomStart(e.target.value)} + /> +
+
+ + setCustomEnd(e.target.value)} + /> +
+
+ ) : ( +
+
+ + +
+
+ )} + + +
+ + {error &&
Error: {error}
} + + {data && ( +
+

{data.symbol}

+ {data.price ? ( +
+ ${data.price?.toFixed(2)} + = 0 ? 'positive' : 'negative'}`}> + {data.change >= 0 ? '+' : ''}{data.change?.toFixed(2)} + ({data.changePercent?.toFixed(2)}%) + +
+ ) : null} + {data.data && ( +
+

Historical data: {data.data.length} records

+

Latest: ${data.data[data.data.length - 1]?.close?.toFixed(2)}

+
+ )} +
+ )} +
+ ); +}; + +export const YFinanceAnalytics = ({ vendor }) => { + const [symbol, setSymbol] = useState('AAPL'); + const [timeframe, setTimeframe] = useState('1mo'); + const [analytics, setAnalytics] = useState(null); + const [loading, setLoading] = useState(false); + + const timeframes = ['1d', '5d', '1mo', '3mo', '6mo', '1y', '2y']; + + const fetchAnalytics = async () => { + if (!symbol.trim()) return; + + setLoading(true); + try { + const result = await VendorManager.executeVendorMethod(vendor, 'getAnalytics', symbol.toUpperCase(), timeframe); + if (result.success) { + setAnalytics(result.data); + } + } catch (err) { + console.error('Analytics error:', err); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+ + setSymbol(e.target.value)} + placeholder="Enter symbol for analysis" + onKeyPress={(e) => e.key === 'Enter' && fetchAnalytics()} + /> +
+
+ + +
+ +
+ + {analytics && ( +
+

Analysis for {analytics.symbol} ({timeframe})

+
+
+ + ${analytics.price?.toFixed(2)} +
+
+ + + {analytics.volatility} + +
+
+ + + {analytics.weeklyTrend} + +
+
+ + + {analytics.recommendation} + +
+
+
+ )} +
+ ); +}; + +export const YFinanceBulkDownloader = ({ vendor }) => { + const [symbols, setSymbols] = useState('AAPL,GOOGL,MSFT'); + const [intervals, setIntervals] = useState(['1d']); + const [period, setPeriod] = useState('1mo'); + const [batchSize, setBatchSize] = useState(5); + const [downloading, setDownloading] = useState(false); + const [results, setResults] = useState(null); + + const availableIntervals = ['1m', '5m', '15m', '1h', '1d', '1wk', '1mo']; + const periods = ['1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', 'max']; + + const handleBulkDownload = async () => { + const symbolList = symbols.split(',').map(s => s.trim().toUpperCase()).filter(s => s); + if (symbolList.length === 0) return; + + setDownloading(true); + try { + const result = await VendorManager.executeVendorMethod( + vendor, + 'bulkDownload', + symbolList, + intervals, + { period, batchSize } + ); + setResults(result); + } catch (err) { + console.error('Bulk download error:', err); + } finally { + setDownloading(false); + } + }; + + return ( +
+

Bulk Historical Data Download

+
+
+ + setSymbols(e.target.value)} + placeholder="AAPL,GOOGL,MSFT,BTC-USD,EURUSD=X" + /> +
+
+
+ + +
+
+ + +
+
+ + setBatchSize(parseInt(e.target.value))} + min="1" + max="50" + /> +
+
+
+ + + {results && ( +
+
Download Complete
+

Downloaded data for {Object.keys(results.data || {}).length} symbols

+
+ )} +
+ ); +}; \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/core/BaseVendorAdapter.js b/BACKUPS/FIRST_PHASE/src/core/BaseVendorAdapter.js new file mode 100644 index 000000000..f5f979cd7 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/core/BaseVendorAdapter.js @@ -0,0 +1,51 @@ +export class BaseVendorAdapter { + constructor(name, config = {}) { + this.name = name; + this.config = config; + this.isConnected = false; + } + + getCapabilities() { + return { + marketData: false, + orderManagement: false, + portfolio: false, + analytics: false, + realtime: false + }; + } + + async connect() { + throw new Error('connect() must be implemented by vendor adapter'); + } + + async disconnect() { + this.isConnected = false; + } + + async getMarketData(symbol) { + throw new Error('getMarketData() not implemented'); + } + + async placeOrder(orderData) { + throw new Error('placeOrder() not implemented'); + } + + async getPortfolio() { + throw new Error('getPortfolio() not implemented'); + } + + async getAnalytics(symbol) { + throw new Error('getAnalytics() not implemented'); + } + + handleError(error, context) { + console.error(`${this.name} Error in ${context}:`, error); + return { + success: false, + error: error.message, + vendor: this.name, + context + }; + } +} \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/core/VendorManager.js b/BACKUPS/FIRST_PHASE/src/core/VendorManager.js new file mode 100644 index 000000000..b4bafabc8 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/core/VendorManager.js @@ -0,0 +1,54 @@ +class VendorManager { + constructor() { + this.vendors = new Map(); + this.capabilities = new Map(); + } + + registerVendor(vendorId, vendorAdapter) { + try { + if (!vendorAdapter.name || !vendorAdapter.getCapabilities) { + throw new Error(`Invalid vendor adapter for ${vendorId}`); + } + + this.vendors.set(vendorId, vendorAdapter); + this.capabilities.set(vendorId, vendorAdapter.getCapabilities()); + return true; + } catch (error) { + console.error(`Failed to register vendor ${vendorId}:`, error); + return false; + } + } + + getVendor(vendorId) { + return this.vendors.get(vendorId); + } + + getVendorCapabilities(vendorId) { + return this.capabilities.get(vendorId) || {}; + } + + getAllVendors() { + return Array.from(this.vendors.keys()); + } + + getVendorsWithCapability(capability) { + return Array.from(this.vendors.keys()).filter(vendorId => + this.getVendorCapabilities(vendorId)[capability] + ); + } + + async executeVendorMethod(vendorId, method, ...args) { + try { + const vendor = this.getVendor(vendorId); + if (!vendor || !vendor[method]) { + throw new Error(`Method ${method} not available for vendor ${vendorId}`); + } + return await vendor[method](...args); + } catch (error) { + console.error(`Error executing ${method} for vendor ${vendorId}:`, error); + throw error; + } + } +} + +export default new VendorManager(); \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/index.css b/BACKUPS/FIRST_PHASE/src/index.css new file mode 100644 index 000000000..ec2585e8c --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/BACKUPS/FIRST_PHASE/src/index.jsx b/BACKUPS/FIRST_PHASE/src/index.jsx new file mode 100644 index 000000000..d563c0fb1 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/index.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/BACKUPS/FIRST_PHASE/src/logo.svg b/BACKUPS/FIRST_PHASE/src/logo.svg new file mode 100644 index 000000000..9dfc1c058 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/reportWebVitals.js b/BACKUPS/FIRST_PHASE/src/reportWebVitals.js new file mode 100644 index 000000000..5253d3ad9 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/BACKUPS/FIRST_PHASE/src/setupTests.js b/BACKUPS/FIRST_PHASE/src/setupTests.js new file mode 100644 index 000000000..8f2609b7b --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/BACKUPS/FIRST_PHASE/src/utils/VendorRegistry.js b/BACKUPS/FIRST_PHASE/src/utils/VendorRegistry.js new file mode 100644 index 000000000..cb45320c8 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/utils/VendorRegistry.js @@ -0,0 +1,56 @@ +import VendorManager from '../core/VendorManager'; + +export const registerNewVendor = (vendorId, VendorClass, config = {}) => { + try { + const vendorInstance = new VendorClass(config); + const success = VendorManager.registerVendor(vendorId, vendorInstance); + + if (success) { + console.log(`✅ Vendor ${vendorId} registered successfully`); + return true; + } else { + console.error(`❌ Failed to register vendor ${vendorId}`); + return false; + } + } catch (error) { + console.error(`❌ Error registering vendor ${vendorId}:`, error); + return false; + } +}; + +export const getVendorInfo = (vendorId) => { + const vendor = VendorManager.getVendor(vendorId); + const capabilities = VendorManager.getVendorCapabilities(vendorId); + + return { + exists: !!vendor, + name: vendor?.name || 'Unknown', + capabilities, + isConnected: vendor?.isConnected || false + }; +}; + +export const testVendor = async (vendorId) => { + try { + const vendor = VendorManager.getVendor(vendorId); + if (!vendor) { + return { success: false, error: 'Vendor not found' }; + } + + const connectionResult = await vendor.connect(); + const capabilities = vendor.getCapabilities(); + + return { + success: true, + connection: connectionResult, + capabilities, + vendor: vendor.name + }; + } catch (error) { + return { + success: false, + error: error.message, + vendor: vendorId + }; + } +}; \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/vendors/ExampleVendorA.js b/BACKUPS/FIRST_PHASE/src/vendors/ExampleVendorA.js new file mode 100644 index 000000000..46f8cdd42 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/vendors/ExampleVendorA.js @@ -0,0 +1,76 @@ +import { BaseVendorAdapter } from '../core/BaseVendorAdapter'; + +class ExampleVendorA extends BaseVendorAdapter { + constructor() { + super('Vendor A', { + apiUrl: 'https://api.vendora.com', + apiKey: 'your-api-key' + }); + } + + getCapabilities() { + return { + marketData: true, + orderManagement: true, + portfolio: true, + analytics: false, + realtime: true + }; + } + + async connect() { + try { + this.isConnected = true; + return { success: true }; + } catch (error) { + return this.handleError(error, 'connect'); + } + } + + async getMarketData(symbol) { + try { + return { + success: true, + data: { + symbol, + price: Math.random() * 100, + change: Math.random() * 10 - 5, + volume: Math.floor(Math.random() * 1000000) + } + }; + } catch (error) { + return this.handleError(error, 'getMarketData'); + } + } + + async placeOrder(orderData) { + try { + return { + success: true, + orderId: `VA_${Date.now()}`, + status: 'pending' + }; + } catch (error) { + return this.handleError(error, 'placeOrder'); + } + } + + async getPortfolio() { + try { + return { + success: true, + data: { + totalValue: 50000, + positions: [ + { symbol: 'AAPL', quantity: 10, value: 1500 }, + { symbol: 'GOOGL', quantity: 5, value: 12500 } + ] + } + }; + } catch (error) { + return this.handleError(error, 'getPortfolio'); + } + } +} + +export default ExampleVendorA; \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/vendors/ExampleVendorB.js b/BACKUPS/FIRST_PHASE/src/vendors/ExampleVendorB.js new file mode 100644 index 000000000..66167fc33 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/vendors/ExampleVendorB.js @@ -0,0 +1,66 @@ +import { BaseVendorAdapter } from '../core/BaseVendorAdapter'; + +class ExampleVendorB extends BaseVendorAdapter { + constructor() { + super('Vendor B', { + baseUrl: 'https://vendorb-api.com/v2', + token: 'bearer-token' + }); + } + + getCapabilities() { + return { + marketData: true, + orderManagement: false, + portfolio: false, + analytics: true, + realtime: false + }; + } + + async connect() { + try { + this.isConnected = true; + return { success: true }; + } catch (error) { + return this.handleError(error, 'connect'); + } + } + + async getMarketData(symbol) { + try { + return { + success: true, + data: { + symbol, + price: Math.random() * 200, + bid: Math.random() * 190, + ask: Math.random() * 210, + timestamp: Date.now() + } + }; + } catch (error) { + return this.handleError(error, 'getMarketData'); + } + } + + async getAnalytics(symbol) { + try { + return { + success: true, + data: { + symbol, + rsi: Math.random() * 100, + macd: Math.random() * 5 - 2.5, + sma20: Math.random() * 150, + sma50: Math.random() * 140, + recommendation: ['BUY', 'SELL', 'HOLD'][Math.floor(Math.random() * 3)] + } + }; + } catch (error) { + return this.handleError(error, 'getAnalytics'); + } + } +} + +export default ExampleVendorB; \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/vendors/VendorTemplate.js b/BACKUPS/FIRST_PHASE/src/vendors/VendorTemplate.js new file mode 100644 index 000000000..c411953db --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/vendors/VendorTemplate.js @@ -0,0 +1,141 @@ +import { BaseVendorAdapter } from '../core/BaseVendorAdapter'; + +/** + * Template for creating new vendor adapters + * Copy this file and implement your vendor-specific logic + */ +class VendorTemplate extends BaseVendorAdapter { + constructor(config = {}) { + super('Vendor Name', { + apiUrl: config.apiUrl || '', + apiKey: config.apiKey || '', + ...config + }); + } + + /** + * Define what this vendor can do + * Set to true only for features this vendor supports + */ + getCapabilities() { + return { + marketData: false, + orderManagement: false, + portfolio: false, + analytics: false, + realtime: false + }; + } + + /** + * Connect to vendor API + */ + async connect() { + try { + // TODO: Implement your connection logic + // Example: await fetch(this.config.apiUrl + '/connect') + + this.isConnected = true; + return { success: true }; + } catch (error) { + return this.handleError(error, 'connect'); + } + } + + /** + * Get market data for a symbol + * Only implement if marketData capability is true + */ + async getMarketData(symbol) { + try { + // TODO: Implement your market data API call + // Example: + // const response = await fetch(`${this.config.apiUrl}/market/${symbol}`); + // const data = await response.json(); + + return { + success: true, + data: { + symbol, + price: 0, + change: 0, + volume: 0 + } + }; + } catch (error) { + return this.handleError(error, 'getMarketData'); + } + } + + /** + * Place an order + * Only implement if orderManagement capability is true + */ + async placeOrder(orderData) { + try { + // TODO: Implement your order placement logic + + return { + success: true, + orderId: '', + status: 'pending' + }; + } catch (error) { + return this.handleError(error, 'placeOrder'); + } + } + + /** + * Get portfolio data + * Only implement if portfolio capability is true + */ + async getPortfolio() { + try { + // TODO: Implement your portfolio API call + + return { + success: true, + data: { + totalValue: 0, + positions: [] + } + }; + } catch (error) { + return this.handleError(error, 'getPortfolio'); + } + } + + /** + * Get analytics for a symbol + * Only implement if analytics capability is true + */ + async getAnalytics(symbol) { + try { + // TODO: Implement your analytics API call + + return { + success: true, + data: { + symbol, + indicators: {} + } + }; + } catch (error) { + return this.handleError(error, 'getAnalytics'); + } + } + + /** + * Add any custom methods your vendor needs + */ + async customMethod() { + try { + // Your custom implementation + return { success: true }; + } catch (error) { + return this.handleError(error, 'customMethod'); + } + } +} + +export default VendorTemplate; \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/src/vendors/YFinanceVendor.js b/BACKUPS/FIRST_PHASE/src/vendors/YFinanceVendor.js new file mode 100644 index 000000000..963720653 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/src/vendors/YFinanceVendor.js @@ -0,0 +1,232 @@ +import { BaseVendorAdapter } from '../core/BaseVendorAdapter'; + +class YFinanceVendor extends BaseVendorAdapter { + constructor(config = {}) { + super('Yahoo Finance', { + baseUrl: 'https://query1.finance.yahoo.com/v8/finance/chart/', + intervals: ['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1wk', '1mo', '3mo'], + ...config + }); + this.cache = new Map(); + this.cacheTimeout = 30000; // 30 seconds + } + + getCapabilities() { + return { + marketData: true, + orderManagement: false, + portfolio: false, + analytics: true, + realtime: true + }; + } + + async connect() { + try { + // Test connection with a simple quote + const testResult = await this.getMarketData('AAPL'); + this.isConnected = testResult.success; + return { success: this.isConnected }; + } catch (error) { + return this.handleError(error, 'connect'); + } + } + + async getMarketData(symbol, interval = '1d') { + try { + const cacheKey = `${symbol}_${interval}`; + const cached = this.cache.get(cacheKey); + + if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { + return { success: true, data: cached.data }; + } + + // Use Yahoo Finance API directly (simplified version of the Python script logic) + const url = `${this.config.baseUrl}${symbol}?interval=${interval}&range=1d`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + const result = this.parseYahooResponse(data, symbol); + + // Cache the result + this.cache.set(cacheKey, { + data: result, + timestamp: Date.now() + }); + + return { success: true, data: result }; + } catch (error) { + return this.handleError(error, 'getMarketData'); + } + } + + async getHistoricalData(symbol, interval = '1d', period = '1mo') { + try { + const url = `${this.config.baseUrl}${symbol}?interval=${interval}&range=${period}`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + return { + success: true, + data: this.parseHistoricalData(data, symbol) + }; + } catch (error) { + return this.handleError(error, 'getHistoricalData'); + } + } + + async getAnalytics(symbol) { + try { + // Get multiple timeframes for analytics + const [daily, weekly] = await Promise.all([ + this.getMarketData(symbol, '1d'), + this.getMarketData(symbol, '1wk') + ]); + + if (!daily.success || !weekly.success) { + throw new Error('Failed to fetch data for analytics'); + } + + return { + success: true, + data: { + symbol, + price: daily.data.price, + change: daily.data.change, + changePercent: daily.data.changePercent, + volume: daily.data.volume, + marketCap: daily.data.marketCap, + pe: daily.data.pe, + weeklyTrend: weekly.data.change > 0 ? 'UP' : 'DOWN', + volatility: this.calculateVolatility(daily.data, weekly.data), + recommendation: this.generateRecommendation(daily.data) + } + }; + } catch (error) { + return this.handleError(error, 'getAnalytics'); + } + } + + parseYahooResponse(data, symbol) { + try { + const chart = data.chart?.result?.[0]; + if (!chart) { + throw new Error('Invalid response format'); + } + + const meta = chart.meta; + const quote = chart.indicators?.quote?.[0]; + + return { + symbol, + price: meta.regularMarketPrice || 0, + change: (meta.regularMarketPrice - meta.previousClose) || 0, + changePercent: ((meta.regularMarketPrice - meta.previousClose) / meta.previousClose * 100) || 0, + volume: meta.regularMarketVolume || 0, + marketCap: meta.marketCap || null, + pe: meta.trailingPE || null, + high: quote?.high?.[quote.high.length - 1] || meta.regularMarketDayHigh, + low: quote?.low?.[quote.low.length - 1] || meta.regularMarketDayLow, + open: quote?.open?.[0] || meta.regularMarketDayLow, + previousClose: meta.previousClose || 0, + timestamp: Date.now() + }; + } catch (error) { + throw new Error(`Failed to parse Yahoo response: ${error.message}`); + } + } + + parseHistoricalData(data, symbol) { + try { + const chart = data.chart?.result?.[0]; + if (!chart) { + throw new Error('Invalid historical data format'); + } + + const timestamps = chart.timestamp || []; + const quote = chart.indicators?.quote?.[0] || {}; + + return { + symbol, + data: timestamps.map((ts, i) => ({ + timestamp: new Date(ts * 1000).toISOString(), + open: quote.open?.[i] || null, + high: quote.high?.[i] || null, + low: quote.low?.[i] || null, + close: quote.close?.[i] || null, + volume: quote.volume?.[i] || null + })).filter(item => item.close !== null) + }; + } catch (error) { + throw new Error(`Failed to parse historical data: ${error.message}`); + } + } + + calculateVolatility(dailyData, weeklyData) { + // Simple volatility calculation + const dailyChange = Math.abs(dailyData.changePercent || 0); + const weeklyChange = Math.abs(weeklyData.changePercent || 0); + + if (dailyChange > 5 || weeklyChange > 15) return 'HIGH'; + if (dailyChange > 2 || weeklyChange > 8) return 'MEDIUM'; + return 'LOW'; + } + + generateRecommendation(data) { + const { changePercent, volume, pe } = data; + + let score = 0; + if (changePercent > 2) score += 1; + if (changePercent < -2) score -= 1; + if (volume > 1000000) score += 0.5; + if (pe && pe < 15) score += 0.5; + if (pe && pe > 30) score -= 0.5; + + if (score > 1) return 'BUY'; + if (score < -1) return 'SELL'; + return 'HOLD'; + } + + // Bulk download method (adapted from Python script) + async bulkDownload(symbols, intervals = ['1d'], options = {}) { + try { + const results = {}; + const batchSize = options.batchSize || 10; + + for (let i = 0; i < symbols.length; i += batchSize) { + const batch = symbols.slice(i, i + batchSize); + + for (const symbol of batch) { + for (const interval of intervals) { + try { + const data = await this.getHistoricalData(symbol, interval, options.period || '1mo'); + if (!results[symbol]) results[symbol] = {}; + results[symbol][interval] = data; + } catch (error) { + console.warn(`Failed to download ${symbol} ${interval}:`, error); + } + } + } + + // Rate limiting (like the Python script) + if (i + batchSize < symbols.length) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + + return { success: true, data: results }; + } catch (error) { + return this.handleError(error, 'bulkDownload'); + } + } +} + +export default YFinanceVendor; \ No newline at end of file diff --git a/BACKUPS/FIRST_PHASE/vite.config.js b/BACKUPS/FIRST_PHASE/vite.config.js new file mode 100644 index 000000000..248c2f847 --- /dev/null +++ b/BACKUPS/FIRST_PHASE/vite.config.js @@ -0,0 +1,11 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + test: { + globals: true, + environment: 'jsdom', + }, +}) diff --git a/BACKUPS/SECOND_PHASE/README_SECOND_PHASE.md b/BACKUPS/SECOND_PHASE/README_SECOND_PHASE.md new file mode 100644 index 000000000..5b5303469 --- /dev/null +++ b/BACKUPS/SECOND_PHASE/README_SECOND_PHASE.md @@ -0,0 +1,37 @@ +# SECOND PHASE BACKUP + +**Date:** $(date) +**Status:** LOCKED - Working State with Code Editor + +## Features Added in Second Phase: +- ✅ Code Refactoring Capability +- ✅ Live Code Editor in Admin Tab +- ✅ Version History (5 versions per code section) +- ✅ Edit/View Mode Toggle +- ✅ One-click Revert System +- ✅ Safe Non-destructive Editing + +## All First Phase Features: +- ✅ Vendor Management System +- ✅ YFinance Integration +- ✅ Trading Dashboard with Tabs +- ✅ Market Data Fetching +- ✅ Analytics & Bulk Download +- ✅ Admin Tab with Code Viewer +- ✅ Error Boundaries +- ✅ Fault Isolation + +## New Components: +- src/components/CodeEditor.jsx + +## Restore Instructions: +```bash +# To restore this phase: +rm -rf src/ +cp -r BACKUPS/SECOND_PHASE/src/ ./ +cp BACKUPS/SECOND_PHASE/package.json ./ +cp BACKUPS/SECOND_PHASE/vite.config.js ./ +cp BACKUPS/SECOND_PHASE/index.html ./ +``` + +**⚠️ DO NOT MODIFY THIS BACKUP ⚠️** \ No newline at end of file diff --git a/BACKUPS/SECOND_PHASE/index.html b/BACKUPS/SECOND_PHASE/index.html new file mode 100644 index 000000000..452ca1f32 --- /dev/null +++ b/BACKUPS/SECOND_PHASE/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + React App + + + +
+ + + + diff --git a/BACKUPS/SECOND_PHASE/package.json b/BACKUPS/SECOND_PHASE/package.json new file mode 100644 index 000000000..5e1b6b8c4 --- /dev/null +++ b/BACKUPS/SECOND_PHASE/package.json @@ -0,0 +1,49 @@ +{ + "name": "codespaces-react", + "version": "0.1.0", + "private": true, + "type": "module", + "dependencies": { + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^14.4.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "web-vitals": "^3.1.0" + }, + "overrides": { + "@svgr/webpack": "^8.0.1", + "@adobe/css-tools": "^4.3.1", + "postcss": "^8.4.31" + }, + "scripts": { + "start": "BROWSER=none WDS_SOCKET_PORT=0 vite --port 3000", + "build": "vite build", + "preview": "vite preview", + "test": "vitest" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.7.0", + "jsdom": "^26.1.0", + "vite": "^6.3.6", + "vitest": "^3.0.7" + } +} diff --git a/BACKUPS/SECOND_PHASE/src/App.css b/BACKUPS/SECOND_PHASE/src/App.css new file mode 100644 index 000000000..f7e4a489b --- /dev/null +++ b/BACKUPS/SECOND_PHASE/src/App.css @@ -0,0 +1,488 @@ +.trading-dashboard { + max-width: 1200px; + margin: 0 auto; + padding: 20px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; +} + +.dashboard-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 2px solid #e0e0e0; +} + +.dashboard-header h1 { + margin: 0; + color: #333; + font-size: 2rem; +} + +.vendor-selector { + padding: 8px 16px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + min-width: 150px; +} + +.tab-navigation { + display: flex; + gap: 4px; + margin-bottom: 20px; + border-bottom: 1px solid #e0e0e0; +} + +.tab { + padding: 12px 24px; + border: none; + background: none; + cursor: pointer; + font-size: 14px; + font-weight: 500; + color: #666; + border-bottom: 3px solid transparent; + transition: all 0.2s ease; +} + +.tab:hover { + color: #333; + background-color: #f5f5f5; +} + +.tab.active { + color: #007acc; + border-bottom-color: #007acc; +} + +.dashboard-content { + min-height: 400px; +} + +.tab-content { + padding: 20px; + background: #f9f9f9; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.tab-content h2 { + margin-top: 0; + color: #333; +} + +.error-boundary { + padding: 20px; + background: #fff3cd; + border: 1px solid #ffeaa7; + border-radius: 8px; + text-align: center; +} + +.error-boundary h3 { + color: #856404; + margin-top: 0; +} + +.error-boundary button { + padding: 8px 16px; + background: #007acc; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + margin-top: 10px; +} + +.error-boundary button:hover { + background: #005a9e; +} + +.fetch-controls, .analytics-controls, .bulk-controls { + background: white; + padding: 20px; + border-radius: 8px; + border: 1px solid #e0e0e0; + margin-bottom: 20px; +} + +.control-row { + display: flex; + gap: 15px; + margin-bottom: 15px; + flex-wrap: wrap; +} + +.control-group { + flex: 1; + min-width: 150px; +} + +.control-group label { + display: block; + margin-bottom: 5px; + font-weight: 500; + font-size: 14px; +} + +.control-group input, .control-group select { + width: 100%; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.control-group input[type="checkbox"] { + width: auto; + margin-right: 8px; +} + +.fetch-btn { + background: #007acc; + color: white; + border: none; + padding: 12px 24px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + width: 100%; + margin-top: 10px; +} + +.fetch-btn:disabled { + background: #ccc; + cursor: not-allowed; +} + +.fetch-btn:hover:not(:disabled) { + background: #005a9e; +} + +.quote-display { + background: white; + padding: 20px; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.price-info { + display: flex; + align-items: center; + gap: 15px; + margin: 10px 0; +} + +.price { + font-size: 2rem; + font-weight: bold; + color: #333; +} + +.change.positive { + color: #28a745; +} + +.change.negative { + color: #dc3545; +} + +.historical-summary { + background: #f8f9fa; + padding: 15px; + border-radius: 6px; + margin-top: 15px; +} + +.historical-summary p { + margin: 5px 0; + font-size: 14px; +} + +.analytics-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; + margin-top: 20px; +} + +.metric { + background: white; + padding: 15px; + border-radius: 6px; + border: 1px solid #e0e0e0; + text-align: center; +} + +.metric label { + display: block; + font-size: 12px; + color: #666; + margin-bottom: 5px; +} + +.metric span { + font-size: 16px; + font-weight: bold; +} + +.volatility.high, .recommendation.sell { + color: #dc3545; +} + +.volatility.medium, .recommendation.hold { + color: #ffc107; +} + +.volatility.low, .recommendation.buy, .trend.up { + color: #28a745; +} + +.trend.down { + color: #dc3545; +} + +.bulk-downloader { + margin-top: 30px; + padding: 20px; + background: white; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.download-results { + margin-top: 15px; + padding: 10px; + background: #d4edda; + border: 1px solid #c3e6cb; + border-radius: 4px; + color: #155724; +} + +.admin-nav { + display: flex; + gap: 10px; + margin-bottom: 20px; +} + +.admin-btn { + padding: 8px 16px; + border: 1px solid #ddd; + background: white; + cursor: pointer; + border-radius: 4px; +} + +.admin-btn.active { + background: #007acc; + color: white; + border-color: #007acc; +} + +.code-viewer { + background: white; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.code-tabs { + display: flex; + border-bottom: 1px solid #e0e0e0; +} + +.code-tab { + padding: 12px 20px; + border: none; + background: none; + cursor: pointer; + border-bottom: 3px solid transparent; +} + +.code-tab.active { + background: #f8f9fa; + border-bottom-color: #007acc; +} + +.code-display { + padding: 20px; +} + +.code-block { + background: #f8f9fa; + padding: 15px; + border-radius: 6px; + overflow-x: auto; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 13px; + line-height: 1.4; + border: 1px solid #e9ecef; +} + +.vendor-status { + background: white; + padding: 20px; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +.status-btn { + background: #28a745; + color: white; + border: none; + padding: 10px 20px; + border-radius: 4px; + cursor: pointer; + margin-bottom: 20px; +} + +.status-grid { + display: grid; + gap: 15px; +} + +.status-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background: #f8f9fa; + border-radius: 4px; +} + +.status-item label { + font-weight: 500; +} + +.connected { + color: #28a745; +} + +.disconnected { + color: #dc3545; +} + +.capabilities { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.capability { + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; +} + +.capability.enabled { + background: #d4edda; + color: #155724; +} + +.capability.disabled { + background: #f8d7da; + color: #721c24; +} + +.code-header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #e0e0e0; +} + +.edit-toggle { + padding: 6px 12px; + border: 1px solid #007acc; + background: white; + color: #007acc; + cursor: pointer; + border-radius: 4px; + margin: 10px; + font-size: 12px; +} + +.edit-toggle.active { + background: #007acc; + color: white; +} + +.code-editor { + background: white; +} + +.editor-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 15px; + border-bottom: 1px solid #e0e0e0; + background: #f8f9fa; +} + +.editor-controls { + display: flex; + gap: 8px; +} + +.history-btn, .save-btn { + padding: 4px 8px; + border: 1px solid #ddd; + background: white; + cursor: pointer; + border-radius: 3px; + font-size: 11px; +} + +.save-btn { + background: #28a745; + color: white; + border-color: #28a745; +} + +.code-textarea { + width: 100%; + min-height: 300px; + padding: 15px; + border: none; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 12px; + line-height: 1.4; + resize: vertical; + outline: none; + background: #f8f9fa; +} + +.code-history { + background: #fff3cd; + padding: 10px; + border-bottom: 1px solid #e0e0e0; +} + +.history-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 5px 8px; + margin: 3px 0; + background: white; + border-radius: 3px; + border: 1px solid #e0e0e0; +} + +.history-time { + font-size: 11px; + color: #666; +} + +.revert-btn { + padding: 2px 6px; + background: #ffc107; + color: #212529; + border: none; + border-radius: 2px; + cursor: pointer; + font-size: 10px; +} \ No newline at end of file diff --git a/BACKUPS/SECOND_PHASE/src/App.jsx b/BACKUPS/SECOND_PHASE/src/App.jsx new file mode 100644 index 000000000..f3a1bf21a --- /dev/null +++ b/BACKUPS/SECOND_PHASE/src/App.jsx @@ -0,0 +1,24 @@ +import React, { useEffect } from 'react'; +import './App.css'; +import TradingDashboard from './components/TradingDashboard'; +import VendorManager from './core/VendorManager'; +import ExampleVendorA from './vendors/ExampleVendorA'; +import ExampleVendorB from './vendors/ExampleVendorB'; +import YFinanceVendor from './vendors/YFinanceVendor'; + +function App() { + useEffect(() => { + // Register vendors on app startup + VendorManager.registerVendor('vendorA', new ExampleVendorA()); + VendorManager.registerVendor('vendorB', new ExampleVendorB()); + VendorManager.registerVendor('yfinance', new YFinanceVendor()); + }, []); + + return ( +
+ +
+ ); +} + +export default App; \ No newline at end of file diff --git a/BACKUPS/SECOND_PHASE/src/App.test.jsx b/BACKUPS/SECOND_PHASE/src/App.test.jsx new file mode 100644 index 000000000..5e9824933 --- /dev/null +++ b/BACKUPS/SECOND_PHASE/src/App.test.jsx @@ -0,0 +1,9 @@ +import { expect, test } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeDefined(); +}); diff --git a/BACKUPS/SECOND_PHASE/src/components/AdminComponents.jsx b/BACKUPS/SECOND_PHASE/src/components/AdminComponents.jsx new file mode 100644 index 000000000..953d9581b --- /dev/null +++ b/BACKUPS/SECOND_PHASE/src/components/AdminComponents.jsx @@ -0,0 +1,323 @@ +import React, { useState } from 'react'; +import VendorManager from '../core/VendorManager'; +import { CodeEditor } from './CodeEditor'; + +export const CodeViewer = ({ vendor }) => { + const [activeCode, setActiveCode] = useState('fetch'); + const [editMode, setEditMode] = useState(false); + const [customCode, setCustomCode] = useState({}); + + const codeExamples = { + fetch: { + title: 'Data Fetching Code', + code: `// Market Data Fetch +const fetchMarketData = async (symbol, interval = '1d') => { + const url = \`https://query1.finance.yahoo.com/v8/finance/chart/\${symbol}?interval=\${interval}&range=1d\`; + const response = await fetch(url); + const data = await response.json(); + return parseYahooResponse(data, symbol); +}; + +// Historical Data Fetch +const fetchHistoricalData = async (symbol, interval, period) => { + const url = \`https://query1.finance.yahoo.com/v8/finance/chart/\${symbol}?interval=\${interval}&range=\${period}\`; + const response = await fetch(url); + const data = await response.json(); + return parseHistoricalData(data, symbol); +}; + +// Bulk Download +const bulkDownload = async (symbols, intervals, options) => { + const results = {}; + const batchSize = options.batchSize || 10; + + for (let i = 0; i < symbols.length; i += batchSize) { + const batch = symbols.slice(i, i + batchSize); + + for (const symbol of batch) { + for (const interval of intervals) { + const data = await fetchHistoricalData(symbol, interval, options.period); + if (!results[symbol]) results[symbol] = {}; + results[symbol][interval] = data; + } + } + + // Rate limiting + if (i + batchSize < symbols.length) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + + return results; +};` + }, + parser: { + title: 'Data Parser Code', + code: `// Yahoo Response Parser +const parseYahooResponse = (data, symbol) => { + const chart = data.chart?.result?.[0]; + const meta = chart.meta; + const quote = chart.indicators?.quote?.[0]; + + return { + symbol, + price: meta.regularMarketPrice || 0, + change: (meta.regularMarketPrice - meta.previousClose) || 0, + changePercent: ((meta.regularMarketPrice - meta.previousClose) / meta.previousClose * 100) || 0, + volume: meta.regularMarketVolume || 0, + high: quote?.high?.[quote.high.length - 1] || meta.regularMarketDayHigh, + low: quote?.low?.[quote.low.length - 1] || meta.regularMarketDayLow, + open: quote?.open?.[0] || meta.regularMarketDayLow, + previousClose: meta.previousClose || 0, + timestamp: Date.now() + }; +}; + +// Historical Data Parser +const parseHistoricalData = (data, symbol) => { + const chart = data.chart?.result?.[0]; + const timestamps = chart.timestamp || []; + const quote = chart.indicators?.quote?.[0] || {}; + + return { + symbol, + data: timestamps.map((ts, i) => ({ + timestamp: new Date(ts * 1000).toISOString(), + open: quote.open?.[i] || null, + high: quote.high?.[i] || null, + low: quote.low?.[i] || null, + close: quote.close?.[i] || null, + volume: quote.volume?.[i] || null + })).filter(item => item.close !== null) + }; +};` + }, + analytics: { + title: 'Analytics Code', + code: `// Volatility Calculator +const calculateVolatility = (dailyData, weeklyData) => { + const dailyChange = Math.abs(dailyData.changePercent || 0); + const weeklyChange = Math.abs(weeklyData.changePercent || 0); + + if (dailyChange > 5 || weeklyChange > 15) return 'HIGH'; + if (dailyChange > 2 || weeklyChange > 8) return 'MEDIUM'; + return 'LOW'; +}; + +// Recommendation Generator +const generateRecommendation = (data) => { + const { changePercent, volume, pe } = data; + + let score = 0; + if (changePercent > 2) score += 1; + if (changePercent < -2) score -= 1; + if (volume > 1000000) score += 0.5; + if (pe && pe < 15) score += 0.5; + if (pe && pe > 30) score -= 0.5; + + if (score > 1) return 'BUY'; + if (score < -1) return 'SELL'; + return 'HOLD'; +}; + +// Analytics Fetch +const getAnalytics = async (symbol, timeframe = '1mo') => { + const [daily, weekly] = await Promise.all([ + getMarketData(symbol, '1d'), + getMarketData(symbol, '1wk') + ]); + + return { + symbol, + price: daily.data.price, + change: daily.data.change, + changePercent: daily.data.changePercent, + volume: daily.data.volume, + volatility: calculateVolatility(daily.data, weekly.data), + weeklyTrend: weekly.data.change > 0 ? 'UP' : 'DOWN', + recommendation: generateRecommendation(daily.data) + }; +};` + }, + vendor: { + title: 'Vendor Adapter Code', + code: `// YFinance Vendor Class +class YFinanceVendor extends BaseVendorAdapter { + constructor(config = {}) { + super('Yahoo Finance', { + baseUrl: 'https://query1.finance.yahoo.com/v8/finance/chart/', + intervals: ['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1wk', '1mo', '3mo'], + ...config + }); + this.cache = new Map(); + this.cacheTimeout = 30000; // 30 seconds + } + + getCapabilities() { + return { + marketData: true, + orderManagement: false, + portfolio: false, + analytics: true, + realtime: true + }; + } + + async connect() { + try { + const testResult = await this.getMarketData('AAPL'); + this.isConnected = testResult.success; + return { success: this.isConnected }; + } catch (error) { + return this.handleError(error, 'connect'); + } + } + + async getMarketData(symbol, interval = '1d') { + try { + const cacheKey = \`\${symbol}_\${interval}\`; + const cached = this.cache.get(cacheKey); + + if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { + return { success: true, data: cached.data }; + } + + const url = \`\${this.config.baseUrl}\${symbol}?interval=\${interval}&range=1d\`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(\`HTTP \${response.status}: \${response.statusText}\`); + } + + const data = await response.json(); + const result = this.parseYahooResponse(data, symbol); + + this.cache.set(cacheKey, { + data: result, + timestamp: Date.now() + }); + + return { success: true, data: result }; + } catch (error) { + return this.handleError(error, 'getMarketData'); + } + } +}` + } + }; + + const handleCodeSave = (code) => { + setCustomCode(prev => ({ + ...prev, + [activeCode]: code + })); + }; + + const getCurrentCode = () => { + return customCode[activeCode] || codeExamples[activeCode].code; + }; + + return ( +
+
+
+ {Object.entries(codeExamples).map(([key, example]) => ( + + ))} +
+ +
+ +
+ {editMode ? ( + + ) : ( +
+

{codeExamples[activeCode].title}

+
+              {getCurrentCode()}
+            
+
+ )} +
+
+ ); +}; + +export const VendorStatus = ({ vendor }) => { + const [status, setStatus] = useState(null); + + const checkStatus = async () => { + try { + const vendorInstance = VendorManager.getVendor(vendor); + const capabilities = VendorManager.getVendorCapabilities(vendor); + + setStatus({ + name: vendorInstance?.name || 'Unknown', + connected: vendorInstance?.isConnected || false, + capabilities, + cacheSize: vendorInstance?.cache?.size || 0 + }); + } catch (error) { + setStatus({ error: error.message }); + } + }; + + return ( +
+ + + {status && ( +
+ {status.error ? ( +
Error: {status.error}
+ ) : ( +
+
+ + {status.name} +
+
+ + + {status.connected ? 'Connected' : 'Disconnected'} + +
+
+ + {status.cacheSize} items +
+
+ +
+ {Object.entries(status.capabilities).map(([cap, enabled]) => ( + + {cap}: {enabled ? '✓' : '✗'} + + ))} +
+
+
+ )} +
+ )} +
+ ); +}; \ No newline at end of file diff --git a/BACKUPS/SECOND_PHASE/src/components/CodeEditor.jsx b/BACKUPS/SECOND_PHASE/src/components/CodeEditor.jsx new file mode 100644 index 000000000..c499444bf --- /dev/null +++ b/BACKUPS/SECOND_PHASE/src/components/CodeEditor.jsx @@ -0,0 +1,75 @@ +import React, { useState, useEffect } from 'react'; + +export const CodeEditor = ({ initialCode, title, onSave }) => { + const [code, setCode] = useState(initialCode); + const [history, setHistory] = useState([]); + const [showHistory, setShowHistory] = useState(false); + + useEffect(() => { + setCode(initialCode); + }, [initialCode]); + + const saveCode = () => { + const newVersion = { + code, + timestamp: new Date().toISOString(), + id: Date.now() + }; + + const newHistory = [newVersion, ...history].slice(0, 5); + setHistory(newHistory); + + if (onSave) onSave(code); + }; + + const revertToVersion = (version) => { + setCode(version.code); + setShowHistory(false); + }; + + return ( +
+
+

{title}

+
+ + +
+
+ + {showHistory && ( +
+
Version History (Last 5)
+ {history.length === 0 ? ( +

No saved versions yet

+ ) : ( + history.map((version) => ( +
+ + {new Date(version.timestamp).toLocaleString()} + + +
+ )) + )} +
+ )} + +