onesearch / components /Wallet.js
jessikat29's picture
OneSearch Technical Specification
11a16a2 verified
Raw
History Blame Contribute Delete
22.1 kB
// Wallet Component for Trust Economy
class Wallet {
static init() {
console.log('Wallet component initialized');
this.setupWalletInterface();
this.loadWalletData();
this.setupRealTimeUpdates();
}
static setupWalletInterface() {
// Setup wallet interaction handlers
const walletButtons = document.querySelectorAll('[data-wallet-action]');
walletButtons.forEach(button => {
button.addEventListener('click', (e) => {
const action = e.target.dataset.walletAction;
this.handleWalletAction(action);
});
});
}
static handleWalletAction(action) {
switch (action) {
case 'view-transactions':
this.showTransactionHistory();
break;
case 'withdraw':
this.showWithdrawModal();
break;
case 'add-payment-method':
this.showPaymentMethodModal();
break;
default:
console.log('Unknown wallet action:', action);
}
}
static async processReferralReward(qrId, conversionData) {
try {
// Calculate reward based on conversion value and recommender trust
const baseReward = this.calculateBaseReward(conversionData.salePrice);
const trustMultiplier = this.getTrustMultiplier(conversionData.recommenderTrust);
const wageBonus = this.getWageTransparencyBonus(conversionData.merchantId);
const totalReward = baseReward * trustMultiplier * wageBonus;
const reward = {
id: this.generateTransactionId(),
type: 'referral_reward',
amount: totalReward,
currency: 'USD',
status: 'pending_maturation',
qrId,
conversionId: conversionData.id,
recipientId: conversionData.recommenderId,
metadata: {
baseReward,
trustMultiplier,
wageBonus,
salePrice: conversionData.salePrice
},
createdAt: new Date().toISOString(),
maturationDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString() // 7 days
};
// Store pending reward
this.storePendingReward(reward);
return { success: true, reward };
} catch (error) {
console.error('Referral reward processing failed:', error);
return { success: false, error: error.message };
}
}
static calculateBaseReward(salePrice) {
// Base reward calculation: 5-10% of sale price
const rate = Math.max(0.05, Math.min(0.10, salePrice / 1000));
return salePrice * rate;
}
static getTrustMultiplier(trustLevel) {
// Trust level affects reward multiplier
const multipliers = {
'low': 0.8,
'medium': 1.0,
'high': 1.2,
'verified': 1.5
};
return multipliers[trustLevel] || 1.0;
}
static getWageTransparencyBonus(merchantId) {
// Bonus for supporting wage-transparent merchants
// In real implementation, this would check merchant's wage transparency score
const wageTransparent = this.isWageTransparent(merchantId);
return wageTransparent ? 1.02 : 1.0; // 2% bonus
}
static isWageTransparent(merchantId) {
// Check if merchant meets wage transparency requirements
const transparentMerchants = JSON.parse(localStorage.getItem('wageTransparentMerchants') || '[]');
return transparentMerchants.includes(merchantId);
}
static async processReviewBonus(reviewId, bonusAmount) {
const review = this.getReviewById(reviewId);
if (!review) {
throw new Error('Review not found');
}
// Check if review meets bonus criteria
if (!this.isReviewBonusEligible(review)) {
throw new Error('Review not eligible for bonus');
}
const bonus = {
id: this.generateTransactionId(),
type: 'review_bonus',
amount: bonusAmount,
currency: 'USD',
status: 'completed',
reviewId,
recipientId: review.userId,
metadata: {
reviewRating: review.rating,
dimensions: review.dimensions,
maturityDays: this.getDaysSinceReview(review.createdAt)
},
createdAt: new Date().toISOString()
};
this.addToWallet(review.userId, bonus.amount);
this.storeTransaction(bonus);
return { success: true, bonus };
}
static isReviewBonusEligible(review) {
const maturityDays = this.getDaysSinceReview(review.createdAt);
const hasMinimumRating = review.rating >= 4;
const hasDetailedContent = review.content && review.content.length >= 100;
const hasAllDimensions = Object.keys(review.dimensions || {}).length >= 3;
return maturityDays >= 14 && hasMinimumRating && hasDetailedContent && hasAllDimensions;
}
static getDaysSinceReview(createdAt) {
return Math.floor((new Date() - new Date(createdAt)) / (1000 * 60 * 60 * 24));
}
static async processDisputeRefund(disputeData) {
// Handle refund processing due to disputes
const refund = {
id: this.generateTransactionId(),
type: 'dispute_refund',
amount: -Math.abs(disputeData.refundAmount),
currency: 'USD',
status: 'completed',
originalTransactionId: disputeData.originalTxId,
recipientId: disputeData.userId,
metadata: {
disputeReason: disputeData.reason,
resolvedAt: new Date().toISOString(),
merchantId: disputeData.merchantId
},
createdAt: new Date().toISOString()
};
// Subtract from merchant payout if applicable
if (disputeData.merchantId) {
await this.adjustMerchantPayout(disputeData.merchantId, refund.amount);
}
// Add back to user wallet
this.addToWallet(disputeData.userId, Math.abs(refund.amount));
this.storeTransaction(refund);
// Trigger score recalculation due to refund
this.triggerScoreRecalculation(disputeData.productId);
return { success: true, refund };
}
static async adjustMerchantPayout(merchantId, adjustmentAmount) {
// Adjust merchant payout for refunds/disputes
const merchantPayouts = JSON.parse(localStorage.getItem(`merchantPayouts_${merchantId}`) || '[]');
const adjustment = {
id: this.generateTransactionId(),
type: 'payout_adjustment',
amount: adjustmentAmount,
currency: 'USD',
status: 'pending',
merchantId,
reason: 'dispute_refund',
createdAt: new Date().toISOString()
};
merchantPayouts.push(adjustment);
localStorage.setItem(`merchantPayouts_${merchantId}`, JSON.stringify(merchantPayouts));
}
static addToWallet(userId, amount) {
const wallets = JSON.parse(localStorage.getItem('wallets') || '{}');
if (!wallets[userId]) {
wallets[userId] = {
balance: 0,
transactions: [],
createdAt: new Date().toISOString()
};
}
wallets[userId].balance += amount;
wallets[userId].lastUpdated = new Date().toISOString();
localStorage.setItem('wallets', JSON.stringify(wallets));
}
static getWalletBalance(userId) {
const wallets = JSON.parse(localStorage.getItem('wallets') || '{}');
return wallets[userId]?.balance || 0;
}
static storeTransaction(transaction) {
const allTransactions = JSON.parse(localStorage.getItem('allTransactions') || '[]');
allTransactions.push(transaction);
localStorage.setItem('allTransactions', JSON.stringify(allTransactions));
}
static storePendingReward(reward) {
const pendingRewards = JSON.parse(localStorage.getItem('pendingRewards') || '[]');
pendingRewards.push(reward);
localStorage.setItem('pendingRewards', JSON.stringify(pendingRewards));
}
static checkPendingMaturation() {
const pendingRewards = JSON.parse(localStorage.getItem('pendingRewards') || '[]');
const now = new Date();
pendingRewards.forEach(reward => {
if (new Date(reward.maturationDate) <= now && reward.status === 'pending_maturation') {
this.matureReward(reward);
}
});
}
static matureReward(reward) {
// Move pending reward to completed wallet transaction
reward.status = 'completed';
reward.maturedAt = new Date().toISOString();
this.addToWallet(reward.recipientId, reward.amount);
this.storeTransaction(reward);
// Update pending rewards list
const pendingRewards = JSON.parse(localStorage.getItem('pendingRewards') || '[]');
const updatedRewards = pendingRewards.filter(r => r.id !== reward.id);
localStorage.setItem('pendingRewards', JSON.stringify(updatedRewards));
// Notify user
if (window.OneSearchApp) {
window.OneSearchApp.showNotification(
`Referral reward of $${reward.amount.toFixed(2)} has matured`,
'success'
);
}
}
static loadWalletData() {
const userId = window.OneSearchApp?.user?.id;
if (!userId) return;
// Initialize demo wallet data if not exists
const wallets = JSON.parse(localStorage.getItem('wallets') || '{}');
if (!wallets[userId]) {
wallets[userId] = {
balance: 47.23,
transactions: [
{
id: 'tx_1',
type: 'referral_reward',
amount: 3.45,
currency: 'USD',
status: 'completed',
description: 'Kitchen organizer purchase',
createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString()
},
{
id: 'tx_2',
type: 'review_bonus',
amount: 1.20,
currency: 'USD',
status: 'completed',
description: 'Verified product review',
createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString()
}
],
createdAt: new Date().toISOString()
};
localStorage.setItem('wallets', JSON.stringify(wallets));
}
this.updateWalletDisplay(userId);
}
static updateWalletDisplay(userId) {
const wallets = JSON.parse(localStorage.getItem('wallets') || '{}');
const wallet = wallets[userId];
if (!wallet) return;
// Update balance display
const balanceElements = document.querySelectorAll('.wallet-balance');
balanceElements.forEach(el => {
el.textContent = `$${wallet.balance.toFixed(2)}`;
});
// Update transaction history
this.updateTransactionDisplay(wallet.transactions);
}
static updateTransactionDisplay(transactions) {
const container = document.querySelector('#wallet .space-y-3');
if (!container) return;
// Clear existing transactions
container.innerHTML = '';
// Add current transactions
transactions.forEach(tx => {
const txElement = this.createTransactionElement(tx);
container.appendChild(txElement);
});
}
static createTransactionElement(transaction) {
const element = document.createElement('div');
element.className = 'flex justify-between items-center p-3 border rounded-lg';
const typeColors = {
'referral_reward': 'text-green-600',
'review_bonus': 'text-green-600',
'dispute_refund': 'text-blue-600',
'withdrawal': 'text-red-600'
};
const iconMap = {
'referral_reward': 'share-2',
'review_bonus': 'star',
'dispute_refund': 'rotate-ccw',
'withdrawal': 'arrow-up'
};
const timeAgo = this.getTimeAgo(transaction.createdAt);
const amountColor = transaction.amount >= 0 ? 'text-green-600' : 'text-red-600';
element.innerHTML = `
<div>
<div class="text-sm font-medium">${this.getTransactionDescription(transaction)}</div>
<div class="text-xs text-gray-600">${timeAgo}</div>
</div>
<div class="text-right">
<div class="font-medium ${amountColor}">${transaction.amount >= 0 ? '+' : ''}$${Math.abs(transaction.amount).toFixed(2)}</div>
<div class="text-xs text-gray-500">${transaction.status}</div>
</div>
`;
return element;
}
static getTransactionDescription(transaction) {
const descriptions = {
'referral_reward': 'Referral Reward',
'review_bonus': 'Review Bonus',
'dispute_refund': 'Refund',
'withdrawal': 'Withdrawal'
};
return descriptions[transaction.type] || transaction.type;
}
static getTimeAgo(timestamp) {
const now = new Date();
const time = new Date(timestamp);
const diffInMs = now - time;
const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24));
const diffInHours = Math.floor(diffInMs / (1000 * 60 * 60));
if (diffInDays > 0) {
return `${diffInDays} day${diffInDays > 1 ? 's' : ''} ago`;
} else if (diffInHours > 0) {
return `${diffInHours} hour${diffInHours > 1 ? 's' : ''} ago`;
} else {
return 'Just now';
}
}
static setupRealTimeUpdates() {
// Check for matured rewards every minute
setInterval(() => {
this.checkPendingMaturation();
}, 60000);
// Update display every 30 seconds
setInterval(() => {
const userId = window.OneSearchApp?.user?.id;
if (userId) {
this.updateWalletDisplay(userId);
}
}, 30000);
}
static showTransactionHistory() {
// Show detailed transaction history modal
const userId = window.OneSearchApp?.user?.id;
if (!userId) return;
const wallets = JSON.parse(localStorage.getItem('wallets') || '{}');
const transactions = wallets[userId]?.transactions || [];
const modal = this.createTransactionHistoryModal(transactions);
document.body.appendChild(modal);
}
static createTransactionHistoryModal(transactions) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50';
modal.innerHTML = `
<div class="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-96 overflow-y-auto">
<div class="px-6 py-4 border-b">
<div class="flex justify-between items-center">
<h3 class="text-lg font-semibold">Transaction History</h3>
<button class="text-gray-400 hover:text-gray-600" onclick="this.closest('.fixed').remove()">
<i data-feather="x" class="h-6 w-6"></i>
</button>
</div>
</div>
<div class="p-6">
<div class="space-y-4">
${transactions.map(tx => this.createDetailedTransactionElement(tx)).join('')}
</div>
</div>
</div>
`;
setTimeout(() => {
if (window.feather) {
window.feather.replace();
}
}, 0);
return modal;
}
static createDetailedTransactionElement(transaction) {
return `
<div class="border rounded-lg p-4">
<div class="flex justify-between items-start mb-2">
<div>
<div class="font-medium">${this.getTransactionDescription(transaction)}</div>
<div class="text-sm text-gray-600">${new Date(transaction.createdAt).toLocaleString()}</div>
</div>
<div class="text-right">
<div class="font-bold ${transaction.amount >= 0 ? 'text-green-600' : 'text-red-600'}">
${transaction.amount >= 0 ? '+' : ''}$${Math.abs(transaction.amount).toFixed(2)}
</div>
<div class="text-xs text-gray-500">${transaction.status}</div>
</div>
</div>
${transaction.description ? `<div class="text-sm text-gray-700">${transaction.description}</div>` : ''}
</div>
`;
}
static showWithdrawModal() {
// Show withdrawal modal
const userId = window.OneSearchApp?.user?.id;
const balance = this.getWalletBalance(userId);
if (balance < 10) {
if (window.OneSearchApp) {
window.OneSearchApp.showNotification('Minimum withdrawal amount is $10.00', 'warning');
}
return;
}
const modal = this.createWithdrawModal(balance);
document.body.appendChild(modal);
}
static createWithdrawModal(balance) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50';
modal.innerHTML = `
<div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
<div class="px-6 py-4 border-b">
<div class="flex justify-between items-center">
<h3 class="text-lg font-semibold">Withdraw Funds</h3>
<button class="text-gray-400 hover:text-gray-600" onclick="this.closest('.fixed').remove()">
<i data-feather="x" class="h-6 w-6"></i>
</button>
</div>
</div>
<div class="p-6">
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Available Balance</label>
<div class="text-2xl font-bold">$${balance.toFixed(2)}</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Withdrawal Amount</label>
<input type="number" class="w-full border border-gray-300 rounded-md px-3 py-2"
min="10" max="${balance}" step="0.01" placeholder="Enter amount">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Withdrawal Method</label>
<select class="w-full border border-gray-300 rounded-md px-3 py-2">
<option>Bank Transfer</option>
<option>PayPal</option>
<option>Check</option>
</select>
</div>
</div>
</div>
<div class="px-6 py-4 border-t bg-gray-50">
<div class="flex space-x-3">
<button class="flex-1 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50">
Cancel
</button>
<button class="flex-1 px-4 py-2 text-sm font-medium text-white bg-blue-500 border border-transparent rounded-md hover:bg-blue-600">
Withdraw
</button>
</div>
</div>
</div>
`;
return modal;
}
static generateTransactionId() {
return 'tx_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
}
static getReviewById(reviewId) {
const reviews = JSON.parse(localStorage.getItem('reviews') || '[]');
return reviews.find(review => review.id === reviewId);
}
static triggerScoreRecalculation(productId) {
// Trigger trust score recalculation due to refund/dispute
const event = new CustomEvent('scoreRecalculationRequested', {
detail: { productId, reason: 'dispute_refund' }
});
document.dispatchEvent(event);
}
static exportWalletData(userId) {
// Export wallet data for user records
const wallets = JSON.parse(localStorage.getItem('wallets') || '{}');
const wallet = wallets[userId];
return {
userId,
balance: wallet?.balance || 0,
transactions: wallet?.transactions || [],
exportDate: new Date().toISOString(),
walletVersion: '1.0'
};
}
}
// Export for use in main app
if (typeof window !== 'undefined') {
window.Wallet = Wallet;
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = Wallet;
}