onesearch / script.js
jessikat29's picture
OneSearch Technical Specification
11a16a2 verified
// OneSearch Main Application Script
// Application State Management
class OneSearchApp {
constructor() {
this.user = null;
this.currentSearch = null;
this.searchResults = [];
this.trustScores = new Map();
this.walletBalance = 47.23;
this.isAuthenticated = false;
this.init();
}
init() {
this.setupEventListeners();
this.initializeComponents();
this.loadInitialData();
this.setupWebSocketConnection();
console.log('OneSearch app initialized');
}
setupEventListeners() {
// Search functionality
const searchBtn = document.getElementById('search-btn');
const problemInput = document.getElementById('problem-input');
if (searchBtn) {
searchBtn.addEventListener('click', () => this.performSearch());
}
if (problemInput) {
problemInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.performSearch();
}
});
}
// QR Generation
const generateQrBtn = document.getElementById('generate-qr');
if (generateQrBtn) {
generateQrBtn.addEventListener('click', () => this.showQRModal());
}
const createQrBtn = document.getElementById('create-qr-btn');
if (createQrBtn) {
createQrBtn.addEventListener('click', () => this.showQRModal());
}
const closeQrModal = document.getElementById('close-qr-modal');
if (closeQrModal) {
closeQrModal.addEventListener('click', () => this.hideQRModal());
}
// Authentication
const authButton = document.getElementById('auth-button');
if (authButton) {
authButton.addEventListener('click', () => this.handleAuth());
}
// Navigation
this.setupNavigation();
}
setupNavigation() {
// Smooth scrolling for navigation links
document.querySelectorAll('a[href^="#"]').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const target = document.querySelector(link.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
}
async performSearch() {
const problemInput = document.getElementById('problem-input');
const query = problemInput?.value.trim();
if (!query) {
this.showNotification('Please enter a problem description', 'warning');
return;
}
try {
this.setLoading(true);
// Simulate AI-powered problem interpretation
const searchResults = await this.interpretProblem(query);
this.searchResults = searchResults;
this.currentSearch = query;
this.updateMindMap(searchResults);
this.displaySearchResults(searchResults);
this.showNotification('Search completed successfully', 'success');
} catch (error) {
console.error('Search error:', error);
this.showNotification('Search failed. Please try again.', 'error');
} finally {
this.setLoading(false);
}
}
async interpretProblem(query) {
// Simulate AI/ML interpretation with mock data
await new Promise(resolve => setTimeout(resolve, 1500)); // Simulate API call
const mockProducts = [
{
id: '1',
title: 'Stackable Storage Containers Set',
description: 'Apex Kitchen Solutions - Set of 6 BPA-free containers with airtight seals',
merchant: 'Apex Kitchen Solutions',
category: 'Storage Solutions',
price: 32.99,
currency: 'USD',
score: 82.6,
scoreBreakdown: {
recommenderReputation: 0.68,
provenance: 0.95,
usageDuration: 0.6,
conversionRate: 0.3,
afterSales: 0.7,
locality: 0.9,
costSatisfaction: 0.64,
qualityReviews: 0.92
},
reviewCount: 124,
averageRating: 4.2,
qrConversions: 23,
wageTransparency: true,
livingWageCompliant: true,
tags: ['kitchen', 'storage', 'organization', 'space-saving']
},
{
id: '2',
title: 'Modular Cabinet System',
description: 'FlexStorage Pro - Adjustable cabinet system for small spaces',
merchant: 'FlexStorage Pro',
category: 'Storage Solutions',
price: 89.99,
currency: 'USD',
score: 78.3,
scoreBreakdown: {
recommenderReputation: 0.72,
provenance: 0.88,
usageDuration: 0.75,
conversionRate: 0.25,
afterSales: 0.65,
locality: 0.85,
costSatisfaction: 0.58,
qualityReviews: 0.85
},
reviewCount: 89,
averageRating: 4.0,
qrConversions: 18,
wageTransparency: false,
livingWageCompliant: false,
tags: ['kitchen', 'storage', 'cabinet', 'modular']
},
{
id: '3',
title: 'Magnetic Organization System',
description: 'MagOrganize - Magnetic strips and containers for vertical storage',
merchant: 'MagOrganize Co',
category: 'Storage Solutions',
price: 24.99,
currency: 'USD',
score: 75.8,
scoreBreakdown: {
recommenderReputation: 0.65,
provenance: 0.82,
usageDuration: 0.55,
conversionRate: 0.4,
afterSales: 0.72,
locality: 0.9,
costSatisfaction: 0.71,
qualityReviews: 0.88
},
reviewCount: 156,
averageRating: 4.1,
qrConversions: 31,
wageTransparency: true,
livingWageCompliant: true,
tags: ['kitchen', 'storage', 'magnetic', 'vertical']
}
];
return mockProducts;
}
updateMindMap(results) {
const mindMapContainer = document.getElementById('mindmap-container');
if (!mindMapContainer || !window.MindMap) return;
// Prepare mind map data
const mindMapData = {
nodes: [
{ id: 'root', label: this.currentSearch, level: 0 },
{ id: 'category', label: 'Storage Solutions', level: 1 },
{ id: 'budget', label: '$25-$90', level: 1 },
{ id: 'size', label: 'Small Space', level: 1 }
],
edges: [
{ from: 'root', to: 'category' },
{ from: 'root', to: 'budget' },
{ from: 'root', to: 'size' }
]
};
// Add product nodes
results.forEach((product, index) => {
const nodeId = `product_${product.id}`;
mindMapData.nodes.push({
id: nodeId,
label: product.title,
level: 2,
score: product.score
});
mindMapData.edges.push({ from: 'category', to: nodeId });
});
window.MindMap.render(mindMapContainer, mindMapData);
}
displaySearchResults(results) {
const resultsContainer = document.getElementById('search-results');
if (!resultsContainer) return;
resultsContainer.innerHTML = '';
results.forEach(product => {
const resultElement = this.createResultElement(product);
resultsContainer.appendChild(resultElement);
});
}
createResultElement(product) {
const result = document.createElement('div');
result.className = 'search-result-item';
result.innerHTML = `
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-3 mb-2">
<h4 class="text-lg font-semibold text-gray-900">${product.title}</h4>
${product.wageTransparency ? '<span class="px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">Wage Transparent</span>' : ''}
${product.livingWageCompliant ? '<span class="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">Living Wage</span>' : ''}
</div>
<p class="text-gray-600 mb-3">${product.description}</p>
<div class="flex items-center gap-6 text-sm text-gray-500 mb-3">
<span>by ${product.merchant}</span>
<span class="flex items-center gap-1">
<i data-feather="star" class="h-4 w-4 fill-current text-yellow-400"></i>
${product.averageRating} (${product.reviewCount} reviews)
</span>
<span class="flex items-center gap-1">
<i data-feather="share-2" class="h-4 w-4"></i>
${product.qrConversions} recommendations
</span>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-3">
<div class="trust-score">
<div class="text-xs text-gray-600">Recommender</div>
<div class="font-medium">${Math.round(product.scoreBreakdown.recommenderReputation * 100)}%</div>
<div class="trust-score-bar">
<div class="trust-score-fill trust-score-good" style="width: ${product.scoreBreakdown.recommenderReputation * 100}%"></div>
</div>
</div>
<div class="trust-score">
<div class="text-xs text-gray-600">Provenance</div>
<div class="font-medium">${Math.round(product.scoreBreakdown.provenance * 100)}%</div>
<div class="trust-score-bar">
<div class="trust-score-fill trust-score-excellent" style="width: ${product.scoreBreakdown.provenance * 100}%"></div>
</div>
</div>
<div class="trust-score">
<div class="text-xs text-gray-600">Reviews</div>
<div class="font-medium">${Math.round(product.scoreBreakdown.qualityReviews * 100)}%</div>
<div class="trust-score-bar">
<div class="trust-score-fill trust-score-excellent" style="width: ${product.scoreBreakdown.qualityReviews * 100}%"></div>
</div>
</div>
<div class="trust-score">
<div class="text-xs text-gray-600">Cost Satisfaction</div>
<div class="font-medium">${Math.round(product.scoreBreakdown.costSatisfaction * 100)}%</div>
<div class="trust-score-bar">
<div class="trust-score-fill trust-score-good" style="width: ${product.scoreBreakdown.costSatisfaction * 100}%"></div>
</div>
</div>
</div>
</div>
<div class="text-right ml-6">
<div class="result-score mb-2">
<div class="score-circle ${this.getScoreColorClass(product.score)}">${product.score}</div>
<span class="text-sm text-gray-600">Trust Score</span>
</div>
<div class="text-2xl font-bold text-gray-900 mb-4">$${product.price}</div>
<div class="space-y-2">
<button class="w-full bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md text-sm font-medium">
Learn More
</button>
<button class="w-full bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-md text-sm font-medium">
Add to Compare
</button>
</div>
</div>
</div>
`;
// Replace feather icons
setTimeout(() => {
if (window.feather) {
window.feather.replace();
}
}, 0);
return result;
}
getScoreColorClass(score) {
if (score >= 80) return 'trust-score-excellent';
if (score >= 70) return 'trust-score-good';
if (score >= 60) return 'trust-score-average';
return 'trust-score-poor';
}
showQRModal() {
const modal = document.getElementById('qr-modal');
if (modal) {
modal.classList.remove('hidden');
modal.classList.add('fade-in');
}
}
hideQRModal() {
const modal = document.getElementById('qr-modal');
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('fade-in');
}
}
handleAuth() {
if (this.isAuthenticated) {
this.logout();
} else {
this.login();
}
}
async login() {
// Simulate OAuth login
try {
this.setLoading(true);
await new Promise(resolve => setTimeout(resolve, 1000));
this.user = {
id: 'user_123',
displayName: 'Demo User',
profileTrust: 0.94,
verifiedLevel: 'merchant-verified',
walletAccountId: 'wallet_456'
};
this.isAuthenticated = true;
this.updateAuthUI();
this.showNotification('Successfully signed in', 'success');
} catch (error) {
this.showNotification('Login failed', 'error');
} finally {
this.setLoading(false);
}
}
logout() {
this.user = null;
this.isAuthenticated = false;
this.updateAuthUI();
this.showNotification('Signed out successfully', 'info');
}
updateAuthUI() {
const authButton = document.getElementById('auth-button');
const generateQrButton = document.getElementById('generate-qr');
if (this.isAuthenticated) {
authButton.textContent = 'Sign Out';
authButton.className = 'bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-md text-sm font-medium';
if (generateQrButton) {
generateQrButton.classList.remove('hidden');
}
} else {
authButton.textContent = 'Sign In';
authButton.className = 'bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md text-sm font-medium';
if (generateQrButton) {
generateQrButton.classList.add('hidden');
}
}
}
setupWebSocketConnection() {
// Simulate WebSocket connection for real-time updates
if ('WebSocket' in window) {
// In a real implementation, this would connect to the actual WebSocket server
console.log('WebSocket support detected');
}
}
initializeComponents() {
// Initialize trust score component
if (window.TrustScore) {
window.TrustScore.init();
}
// Initialize review system
if (window.ReviewSystem) {
window.ReviewSystem.init();
}
// Initialize wallet component
if (window.Wallet) {
window.Wallet.init();
}
}
loadInitialData() {
// Load initial sample data
this.updateTrustScores();
this.loadSampleReviews();
}
updateTrustScores() {
// Simulate trust score updates
const scores = [82.6, 78.3, 75.8, 89.2, 71.5];
scores.forEach((score, index) => {
this.trustScores.set(`product_${index + 1}`, score);
});
}
loadSampleReviews() {
// Sample reviews are already in the HTML
// This method can be used to load dynamic review data
}
setLoading(loading) {
const searchBtn = document.getElementById('search-btn');
if (loading) {
searchBtn.disabled = true;
searchBtn.innerHTML = '<span class="spinner"></span> Searching...';
} else {
searchBtn.disabled = false;
searchBtn.innerHTML = 'Search';
}
}
showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transform transition-all duration-300 translate-x-full`;
const bgColor = {
success: 'bg-green-500',
error: 'bg-red-500',
warning: 'bg-yellow-500',
info: 'bg-blue-500'
}[type];
notification.classList.add(bgColor);
notification.innerHTML = `
<div class="flex items-center text-white">
<span class="flex-1">${message}</span>
<button class="ml-4 text-white hover:text-gray-200" onclick="this.parentElement.parentElement.remove()">
<i data-feather="x" class="h-4 w-4"></i>
</button>
</div>
`;
document.body.appendChild(notification);
// Trigger animation
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// Auto remove after 5 seconds
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 300);
}, 5000);
// Replace icons
setTimeout(() => {
if (window.feather) {
window.feather.replace();
}
}, 0);
}
}
// Initialize application when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
// Initialize the main app
window.OneSearchApp = new OneSearchApp();
// Setup global error handling
window.addEventListener('error', (event) => {
console.error('Global error:', event.error);
if (window.OneSearchApp) {
window.OneSearchApp.showNotification('An unexpected error occurred', 'error');
}
});
// Setup unhandled promise rejection handling
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
if (window.OneSearchApp) {
window.OneSearchApp.showNotification('A network error occurred', 'error');
}
});
});
// Export for use in other components
if (typeof module !== 'undefined' && module.exports) {
module.exports = OneSearchApp;
}