طيب شوف بقا ايه التحديثات ولا ايه الاضافات اللي ناقصه عشان النظام يكتمل
دي صفحه الاشتراكات
/home/mark/cv/frontend/public/subscription.html
Subscription Plans - MGZon
Choose Your Plan
Unlock premium features and take your portfolio to the next level
Frequently Asked Questions
❓ Can I change my plan later?
Yes, you can upgrade or downgrade your plan at any time from your account settings.
❓ Is there a free trial?
Some plans include a free trial. Check the plan details above.
❓ What payment methods do you accept?
We accept bank transfers, mobile wallets, and online payments. All methods are shown during checkout.
❓ Can I cancel my subscription?
Yes, you can cancel anytime. Your access will continue until the end of the billing period.
Complete Payment
Loading payment methods...
ودي صفحه ال
checkout
/home/mark/cv/frontend/public/checkout.html
Checkout - MGZon Store
Purchasing from
Loading...
Additional Notes
Optional - Any special instructions for the seller?
Payment Method
Select how you want to pay
Loading payment methods...
By placing order, you agree to the seller's terms.
Payment is made directly to the seller.
ودي صفحه المتجر
/home/mark/cv/frontend/public/shop.html
Store - MGZon
و
/home/mark/cv/frontend/public/product.html
Product Details - MGZon Store
Loading product details...
Write a Review
Product Not Found
The product you're looking for doesn't exist or has been removed.
Back to Home
و
order-details.html
Order Details - MGZon
Payment Information
Payment Method:
Transaction ID:
Update Order Status
Order Not Found
The order you're looking for doesn't exist or you don't have permission to view it.
View My Orders
وفي
/home/mark/cv/frontend/public/subscription-details.html
Subscription Details - MGZon
Loading subscription details...
Subscription Not Found
View Plans →
وفي الادمن
Subscription Plans
Payment Methods
Add Payment Method
Subscriptions Management
Subscription Earnings
Monthly Revenue
Subscription Settings
و
// ============================================
// SUBSCRIPTION PLANS MANAGEMENT
// ============================================
async function loadPlans() {
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscription/plans`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (response.ok) {
const data = await response.json();
const plans = data.plans || [];
renderPlansGrid(plans);
// Update stats
document.getElementById('total-plans').textContent = plans.length;
document.getElementById('active-plans').textContent = plans.filter(p => p.isActive).length;
// Get total subscribers
const subsResponse = await fetch(`${window.ENV.API_URL}/api/admin/subscriptions?status=active`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (subsResponse.ok) {
const subsData = await subsResponse.json();
document.getElementById('total-subscribers').textContent = subsData.pagination?.total || 0;
}
}
} catch (error) {
console.error('Error loading plans:', error);
}
}
function renderPlansGrid(plans) {
const container = document.getElementById('plans-container');
if (!container) return;
if (!plans || plans.length === 0) {
container.innerHTML = 'No plans created yet. Click "Add Plan" to get started.
';
return;
}
container.innerHTML = plans.map(plan => {
const currencySymbol = plan.currencySymbol || (plan.currency === 'USD' ? '$' : plan.currency === 'EUR' ? '€' : plan.currency === 'GBP' ? '£' : 'ج.م');
const priceDisplay = plan.price === 0 ? 'Free' : `${currencySymbol}${plan.price}/${plan.duration === 'monthly' ? 'mo' : plan.duration === 'yearly' ? 'yr' : 'once'}`;
// Get feature list
const features = [];
if (plan.features.storeEnabled) features.push('🛒 Store');
if (plan.features.pageBuilderEnabled) features.push('🎨 Page Builder');
if (plan.features.customDomain) features.push('🌐 Custom Domain');
if (plan.features.analyticsEnabled) features.push('📊 Analytics');
if (plan.features.prioritySupport) features.push('⭐ Priority Support');
if (plan.features.maxProducts > 0) features.push(`📦 ${plan.features.maxProducts === 0 ? 'Unlimited' : plan.features.maxProducts} Products`);
const badgeClass = plan.badge === 'popular' ? 'bg-orange-500' : plan.badge === 'best-value' ? 'bg-green-500' : plan.badge === 'enterprise' ? 'bg-purple-500' : '';
return `
${escapeHtml(plan.name)}
${plan.nameAr ? `
${escapeHtml(plan.nameAr)}
` : ''}
${plan.badge ? `
${plan.badge === 'popular' ? '🔥 Popular' : plan.badge === 'best-value' ? '⭐ Best Value' : '🏢 Enterprise'}` : ''}
${priceDisplay}
${plan.description ? `
${escapeHtml(plan.description)}
` : ''}
Features:
${features.map(f => `${f}`).join('')}
${plan.hasFreeTrial ? `
🎁 ${plan.freeTrialDays}-day free trial
` : ''}
`;
}).join('');
}
let editingPlanId = null;
function openAddPlanModal() {
editingPlanId = null;
document.getElementById('plan-modal-title').textContent = 'Add Plan';
document.getElementById('plan-id').value = '';
document.getElementById('plan-name').value = '';
document.getElementById('plan-name-ar').value = '';
document.getElementById('plan-price').value = '';
document.getElementById('plan-currency').value = 'USD';
document.getElementById('plan-duration').value = 'monthly';
document.getElementById('plan-description').value = '';
document.getElementById('plan-description-ar').value = '';
document.getElementById('plan-order').value = 0;
document.getElementById('plan-icon').value = 'bx-star';
document.getElementById('plan-active').checked = true;
document.getElementById('plan-has-trial').checked = false;
document.getElementById('trial-days-container').classList.add('hidden');
document.getElementById('feature-store').checked = false;
document.getElementById('feature-page-builder').checked = false;
document.getElementById('feature-custom-domain').checked = false;
document.getElementById('feature-analytics').checked = false;
document.getElementById('feature-priority-support').checked = false;
document.getElementById('feature-remove-branding').checked = false;
document.getElementById('feature-api-access').checked = false;
document.getElementById('feature-custom-css').checked = false;
document.getElementById('feature-max-products').value = 0;
document.getElementById('feature-team-members').value = 1;
document.getElementById('plan-badge').value = '';
document.getElementById('plan-modal').style.display = 'flex';
}
window.editPlan = async function(planId) {
editingPlanId = planId;
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscription/plans`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
const data = await response.json();
const plan = data.plans.find(p => p._id === planId);
if (plan) {
document.getElementById('plan-modal-title').textContent = 'Edit Plan';
document.getElementById('plan-id').value = plan._id;
document.getElementById('plan-name').value = plan.name;
document.getElementById('plan-name-ar').value = plan.nameAr || '';
document.getElementById('plan-price').value = plan.price;
document.getElementById('plan-currency').value = plan.currency;
document.getElementById('plan-duration').value = plan.duration;
document.getElementById('plan-description').value = plan.description || '';
document.getElementById('plan-description-ar').value = plan.descriptionAr || '';
document.getElementById('plan-order').value = plan.order || 0;
document.getElementById('plan-icon').value = plan.icon || 'bx-star';
document.getElementById('plan-active').checked = plan.isActive;
document.getElementById('plan-has-trial').checked = plan.hasFreeTrial;
document.getElementById('plan-trial-days').value = plan.freeTrialDays || 14;
document.getElementById('trial-days-container').style.display = plan.hasFreeTrial ? 'block' : 'none';
document.getElementById('feature-store').checked = plan.features?.storeEnabled || false;
document.getElementById('feature-page-builder').checked = plan.features?.pageBuilderEnabled || false;
document.getElementById('feature-custom-domain').checked = plan.features?.customDomain || false;
document.getElementById('feature-analytics').checked = plan.features?.analyticsEnabled || false;
document.getElementById('feature-priority-support').checked = plan.features?.prioritySupport || false;
document.getElementById('feature-remove-branding').checked = plan.features?.removeBranding || false;
document.getElementById('feature-api-access').checked = plan.features?.apiAccess || false;
document.getElementById('feature-custom-css').checked = plan.features?.customCss || false;
document.getElementById('feature-max-products').value = plan.features?.maxProducts || 0;
document.getElementById('feature-team-members').value = plan.features?.teamMembers || 1;
document.getElementById('plan-badge').value = plan.badge || '';
document.getElementById('plan-modal').style.display = 'flex';
}
} catch (error) {
showToast('Error loading plan details', 'error');
}
};
async function savePlan() {
const planData = {
name: document.getElementById('plan-name').value,
nameAr: document.getElementById('plan-name-ar').value,
price: parseFloat(document.getElementById('plan-price').value),
currency: document.getElementById('plan-currency').value,
duration: document.getElementById('plan-duration').value,
description: document.getElementById('plan-description').value,
descriptionAr: document.getElementById('plan-description-ar').value,
order: parseInt(document.getElementById('plan-order').value) || 0,
icon: document.getElementById('plan-icon').value,
isActive: document.getElementById('plan-active').checked,
hasFreeTrial: document.getElementById('plan-has-trial').checked,
freeTrialDays: parseInt(document.getElementById('plan-trial-days').value) || 0,
badge: document.getElementById('plan-badge').value || null,
features: {
storeEnabled: document.getElementById('feature-store').checked,
pageBuilderEnabled: document.getElementById('feature-page-builder').checked,
customDomain: document.getElementById('feature-custom-domain').checked,
analyticsEnabled: document.getElementById('feature-analytics').checked,
prioritySupport: document.getElementById('feature-priority-support').checked,
removeBranding: document.getElementById('feature-remove-branding').checked,
apiAccess: document.getElementById('feature-api-access').checked,
customCss: document.getElementById('feature-custom-css').checked,
maxProducts: parseInt(document.getElementById('feature-max-products').value) || 0,
teamMembers: parseInt(document.getElementById('feature-team-members').value) || 1
}
};
try {
const url = editingPlanId
? `${window.ENV.API_URL}/api/admin/subscription/plans/${editingPlanId}`
: `${window.ENV.API_URL}/api/admin/subscription/plans`;
const method = editingPlanId ? 'PUT' : 'POST';
const response = await fetch(url, {
method,
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(planData)
});
if (response.ok) {
showToast(editingPlanId ? 'Plan updated!' : 'Plan created!', 'success');
closePlanModal();
loadPlans();
} else {
const error = await response.json();
showToast(error.error || 'Failed to save plan', 'error');
}
} catch (error) {
showToast('Error saving plan', 'error');
}
}
async function togglePlanStatus(planId, activate) {
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscription/plans/${planId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ isActive: activate })
});
if (response.ok) {
showToast(activate ? 'Plan activated' : 'Plan deactivated', 'success');
loadPlans();
}
} catch (error) {
showToast('Error updating plan status', 'error');
}
}
async function deletePlan(planId) {
if (!confirm('⚠️ Are you sure you want to delete this plan? This action cannot be undone and may affect existing subscribers.')) return;
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscription/plans/${planId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (response.ok) {
showToast('Plan deleted', 'success');
loadPlans();
} else {
const error = await response.json();
showToast(error.error || 'Cannot delete plan with active subscriptions', 'error');
}
} catch (error) {
showToast('Error deleting plan', 'error');
}
}
function closePlanModal() {
document.getElementById('plan-modal').style.display = 'none';
}
// ============================================
// PAYMENT METHODS MANAGEMENT
// ============================================
async function loadPaymentMethods() {
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscription/payment-methods`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (response.ok) {
const data = await response.json();
const methods = data.methods || [];
renderPaymentMethodsGrid(methods);
}
} catch (error) {
console.error('Error loading payment methods:', error);
}
}
function renderPaymentMethodsGrid(methods) {
const container = document.getElementById('payment-methods-container');
if (!container) return;
if (!methods || methods.length === 0) {
container.innerHTML = 'No payment methods added. Click "Add Method" to get started.
';
return;
}
const typeIcons = { bank: '🏦', mobile_wallet: '📱', online: '💳' };
const typeNames = { bank: 'Bank Transfer', mobile_wallet: 'Mobile Wallet', online: 'Online Payment' };
container.innerHTML = methods.map(method => `
${escapeHtml(method.name)}
${method.nameAr ? `
${escapeHtml(method.nameAr)}
` : ''}
${method.isActive ? 'Active' : 'Inactive'}
Type: ${typeIcons[method.type]} ${typeNames[method.type]}
${method.bankDetails?.bankName ? `
Bank: ${escapeHtml(method.bankDetails.bankName)}
Account: ${escapeHtml(method.bankDetails.accountNumber)}
` : ''}
${method.mobileWalletDetails?.phone ? `
Provider: ${escapeHtml(method.mobileWalletDetails.provider)}
Phone: ${escapeHtml(method.mobileWalletDetails.phone)}
` : ''}
`).join('');
}
let editingPaymentMethodId = null;
function openAddPaymentMethodModal() {
editingPaymentMethodId = null;
document.getElementById('pm-modal-title').textContent = 'Add Payment Method';
document.getElementById('pm-id').value = '';
document.getElementById('pm-name').value = '';
document.getElementById('pm-name-ar').value = '';
document.getElementById('pm-type').value = 'bank';
document.getElementById('pm-instructions').value = '';
document.getElementById('pm-instructions-ar').value = '';
document.getElementById('pm-icon').value = 'bx-bank';
document.getElementById('pm-order').value = 0;
document.getElementById('pm-active').checked = true;
document.getElementById('bank-details').classList.remove('hidden');
document.getElementById('mobile-wallet-details').classList.add('hidden');
document.getElementById('pm-bank-name').value = '';
document.getElementById('pm-account-name').value = '';
document.getElementById('pm-account-number').value = '';
document.getElementById('pm-iban').value = '';
document.getElementById('pm-swift').value = '';
document.getElementById('payment-method-modal').style.display = 'flex';
}
window.editPaymentMethod = async function(methodId) {
editingPaymentMethodId = methodId;
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscription/payment-methods`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
const data = await response.json();
const method = data.methods.find(m => m._id === methodId);
if (method) {
document.getElementById('pm-modal-title').textContent = 'Edit Payment Method';
document.getElementById('pm-id').value = method._id;
document.getElementById('pm-name').value = method.name;
document.getElementById('pm-name-ar').value = method.nameAr || '';
document.getElementById('pm-type').value = method.type;
document.getElementById('pm-instructions').value = method.instructions || '';
document.getElementById('pm-instructions-ar').value = method.instructionsAr || '';
document.getElementById('pm-icon').value = method.icon || 'bx-bank';
document.getElementById('pm-order').value = method.order || 0;
document.getElementById('pm-active').checked = method.isActive;
togglePaymentMethodFields(method.type);
if (method.bankDetails) {
document.getElementById('pm-bank-name').value = method.bankDetails.bankName || '';
document.getElementById('pm-account-name').value = method.bankDetails.accountName || '';
document.getElementById('pm-account-number').value = method.bankDetails.accountNumber || '';
document.getElementById('pm-iban').value = method.bankDetails.iban || '';
document.getElementById('pm-swift').value = method.bankDetails.swiftCode || '';
}
if (method.mobileWalletDetails) {
document.getElementById('pm-provider').value = method.mobileWalletDetails.provider || 'vodafone';
document.getElementById('pm-phone').value = method.mobileWalletDetails.phoneNumber || '';
}
document.getElementById('payment-method-modal').style.display = 'flex';
}
} catch (error) {
showToast('Error loading payment method', 'error');
}
};
function togglePaymentMethodFields(type) {
const bankDiv = document.getElementById('bank-details');
const mobileDiv = document.getElementById('mobile-wallet-details');
bankDiv.classList.add('hidden');
mobileDiv.classList.add('hidden');
if (type === 'bank') {
bankDiv.classList.remove('hidden');
} else if (type === 'mobile_wallet') {
mobileDiv.classList.remove('hidden');
}
}
async function savePaymentMethod() {
const methodData = {
name: document.getElementById('pm-name').value,
nameAr: document.getElementById('pm-name-ar').value,
type: document.getElementById('pm-type').value,
instructions: document.getElementById('pm-instructions').value,
instructionsAr: document.getElementById('pm-instructions-ar').value,
icon: document.getElementById('pm-icon').value,
order: parseInt(document.getElementById('pm-order').value) || 0,
isActive: document.getElementById('pm-active').checked,
bankDetails: {
bankName: document.getElementById('pm-bank-name').value,
accountName: document.getElementById('pm-account-name').value,
accountNumber: document.getElementById('pm-account-number').value,
iban: document.getElementById('pm-iban').value,
swiftCode: document.getElementById('pm-swift').value
},
mobileWalletDetails: {
provider: document.getElementById('pm-provider')?.value || 'vodafone',
phoneNumber: document.getElementById('pm-phone')?.value || ''
}
};
try {
const url = editingPaymentMethodId
? `${window.ENV.API_URL}/api/admin/subscription/payment-methods/${editingPaymentMethodId}`
: `${window.ENV.API_URL}/api/admin/subscription/payment-methods`;
const method = editingPaymentMethodId ? 'PUT' : 'POST';
const response = await fetch(url, {
method,
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(methodData)
});
if (response.ok) {
showToast(editingPaymentMethodId ? 'Payment method updated!' : 'Payment method added!', 'success');
closePaymentMethodModal();
loadPaymentMethods();
} else {
const error = await response.json();
showToast(error.error || 'Failed to save', 'error');
}
} catch (error) {
showToast('Error saving payment method', 'error');
}
}
function closePaymentMethodModal() {
document.getElementById('payment-method-modal').style.display = 'none';
}
async function deletePaymentMethod(methodId) {
if (!confirm('⚠️ Are you sure you want to delete this payment method?')) return;
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscription/payment-methods/${methodId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (response.ok) {
showToast('Payment method deleted', 'success');
loadPaymentMethods();
}
} catch (error) {
showToast('Error deleting payment method', 'error');
}
}
// ============================================
// SUBSCRIPTIONS MANAGEMENT
// ============================================
let currentSubscriptionsPage = 1;
let currentSubscriptionsFilter = 'all';
async function loadSubscriptions() {
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscriptions?status=${currentSubscriptionsFilter}&page=${currentSubscriptionsPage}&limit=20`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (response.ok) {
const data = await response.json();
renderSubscriptionsList(data.subscriptions);
renderSubscriptionPagination(data.pagination);
// Update stats
const statsResponse = await fetch(`${window.ENV.API_URL}/api/admin/subscription/stats`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (statsResponse.ok) {
const stats = await statsResponse.json();
document.getElementById('sub-stats-total').textContent = stats.stats.total;
document.getElementById('sub-stats-pending').textContent = stats.stats.pending;
document.getElementById('sub-stats-active').textContent = stats.stats.active;
document.getElementById('sub-stats-expired').textContent = stats.stats.expired;
document.getElementById('sub-stats-cancelled').textContent = stats.stats.cancelled;
const pendingBadge = document.getElementById('subscriptions-badge');
if (pendingBadge && stats.stats.pending > 0) {
pendingBadge.textContent = stats.stats.pending > 9 ? '9+' : stats.stats.pending;
pendingBadge.classList.remove('hidden');
} else if (pendingBadge) {
pendingBadge.classList.add('hidden');
}
}
}
} catch (error) {
console.error('Error loading subscriptions:', error);
}
}
function renderSubscriptionsList(subscriptions) {
const container = document.getElementById('subscriptions-list');
if (!container) return;
if (!subscriptions || subscriptions.length === 0) {
container.innerHTML = 'No subscriptions found
';
return;
}
const statusColors = {
pending: 'bg-yellow-100 text-yellow-700',
active: 'bg-green-100 text-green-700',
expired: 'bg-orange-100 text-orange-700',
cancelled: 'bg-red-100 text-red-700'
};
const statusIcons = {
pending: '⏳',
active: '✅',
expired: '📅',
cancelled: '❌'
};
container.innerHTML = subscriptions.map(sub => `
#${sub._id.slice(-8)}
User: ${escapeHtml(sub.userId?.username || 'Unknown')}
Plan: ${escapeHtml(sub.planId?.name || 'Unknown')}
${statusIcons[sub.status]} ${sub.status.charAt(0).toUpperCase() + sub.status.slice(1)}
Amount: ${sub.planId?.currencySymbol || '$'}${sub.amount || sub.planId?.price || 0}
Start: ${sub.startDate ? new Date(sub.startDate).toLocaleDateString() : '-'}
End: ${sub.endDate ? new Date(sub.endDate).toLocaleDateString() : '-'}
Created: ${new Date(sub.createdAt).toLocaleDateString()}
${sub.paymentProof ? `
` : ''}
${sub.status === 'pending' ? `
` : ''}
${sub.status === 'active' ? `
` : ''}
`).join('');
}
function renderSubscriptionPagination(pagination) {
const container = document.getElementById('subscriptions-pagination');
if (!container || !pagination || pagination.pages <= 1) {
if (container) container.innerHTML = '';
return;
}
let html = '';
for (let i = 1; i <= pagination.pages; i++) {
html += ``;
}
container.innerHTML = html;
}
function goToSubscriptionPage(page) {
currentSubscriptionsPage = page;
loadSubscriptions();
}
async function approveSubscription(subId) {
const adminNote = prompt('Admin note (optional):', '');
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscriptions/${subId}/approve`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ adminNotes: adminNote || '' })
});
if (response.ok) {
showToast('Subscription approved!', 'success');
loadSubscriptions();
} else {
const error = await response.json();
showToast(error.error || 'Failed to approve', 'error');
}
} catch (error) {
showToast('Error approving subscription', 'error');
}
}
async function rejectSubscription(subId) {
const reason = prompt('Rejection reason:', '');
if (!reason) return;
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscriptions/${subId}/reject`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ rejectionReason: reason })
});
if (response.ok) {
showToast('Subscription rejected', 'success');
loadSubscriptions();
}
} catch (error) {
showToast('Error rejecting subscription', 'error');
}
}
async function cancelSubscription(subId) {
if (!confirm('Are you sure you want to cancel this active subscription?')) return;
const reason = prompt('Cancellation reason:', '');
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscriptions/${subId}/cancel`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ reason: reason || '' })
});
if (response.ok) {
showToast('Subscription cancelled', 'success');
loadSubscriptions();
}
} catch (error) {
showToast('Error cancelling subscription', 'error');
}
}
function viewSubscriptionDetails(subId) {
window.open(`/admin/subscription-details.html?id=${subId}`, '_blank');
}
// ============================================
// SUBSCRIPTION EARNINGS
// ============================================
let revenueChart = null;
async function loadSubscriptionEarnings() {
try {
const response = await fetch(`${window.ENV.API_URL}/api/admin/subscription/stats`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (response.ok) {
const data = await response.json();
document.getElementById('total-revenue').textContent = `${data.stats.currencySymbol || '$'}${data.stats.totalRevenue.toLocaleString()}`;
document.getElementById('monthly-revenue').textContent = `${data.stats.currencySymbol || '$'}${(data.stats.monthlyEarnings[data.stats.monthlyEarnings.length - 1]?.total || 0).toLocaleString()}`;
renderRevenueChart(data.stats.monthlyEarnings);
renderEarningsTransactions(data.pendingSubscriptionsList || []);
}
} catch (error) {
console.error('Error loading earnings:', error);
}
}
function renderRevenueChart(monthlyData) {
const ctx = document.getElementById('revenue-chart')?.getContext('2d');
if (!ctx) return;
if (revenueChart) revenueChart.destroy();
revenueChart = new Chart(ctx, {
type: 'line',
data: {
labels: monthlyData.map(m => m._id),
datasets: [{
label: 'Revenue',
data: monthlyData.map(m => m.total),
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'top' },
tooltip: { callbacks: { label: (ctx) => `$${ctx.raw.toLocaleString()}` } }
}
}
});
}
function renderEarningsTransactions(transactions) {
const container = document.getElementById('earnings-transactions');
if (!container) return;
if (!transactions || transactions.length === 0) {
container.innerHTML = 'No transactions yet
';
return;
}
container.innerHTML = transactions.map(t => `
${escapeHtml(t.userId?.username || 'Unknown')}
${escapeHtml(t.planId?.name || 'Plan')}
${new Date(t.createdAt).toLocaleDateString()}
+${t.planId?.currencySymbol || '$'}${t.amount || t.planId?.price || 0}
${t.status}
`).join('');
}
// ============================================
// SUBSCRIPTION SETTINGS
// ============================================
async function loadSubscriptionSettings() {
try {
const response = await fetch(`${window.ENV.API_URL}/api/subscription/settings`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (response.ok) {
const data = await response.json();
const settings = data.settings || {};
document.getElementById('setting-trial-days').value = settings.defaultFreeTrialDays || 14;
document.getElementById('setting-reminder-days').value = settings.reminderDays || 7;
document.getElementById('setting-allow-trial-existing').checked = settings.allowFreeTrialForExistingUsers || false;
document.getElementById('setting-commission-enabled').checked = settings.commissionEnabled || false;
document.getElementById('setting-commission-rate').value = settings.commissionRate || 0;
document.getElementById('setting-send-invoice').checked = settings.sendInvoiceEmail !== false;
document.getElementById('setting-send-reminder').checked = settings.sendReminderEmail !== false;
document.getElementById('setting-notify-admin').checked = settings.notifyAdminOnNewSubscription !== false;
toggleCommissionRate();
}
} catch (error) {
console.error('Error loading subscription settings:', error);
}
}
function toggleCommissionRate() {
const enabled = document.getElementById('setting-commission-enabled').checked;
const rateContainer = document.getElementById('commission-rate-container');
if (rateContainer) {
rateContainer.classList.toggle('hidden', !enabled);
}
}
async function saveSubscriptionSettings() {
const settings = {
defaultFreeTrialDays: parseInt(document.getElementById('setting-trial-days').value) || 14,
reminderDays: parseInt(document.getElementById('setting-reminder-days').value) || 7,
allowFreeTrialForExistingUsers: document.getElementById('setting-allow-trial-existing').checked,
commissionEnabled: document.getElementById('setting-commission-enabled').checked,
commissionRate: parseFloat(document.getElementById('setting-commission-rate').value) || 0,
sendInvoiceEmail: document.getElementById('setting-send-invoice').checked,
sendReminderEmail: document.getElementById('setting-send-reminder').checked,
notifyAdminOnNewSubscription: document.getElementById('setting-notify-admin').checked
};
try {
const response = await fetch(`${window.ENV.API_URL}/api/subscription/settings`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(settings)
});
if (response.ok) {
showToast('Subscription settings saved!', 'success');
} else {
throw new Error('Save failed');
}
} catch (error) {
showToast('Error saving settings', 'error');
}
}
// ============================================
// EVENT LISTENERS FOR SUBSCRIPTION
// ============================================
// Plan modal
document.getElementById('add-plan-btn')?.addEventListener('click', openAddPlanModal);
document.getElementById('close-plan-modal')?.addEventListener('click', closePlanModal);
document.getElementById('save-plan')?.addEventListener('click', savePlan);
document.getElementById('plan-has-trial')?.addEventListener('change', (e) => {
const container = document.getElementById('trial-days-container');
if (container) container.style.display = e.target.checked ? 'block' : 'none';
});
// Payment method modal
document.getElementById('add-payment-method-btn')?.addEventListener('click', openAddPaymentMethodModal);
document.getElementById('close-pm-modal')?.addEventListener('click', closePaymentMethodModal);
document.getElementById('save-payment-method')?.addEventListener('click', savePaymentMethod);
document.getElementById('pm-type')?.addEventListener('change', (e) => togglePaymentMethodFields(e.target.value));
// Subscriptions filter
document.getElementById('subscription-status-filter')?.addEventListener('change', (e) => {
currentSubscriptionsFilter = e.target.value;
currentSubscriptionsPage = 1;
loadSubscriptions();
});
document.getElementById('refresh-subscriptions')?.addEventListener('click', () => loadSubscriptions());
// Settings
document.getElementById('setting-commission-enabled')?.addEventListener('change', toggleCommissionRate);
document.getElementById('save-subscription-settings')?.addEventListener('click', saveSubscriptionSettings);
// Section navigation - add to existing section handler
const originalSectionHandler = document.querySelectorAll('.sidebar-item').forEach(item => {
const originalClick = item.onclick;
item.addEventListener('click', () => {
const section = item.dataset.section;
if (section === 'subscription-plans') loadPlans();
if (section === 'payment-methods') loadPaymentMethods();
if (section === 'subscriptions') loadSubscriptions();
if (section === 'subscription-earnings') loadSubscriptionEarnings();
if (section === 'subscription-settings') loadSubscriptionSettings();
});
});
وصفحه
/home/mark/cv/frontend/public/my-orders.html
My Orders - MGZon
و
/home/mark/cv/frontend/public/order-confirmation.html
Order Confirmation - MGZon
Order Confirmed! 🎉
Thank you for your purchase
A confirmation email has been sent to your registered email address.
You can track your order status in My Orders.
وفي صفحه الاعدداات
/home/mark/cv/frontend/public/settings.html
Store Description
Currency
Page Sections (Drag & Drop)
Drag and drop to reorder sections on your store page
Store Background
Typography
Font Family
Effects
Card Shadow
Hover Animation
Glass Morphism (Frosted Glass Effect)
Custom CSS (Advanced)
⚠️ Advanced users only. Invalid CSS may break your store layout.
Payment Methods
Add your payment details. Money goes directly to you. MGZon does not hold your funds.
Your Products
Add Product
My Subscription
Manage your current plan and subscription settings
Loading subscription info...
Need More Features?
Upgrade your plan to unlock more features and increase limits.
Upgrade Plan
// ============================================
// 🛒 STORE SETTINGS JAVASCRIPT - FULL VERSION
// ============================================
let storeSettings = {};
let storeProducts = [];
let storeOrders = [];
let storeEarnings = {};
let currentProductFilter = 'all';
let currentOrderFilter = 'all';
let earningsChart = null;
let currentPaymentMethods = [];
let currentStoreSections = [];
let storeSections = [];
let storeDesignSettings = {};
// ============================================
// 1. SUBSCRIPTION CHECK BEFORE LOADING
// ============================================
async function checkSubscriptionStatus() {
if (!userToken) return { hasSubscription: false, canCreateStore: false };
try {
const response = await fetch(`${cleanApiUrl}/api/subscription/check`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (response.ok) {
const data = await response.json();
return {
hasSubscription: data.hasActiveSubscription || false,
canCreateStore: data.canCreateStore || false,
isTrial: data.isTrial || false,
daysLeft: data.daysLeft || 0,
plan: data.plan || null
};
}
return { hasSubscription: false, canCreateStore: false };
} catch (error) {
console.error('Subscription check error:', error);
return { hasSubscription: false, canCreateStore: false };
}
}
// ============================================
// 2. LOAD STORE SETTINGS WITH SUBSCRIPTION CHECK
// ============================================
async function loadStoreSettings() {
if (!userToken) return;
// ✅ Check subscription first
const subStatus = await checkSubscriptionStatus();
// Show/Hide subscription warning
const toggleContainer = document.querySelector('#storeEnabled').closest('.mb-6');
const existingWarning = document.getElementById('subscription-warning');
const existingUpgradeWarning = document.getElementById('upgrade-warning');
if (existingWarning) existingWarning.remove();
if (existingUpgradeWarning) existingUpgradeWarning.remove();
if (!subStatus.hasSubscription) {
// No subscription - disable store
document.getElementById('storeEnabled').disabled = true;
document.getElementById('storeEnabled').checked = false;
document.getElementById('store-config').style.display = 'none';
const warningDiv = document.createElement('div');
warningDiv.id = 'subscription-warning';
warningDiv.className = 'mb-4 p-4 bg-yellow-100 dark:bg-yellow-900/20 rounded-lg border border-yellow-300';
warningDiv.innerHTML = `
📋 Subscription Required
You need an active subscription to access store features.
View Plans →
`;
if (toggleContainer && !document.getElementById('subscription-warning')) {
toggleContainer.insertAdjacentElement('afterend', warningDiv);
}
return;
}
if (!subStatus.canCreateStore && subStatus.hasSubscription) {
// Has subscription but doesn't support store - show upgrade warning
document.getElementById('storeEnabled').disabled = true;
document.getElementById('storeEnabled').checked = false;
document.getElementById('store-config').style.display = 'none';
const upgradeDiv = document.createElement('div');
upgradeDiv.id = 'upgrade-warning';
upgradeDiv.className = 'mb-4 p-4 bg-purple-100 dark:bg-purple-900/20 rounded-lg border border-purple-300';
upgradeDiv.innerHTML = `
🚀 Upgrade to Enable Store
Your current plan "${subStatus.plan?.name || 'Basic'}" does not include store features.
${subStatus.daysLeft > 0 ? `
${subStatus.daysLeft} days remaining on current plan
` : ''}
Upgrade Now →
`;
if (toggleContainer && !document.getElementById('upgrade-warning')) {
toggleContainer.insertAdjacentElement('afterend', upgradeDiv);
}
return;
}
// ✅ Has valid subscription - enable store settings
document.getElementById('storeEnabled').disabled = false;
try {
const response = await fetchWithRefresh(`${cleanApiUrl}/api/store/settings`);
if (response.ok) {
const data = await response.json();
storeSettings = data.settings || {};
// Apply to form
document.getElementById('storeEnabled').checked = storeSettings.enabled || false;
document.getElementById('storeName').value = storeSettings.storeName || '';
document.getElementById('storeDescription').value = storeSettings.storeDescription || '';
document.getElementById('storeCurrency').value = storeSettings.currency || 'USD';
document.getElementById('storeLogo').value = storeSettings.storeLogo || '';
document.getElementById('storeBanner').value = storeSettings.storeBanner || '';
document.getElementById('storePrimaryColor').value = storeSettings.primaryColor || '#3b82f6';
document.getElementById('storeSecondaryColor').value = storeSettings.secondaryColor || '#8b5cf6';
// Update previews
if (storeSettings.storeLogo) {
document.getElementById('storeLogoPreview').src = storeSettings.storeLogo;
}
if (storeSettings.storeBanner) {
document.getElementById('storeBannerPreview').src = storeSettings.storeBanner;
}
// Update store URL preview
const nickname = document.getElementById('nickname')?.value;
if (nickname) {
document.getElementById('store-url-preview').innerHTML = `${window.location.origin}/shop/${nickname}`;
}
// Render payment methods
if (storeSettings.paymentMethods) {
renderPaymentMethods(storeSettings.paymentMethods);
}
// Render page builder sections
if (storeSettings.pageBuilder?.sections) {
renderStoreSections(storeSettings.pageBuilder.sections);
}
toggleStoreConfig();
}
// Load products, orders, earnings
await Promise.all([
loadStoreProducts(),
loadSellerOrders(),
loadStoreEarnings()
]);
} catch (error) {
console.error('Error loading store settings:', error);
showToast('Failed to load store settings', 'error');
}
}
// ============================================
// 3. TOGGLE STORE CONFIG WITH SUBSCRIPTION CHECK
// ============================================
function toggleStoreConfig() {
const enabled = document.getElementById('storeEnabled').checked;
const configDiv = document.getElementById('store-config');
configDiv.style.display = enabled ? 'block' : 'none';
if (enabled && storeSettings.enabled !== enabled) {
saveStoreSetting('enabled', true);
} else if (!enabled && storeSettings.enabled !== enabled) {
saveStoreSetting('enabled', false);
}
}
async function saveStoreSetting(key, value) {
try {
const csrfToken = await fetchCsrfToken();
await fetch(`${cleanApiUrl}/api/store/settings`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ [key]: value })
});
} catch (error) {
console.error('Error saving store setting:', error);
}
}
// ============================================
// 4. SAVE ALL STORE SETTINGS
// ============================================
async function saveAllStoreSettings() {
const settings = {
storeName: document.getElementById('storeName').value,
storeDescription: document.getElementById('storeDescription').value,
currency: document.getElementById('storeCurrency').value,
storeLogo: document.getElementById('storeLogo').value,
storeBanner: document.getElementById('storeBanner').value,
primaryColor: document.getElementById('storePrimaryColor').value,
secondaryColor: document.getElementById('storeSecondaryColor').value,
paymentMethods: currentPaymentMethods || [],
pageBuilder: { sections: currentStoreSections || [] }
};
try {
const csrfToken = await fetchCsrfToken();
const response = await fetch(`${cleanApiUrl}/api/store/settings`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(settings)
});
if (response.ok) {
showToast('✅ Store settings saved successfully!', 'success');
} else {
throw new Error('Save failed');
}
} catch (error) {
showToast('Failed to save store settings', 'error');
}
}
// ============================================
// 5. STORE PRODUCTS FUNCTIONS
// ============================================
async function loadStoreProducts() {
try {
const response = await fetchWithRefresh(`${cleanApiUrl}/api/store/products`);
if (response.ok) {
const data = await response.json();
storeProducts = data.products || [];
renderStoreProducts();
}
} catch (error) {
console.error('Error loading products:', error);
}
}
function renderStoreProducts() {
const container = document.getElementById('store-products-grid');
if (!container) return;
let filtered = storeProducts;
if (currentProductFilter !== 'all') {
filtered = storeProducts.filter(p => p.type === currentProductFilter);
}
if (filtered.length === 0) {
container.innerHTML = 'No products yet. Click "Add Product" to start selling.
';
return;
}
container.innerHTML = filtered.map(product => `
${product.images?.[0] ? `

` : `
`}
${escapeHtml(product.title)}
${product.price} ${storeSettings.currencySymbol || '$'}
${product.type === 'digital' ? '📁 Digital' : product.type === 'project' ? '💻 Project' : '⚡ Service'}
${!product.isActive ? 'Inactive' : ''}
`).join('');
}
// ============================================
// 6. STORE ORDERS FUNCTIONS
// ============================================
async function loadSellerOrders() {
try {
const response = await fetchWithRefresh(`${cleanApiUrl}/api/store/orders/seller`);
if (response.ok) {
const data = await response.json();
storeOrders = data.orders || [];
renderSellerOrders();
}
} catch (error) {
console.error('Error loading orders:', error);
}
}
function renderSellerOrders() {
const container = document.getElementById('seller-orders-list');
if (!container) return;
let filtered = storeOrders;
if (currentOrderFilter !== 'all') {
filtered = storeOrders.filter(o => o.status === currentOrderFilter);
}
if (filtered.length === 0) {
container.innerHTML = 'No orders found
';
return;
}
container.innerHTML = filtered.map(order => `
#${order.orderNumber}
${new Date(order.createdAt).toLocaleString()}
${order.status === 'pending' ? '⏳ Pending' : order.status === 'paid' ? '💰 Paid' : order.status === 'completed' ? '✅ Completed' : '❌ Cancelled'}
Buyer: ${order.buyerId?.username || 'Unknown'}
Items: ${order.items.length} product(s)
Total: ${order.totalAmount} ${storeSettings.currencySymbol || '$'}
${order.status === 'completed' && order.items.some(i => i.fileUrl) ? `
` : ''}
`).join('');
}
async function updateOrderStatus(orderId, status) {
try {
const csrfToken = await fetchCsrfToken();
const response = await fetch(`${cleanApiUrl}/api/store/orders/${orderId}/status`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ status })
});
if (response.ok) {
showToast(`Order status updated to ${status}`, 'success');
loadSellerOrders();
loadStoreEarnings();
} else {
throw new Error('Update failed');
}
} catch (error) {
showToast('Failed to update order status', 'error');
}
}
// ============================================
// 7. STORE EARNINGS FUNCTIONS
// ============================================
async function loadStoreEarnings() {
try {
const response = await fetchWithRefresh(`${cleanApiUrl}/api/store/earnings`);
if (response.ok) {
const data = await response.json();
storeEarnings = data.earnings || { totalSales: 0, totalEarnings: 0, pendingPayout: 0 };
document.getElementById('total-sales').textContent = storeEarnings.totalSales || 0;
document.getElementById('total-earnings').textContent = `${storeSettings.currencySymbol || '$'}${(storeEarnings.totalEarnings || 0).toFixed(2)}`;
document.getElementById('pending-payout').textContent = `${storeSettings.currencySymbol || '$'}${(storeEarnings.pendingPayout || 0).toFixed(2)}`;
renderTransactions(data.recentOrders || []);
if (data.monthlyStats) {
renderEarningsChart(data.monthlyStats);
}
}
} catch (error) {
console.error('Error loading earnings:', error);
}
}
function renderTransactions(orders) {
const container = document.getElementById('transactions-list');
if (!container) return;
if (!orders || orders.length === 0) {
container.innerHTML = 'No transactions yet
';
return;
}
container.innerHTML = orders.map(order => `
#${order.orderNumber}
${new Date(order.createdAt).toLocaleDateString()}
+${order.totalAmount} ${storeSettings.currencySymbol || '$'}
${order.status}
`).join('');
}
function renderEarningsChart(monthlyStats) {
const ctx = document.getElementById('earnings-chart')?.getContext('2d');
if (!ctx) return;
if (earningsChart) earningsChart.destroy();
earningsChart = new Chart(ctx, {
type: 'line',
data: {
labels: monthlyStats.map(s => s.month),
datasets: [{
label: 'Earnings',
data: monthlyStats.map(s => s.earnings),
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
callbacks: {
label: (ctx) => `${storeSettings.currencySymbol || '$'}${ctx.raw.toLocaleString()}`
}
}
}
}
});
}
// ============================================
// 8. PAYMENT METHODS FUNCTIONS
// ============================================
function renderPaymentMethods(methods) {
const container = document.getElementById('payment-methods-container');
if (!container) return;
currentPaymentMethods = methods || [];
if (currentPaymentMethods.length === 0) {
container.innerHTML = 'No payment methods added. Click "Add Method" to start selling.
';
return;
}
container.innerHTML = currentPaymentMethods.map((method, index) => `
${renderPaymentMethodFields(method, index)}
`).join('');
document.querySelectorAll('.payment-active').forEach(cb => {
cb.addEventListener('change', (e) => {
const idx = parseInt(e.target.dataset.index);
if (currentPaymentMethods[idx]) {
currentPaymentMethods[idx].isActive = e.target.checked;
}
});
});
}
function renderPaymentMethodFields(method, index) {
switch(method.type) {
case 'vodafone':
case 'instapay':
return `
Phone Number
`;
case 'paypal':
return `
PayPal Email
`;
case 'stripe':
return `
Stripe Account ID
`;
case 'bank':
return `
`;
default:
return '';
}
}
function openAddPaymentMethodModal() {
const types = [
{ value: 'paypal', label: 'PayPal', icon: 'bxl-paypal', color: 'blue' },
{ value: 'stripe', label: 'Stripe', icon: 'bx-credit-card', color: 'purple' },
{ value: 'vodafone', label: 'Vodafone Cash', icon: 'bx-mobile-alt', color: 'red' },
{ value: 'instapay', label: 'InstaPay', icon: 'bx-transfer', color: 'green' },
{ value: 'bank', label: 'Bank Transfer', icon: 'bx-bank', color: 'gray' }
];
const modalHtml = `
Add Payment Method
${types.map(t => `
`).join('')}
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
}
function selectPaymentMethodType(type, label) {
closePaymentMethodModal();
currentPaymentMethods.push({
type,
label,
details: {},
isActive: true
});
renderPaymentMethods(currentPaymentMethods);
}
function closePaymentMethodModal() {
document.getElementById('payment-method-modal')?.remove();
}
function removePaymentMethod(index) {
currentPaymentMethods.splice(index, 1);
renderPaymentMethods(currentPaymentMethods);
}
document.addEventListener('input', (e) => {
if (e.target.classList.contains('payment-detail')) {
const index = parseInt(e.target.dataset.index);
const field = e.target.dataset.field;
if (currentPaymentMethods[index]) {
if (!currentPaymentMethods[index].details) currentPaymentMethods[index].details = {};
currentPaymentMethods[index].details[field] = e.target.value;
}
}
});
// ============================================
// 9. STORE SECTIONS (PAGE BUILDER)
// ============================================
function renderStoreSections(sections) {
const container = document.getElementById('store-sections-list');
if (!container) return;
currentStoreSections = sections || [];
if (currentStoreSections.length === 0) {
container.innerHTML = 'No sections added. Click "Add Section" to build your store page.
';
return;
}
const sectionTypes = {
hero: { icon: 'bx-home', name: 'Hero Section', color: 'blue', desc: 'Large banner with title and CTA button' },
featured: { icon: 'bx-star', name: 'Featured Products', color: 'green', desc: 'Showcase your best products' },
products: { icon: 'bx-package', name: 'Products Grid', color: 'purple', desc: 'Main products listing' },
categories: { icon: 'bx-category', name: 'Categories', color: 'orange', desc: 'Show product categories' },
testimonials: { icon: 'bx-chat', name: 'Testimonials', color: 'pink', desc: 'Customer reviews' },
faq: { icon: 'bx-question-mark', name: 'FAQ Section', color: 'yellow', desc: 'Frequently asked questions' },
contact: { icon: 'bx-envelope', name: 'Contact Section', color: 'indigo', desc: 'Contact form' }
};
container.innerHTML = currentStoreSections.map((section, index) => {
const typeInfo = sectionTypes[section.type] || { icon: 'bx-folder', name: section.type, color: 'gray', desc: '' };
return `
${typeInfo.name}
${typeInfo.desc}
`;
}).join('');
setupSectionDragDrop();
}
function setupSectionDragDrop() {
const items = document.querySelectorAll('#store-sections-list > div');
let draggedItem = null;
items.forEach(item => {
item.addEventListener('dragstart', (e) => {
draggedItem = item;
e.dataTransfer.effectAllowed = 'move';
item.style.opacity = '0.5';
});
item.addEventListener('dragend', (e) => {
if (draggedItem) draggedItem.style.opacity = '';
draggedItem = null;
});
item.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
});
item.addEventListener('drop', (e) => {
e.preventDefault();
if (draggedItem && draggedItem !== item) {
const draggedIndex = parseInt(draggedItem.dataset.sectionIndex);
const targetIndex = parseInt(item.dataset.sectionIndex);
const [moved] = currentStoreSections.splice(draggedIndex, 1);
currentStoreSections.splice(targetIndex, 0, moved);
renderStoreSections(currentStoreSections);
}
if (draggedItem) draggedItem.style.opacity = '';
draggedItem = null;
});
});
}
function openAddStoreSectionModal() {
const sectionTypes = [
{ type: 'hero', name: 'Hero Section', icon: 'bx-home', color: 'blue', description: 'Large banner with title and CTA button' },
{ type: 'featured', name: 'Featured Products', icon: 'bx-star', color: 'green', description: 'Showcase your best products' },
{ type: 'products', name: 'Products Grid', icon: 'bx-package', color: 'purple', description: 'Main products listing' },
{ type: 'categories', name: 'Categories', icon: 'bx-category', color: 'orange', description: 'Show product categories' },
{ type: 'testimonials', name: 'Testimonials', icon: 'bx-chat', color: 'pink', description: 'Customer reviews' },
{ type: 'faq', name: 'FAQ Section', icon: 'bx-question-mark', color: 'yellow', description: 'Frequently asked questions' },
{ type: 'contact', name: 'Contact Section', icon: 'bx-envelope', color: 'indigo', description: 'Contact form' }
];
const modalHtml = `
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
}
function addStoreSectionType(type) {
const newSection = {
id: `${type}_${Date.now()}`,
type: type,
enabled: true,
order: currentStoreSections.length + 1,
content: type === 'hero' ? { title: 'Welcome to my store', subtitle: '', buttonText: 'Shop Now', buttonLink: '#products' } : {},
title: type === 'featured' ? 'Featured Products' : type === 'products' ? 'All Products' : type === 'categories' ? 'Categories' : null,
limit: type === 'featured' ? 6 : undefined,
layout: type === 'products' ? 'grid' : undefined
};
currentStoreSections.push(newSection);
renderStoreSections(currentStoreSections);
closeAddStoreSectionModal();
showToast(`${getSectionTitle(type)} added!`, 'success');
}
function closeAddStoreSectionModal() {
document.getElementById('add-store-section-modal')?.remove();
}
function getSectionTitle(type) {
const titles = {
hero: 'Hero Section',
featured: 'Featured Products',
products: 'Products Grid',
categories: 'Categories',
testimonials: 'Testimonials',
faq: 'FAQ Section',
contact: 'Contact Section'
};
return titles[type] || type;
}
window.toggleStoreSection = function(index) {
currentStoreSections[index].enabled = !currentStoreSections[index].enabled;
renderStoreSections(currentStoreSections);
};
window.editStoreSection = function(index) {
const section = currentStoreSections[index];
const newTitle = prompt('Edit section title:', section.title || getSectionTitle(section.type));
if (newTitle !== null && newTitle.trim()) {
if (section.type === 'hero') {
section.content.title = newTitle;
} else {
section.title = newTitle;
}
renderStoreSections(currentStoreSections);
showToast('Section updated', 'success');
}
};
window.deleteStoreSection = function(index) {
if (confirm('Remove this section from your store page?')) {
currentStoreSections.splice(index, 1);
renderStoreSections(currentStoreSections);
showToast('Section removed', 'success');
}
};
// ============================================
// 10. STORE DESIGN CONTROLS
// ============================================
async function loadStoreDesign() {
if (!userToken) return;
try {
const response = await fetchWithRefresh(`${cleanApiUrl}/api/store/settings`);
if (response.ok) {
const data = await response.json();
const settings = data.settings || {};
storeDesignSettings = {
primaryColor: settings.primaryColor || '#3b82f6',
secondaryColor: settings.secondaryColor || '#8b5cf6',
fontFamily: settings.fontFamily || 'Inter, sans-serif',
borderRadius: settings.borderRadius || 16,
shadowType: settings.shadowType || 'md',
animationType: settings.animationType || 'lift',
glassEffect: settings.glassEffect || false,
backgroundType: settings.backgroundType || 'default',
backgroundValue: settings.backgroundValue || '',
customCss: settings.customCss || ''
};
storeSections = settings.pageBuilder?.sections || [
{ id: 'hero', type: 'hero', enabled: true, order: 1, content: { title: 'Welcome to my store', subtitle: '', buttonText: 'Shop Now', buttonLink: '#products' } },
{ id: 'featured', type: 'featured', enabled: true, order: 2, title: 'Featured Products', limit: 6 },
{ id: 'products', type: 'products', enabled: true, order: 3, title: 'All Products', layout: 'grid' },
{ id: 'categories', type: 'categories', enabled: true, order: 4, title: 'Categories' }
];
applyStoreDesignToUI();
renderStoreSections();
}
} catch (error) {
console.error('Error loading store design:', error);
}
}
function applyStoreDesignToUI() {
document.getElementById('storePrimaryColor').value = storeDesignSettings.primaryColor;
document.getElementById('storeSecondaryColor').value = storeDesignSettings.secondaryColor;
document.getElementById('storeFontFamily').value = storeDesignSettings.fontFamily;
document.getElementById('storeBorderRadius').value = storeDesignSettings.borderRadius;
document.getElementById('storeRadiusValue').textContent = storeDesignSettings.borderRadius + 'px';
document.getElementById('storeShadowType').value = storeDesignSettings.shadowType;
document.getElementById('storeAnimationType').value = storeDesignSettings.animationType;
document.getElementById('storeGlassEffect').checked = storeDesignSettings.glassEffect;
document.getElementById('storeCustomCss').value = storeDesignSettings.customCss;
if (storeDesignSettings.backgroundType === 'gradient' && storeDesignSettings.backgroundValue) {
document.getElementById('store-gradient-preset').value = storeDesignSettings.backgroundValue;
document.getElementById('store-gradient-panel').classList.remove('hidden');
} else if (storeDesignSettings.backgroundType === 'image' && storeDesignSettings.backgroundValue) {
document.getElementById('store-bg-url').value = storeDesignSettings.backgroundValue;
document.getElementById('store-image-panel').classList.remove('hidden');
}
}
async function saveStoreDesign() {
const primaryColor = document.getElementById('storePrimaryColor').value;
const secondaryColor = document.getElementById('storeSecondaryColor').value;
const fontFamily = document.getElementById('storeFontFamily').value;
const borderRadius = parseInt(document.getElementById('storeBorderRadius').value);
const shadowType = document.getElementById('storeShadowType').value;
const animationType = document.getElementById('storeAnimationType').value;
const glassEffect = document.getElementById('storeGlassEffect').checked;
const customCss = document.getElementById('storeCustomCss').value;
let backgroundType = 'default';
let backgroundValue = '';
const gradientPanel = document.getElementById('store-gradient-panel');
const imagePanel = document.getElementById('store-image-panel');
if (gradientPanel && !gradientPanel.classList.contains('hidden')) {
backgroundType = 'gradient';
backgroundValue = document.getElementById('store-gradient-preset').value;
} else if (imagePanel && !imagePanel.classList.contains('hidden')) {
const bgUrl = document.getElementById('store-bg-url').value;
if (bgUrl) {
backgroundType = 'image';
backgroundValue = bgUrl;
}
}
const designData = {
primaryColor,
secondaryColor,
fontFamily,
borderRadius,
shadowType,
animationType,
glassEffect,
backgroundType,
backgroundValue,
customCss,
pageBuilder: { sections: storeSections }
};
try {
const csrfToken = await fetchCsrfToken();
const response = await fetch(`${cleanApiUrl}/api/store/settings`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken || ''
},
body: JSON.stringify(designData)
});
if (response.ok) {
showToast('✅ Store design saved successfully!', 'success');
storeDesignSettings = designData;
} else {
throw new Error('Save failed');
}
} catch (error) {
console.error('Error saving store design:', error);
showToast('Failed to save store design', 'error');
}
}
async function resetStoreDesign() {
if (!confirm('⚠️ Reset all store design settings to default?')) return;
storeDesignSettings = {
primaryColor: '#3b82f6',
secondaryColor: '#8b5cf6',
fontFamily: 'Inter, sans-serif',
borderRadius: 16,
shadowType: 'md',
animationType: 'lift',
glassEffect: false,
backgroundType: 'default',
backgroundValue: '',
customCss: ''
};
storeSections = [
{ id: 'hero', type: 'hero', enabled: true, order: 1, content: { title: 'Welcome to my store', subtitle: '', buttonText: 'Shop Now', buttonLink: '#products' } },
{ id: 'featured', type: 'featured', enabled: true, order: 2, title: 'Featured Products', limit: 6 },
{ id: 'products', type: 'products', enabled: true, order: 3, title: 'All Products', layout: 'grid' },
{ id: 'categories', type: 'categories', enabled: true, order: 4, title: 'Categories' }
];
applyStoreDesignToUI();
renderStoreSections();
await saveStoreDesign();
showToast('Store design reset to default', 'success');
}
function previewStoreDesign() {
const nickname = document.getElementById('nickname')?.value;
if (nickname) {
window.open(`/shop/${nickname}?preview=1`, '_blank');
} else {
showToast('Please save your nickname first', 'error');
}
}
// ============================================
// 11. PRODUCT FUNCTIONS
// ============================================
function openProductModal(productId = null) {
const modal = document.getElementById('product-modal');
const title = document.getElementById('product-modal-title');
const editId = document.getElementById('edit-product-id');
if (productId) {
const product = storeProducts.find(p => p._id === productId);
if (product) {
title.textContent = 'Edit Product';
editId.value = product._id;
document.getElementById('product-title').value = product.title;
document.getElementById('product-description').value = product.description;
document.getElementById('product-price').value = product.price;
document.getElementById('product-delivery').value = product.deliveryTime || '';
document.getElementById('product-tags').value = (product.tags || []).join(', ');
document.getElementById('product-active').checked = product.isActive !== false;
// Set product type radio
const radio = document.querySelector(`.product-type-radio[value="${product.type}"]`);
if (radio) radio.checked = true;
toggleProductTypeFields(product.type);
// Set images
const imagesContainer = document.getElementById('product-images-container');
imagesContainer.innerHTML = '';
if (product.images && product.images.length > 0) {
product.images.forEach(img => {
addProductImageField(img);
});
} else {
addProductImageField();
}
// Show existing file info
if (product.fileUrl) {
const fileInfo = document.getElementById('existing-file-info');
const fileName = document.getElementById('current-file-name');
if (fileInfo && fileName) {
fileName.textContent = product.fileUrl.split('/').pop();
fileInfo.classList.remove('hidden');
}
}
}
} else {
title.textContent = 'Add Product';
editId.value = '';
document.getElementById('product-title').value = '';
document.getElementById('product-description').value = '';
document.getElementById('product-price').value = '';
document.getElementById('product-delivery').value = '';
document.getElementById('product-tags').value = '';
document.getElementById('product-active').checked = true;
document.getElementById('product-images-container').innerHTML = '';
addProductImageField();
document.getElementById('existing-file-info')?.classList.add('hidden');
document.getElementById('product-file').value = '';
}
modal.classList.remove('hidden');
modal.style.display = 'flex';
}
function closeProductModal() {
const modal = document.getElementById('product-modal');
modal.classList.add('hidden');
modal.style.display = 'none';
}
function addProductImageField(value = '') {
const container = document.getElementById('product-images-container');
const div = document.createElement('div');
div.className = 'flex gap-2';
div.innerHTML = `
`;
container.appendChild(div);
div.querySelector('.remove-image-btn').addEventListener('click', () => div.remove());
}
function toggleProductTypeFields(type) {
const deliveryContainer = document.getElementById('delivery-time-container');
const fileContainer = document.getElementById('file-upload-container');
deliveryContainer.style.display = type === 'service' ? 'block' : 'none';
fileContainer.style.display = type === 'digital' ? 'block' : 'none';
}
async function saveProduct() {
const productId = document.getElementById('edit-product-id').value;
const type = document.querySelector('.product-type-radio:checked')?.value;
const title = document.getElementById('product-title').value.trim();
const description = document.getElementById('product-description').value.trim();
const price = parseFloat(document.getElementById('product-price').value);
const deliveryTime = document.getElementById('product-delivery').value;
const tags = document.getElementById('product-tags').value.split(',').map(t => t.trim()).filter(t => t);
const isActive = document.getElementById('product-active').checked;
const images = [];
document.querySelectorAll('.product-image-url').forEach(input => {
if (input.value.trim()) images.push(input.value.trim());
});
if (!title || !description || isNaN(price)) {
showToast('Please fill all required fields', 'error');
return;
}
const formData = new FormData();
formData.append('type', type);
formData.append('title', title);
formData.append('description', description);
formData.append('price', price);
if (deliveryTime) formData.append('deliveryTime', deliveryTime);
formData.append('tags', JSON.stringify(tags));
formData.append('images', JSON.stringify(images));
formData.append('isActive', isActive);
const fileInput = document.getElementById('product-file');
if (fileInput.files[0]) {
formData.append('file', fileInput.files[0]);
}
try {
const csrfToken = await fetchCsrfToken();
const url = productId ? `${cleanApiUrl}/api/store/products/${productId}` : `${cleanApiUrl}/api/store/products`;
const method = productId ? 'PUT' : 'POST';
const response = await fetch(url, {
method,
headers: { 'Authorization': `Bearer ${userToken}`, 'X-CSRF-Token': csrfToken || '' },
body: formData
});
if (response.ok) {
showToast(productId ? 'Product updated!' : 'Product added!', 'success');
closeProductModal();
loadStoreProducts();
} else {
const error = await response.json();
showToast(error.error || 'Failed to save product', 'error');
}
} catch (error) {
showToast('Error saving product', 'error');
}
}
async function deleteProduct(productId) {
if (!confirm('Delete this product permanently?')) return;
try {
const csrfToken = await fetchCsrfToken();
const response = await fetch(`${cleanApiUrl}/api/store/products/${productId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${userToken}`, 'X-CSRF-Token': csrfToken || '' }
});
if (response.ok) {
showToast('Product deleted', 'success');
loadStoreProducts();
} else {
showToast('Failed to delete product', 'error');
}
} catch (error) {
showToast('Error deleting product', 'error');
}
}
// ============================================
// 12. EVENT LISTENERS
// ============================================
document.getElementById('storeEnabled')?.addEventListener('change', toggleStoreConfig);
document.getElementById('save-store-settings')?.addEventListener('click', saveAllStoreSettings);
document.getElementById('preview-store')?.addEventListener('click', () => {
const nickname = document.getElementById('nickname')?.value;
if (nickname) window.open(`/shop/${nickname}`, '_blank');
});
document.getElementById('add-payment-method')?.addEventListener('click', openAddPaymentMethodModal);
document.getElementById('add-store-section')?.addEventListener('click', openAddStoreSectionModal);
document.getElementById('add-product-modal-btn')?.addEventListener('click', () => openProductModal());
document.getElementById('save-product')?.addEventListener('click', saveProduct);
document.querySelectorAll('.close-product-modal').forEach(btn => {
btn.addEventListener('click', closeProductModal);
});
document.querySelectorAll('.product-type-radio').forEach(radio => {
radio.addEventListener('change', (e) => toggleProductTypeFields(e.target.value));
});
// Tab switching
document.querySelectorAll('.store-tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
const tab = btn.dataset.storeTab;
document.querySelectorAll('.store-tab-btn').forEach(b => {
b.classList.remove('border-blue-500', 'text-blue-600');
b.classList.add('text-gray-500');
});
btn.classList.add('border-blue-500', 'text-blue-600');
document.querySelectorAll('.store-tab-content').forEach(content => {
content.classList.add('hidden');
});
document.getElementById(`store-tab-${tab}`)?.classList.remove('hidden');
});
});
// Product filters
document.querySelectorAll('.product-filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentProductFilter = btn.dataset.productFilter;
document.querySelectorAll('.product-filter-btn').forEach(b => {
b.classList.remove('bg-blue-500', 'text-white');
b.classList.add('bg-gray-200', 'dark:bg-gray-700');
});
btn.classList.add('bg-blue-500', 'text-white');
renderStoreProducts();
});
});
// Order filters
document.querySelectorAll('.order-filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentOrderFilter = btn.dataset.orderFilter;
document.querySelectorAll('.order-filter-btn').forEach(b => {
b.classList.remove('bg-blue-500', 'text-white');
b.classList.add('bg-gray-200', 'dark:bg-gray-700');
});
btn.classList.add('bg-blue-500', 'text-white');
renderSellerOrders();
});
});
// Store design listeners
document.getElementById('save-store-design')?.addEventListener('click', saveStoreDesign);
document.getElementById('reset-store-design')?.addEventListener('click', resetStoreDesign);
document.getElementById('preview-store-design')?.addEventListener('click', previewStoreDesign);
document.querySelectorAll('.store-bg-option').forEach(btn => {
btn.addEventListener('click', () => {
const bgType = btn.getAttribute('data-store-bg');
const gradientPanel = document.getElementById('store-gradient-panel');
const imagePanel = document.getElementById('store-image-panel');
gradientPanel?.classList.add('hidden');
imagePanel?.classList.add('hidden');
if (bgType === 'gradient' && gradientPanel) gradientPanel.classList.remove('hidden');
else if (bgType === 'image' && imagePanel) imagePanel.classList.remove('hidden');
});
});
const radiusSlider = document.getElementById('storeBorderRadius');
const radiusValue = document.getElementById('storeRadiusValue');
if (radiusSlider && radiusValue) {
radiusSlider.addEventListener('input', (e) => radiusValue.textContent = e.target.value + 'px');
}
document.getElementById('store-apply-gradient')?.addEventListener('click', () => {
showToast('Gradient selected. Save settings to apply.', 'success');
});
document.getElementById('store-apply-image')?.addEventListener('click', () => {
const url = document.getElementById('store-bg-url').value;
if (url) showToast('Background image selected. Save settings to apply.', 'success');
else showToast('Please enter an image URL', 'warning');
});
document.getElementById('add-product-image')?.addEventListener('click', () => addProductImageField());
// Load store design when section is shown
const observer = new MutationObserver(() => {
const storeSection = document.getElementById('store');
if (storeSection && !storeSection.classList.contains('hidden')) {
loadStoreDesign();
observer.disconnect();
}
});
observer.observe(document.getElementById('store-tab-design')?.parentElement || document.body, { attributes: true, attributeFilter: ['class'] });
// Load store settings on page load
setTimeout(() => {
if (document.getElementById('store') && !document.getElementById('store').classList.contains('hidden')) {
loadStoreSettings();
}
}, 500);
// ============================================
// 🌟 MY SUBSCRIPTION FUNCTIONS
// ============================================
// Load user subscription data
async function loadUserSubscription() {
if (!userToken) {
document.getElementById('current-subscription-card').innerHTML = `
Please Login
Login to view your subscription details
Login →
`;
return;
}
try {
const response = await fetchWithRefresh(`${cleanApiUrl}/api/subscription/my`);
if (response.ok) {
const data = await response.json();
renderCurrentSubscription(data.subscription);
renderSubscriptionHistory(data.history);
} else if (response.status === 404) {
renderCurrentSubscription(null);
renderSubscriptionHistory([]);
} else {
throw new Error('Failed to load subscription');
}
} catch (error) {
console.error('Error loading subscription:', error);
document.getElementById('current-subscription-card').innerHTML = `
Failed to load subscription
Please try again later
`;
}
}
function renderCurrentSubscription(subscription) {
const container = document.getElementById('current-subscription-card');
if (!container) return;
if (!subscription || subscription.status !== 'active') {
container.innerHTML = `
No Active Subscription
Subscribe to unlock premium features and create your store
`;
return;
}
const plan = subscription.planId;
const now = new Date();
const endDate = new Date(subscription.endDate);
const daysLeft = Math.ceil((endDate - now) / (1000 * 60 * 60 * 24));
const isExpiringSoon = daysLeft <= 7 && daysLeft > 0;
const isExpired = daysLeft <= 0;
let statusBadge = '';
let statusColor = '';
if (subscription.isTrial) {
statusBadge = '🎁 Free Trial';
statusColor = 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400';
} else if (isExpired) {
statusBadge = '📅 Expired';
statusColor = 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400';
} else if (isExpiringSoon) {
statusBadge = '⚠️ Expiring Soon';
statusColor = 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400';
} else {
statusBadge = '✅ Active';
statusColor = 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400';
}
// Build features list
const features = [];
if (plan?.features?.storeEnabled) features.push('🛒 Digital Store');
if (plan?.features?.pageBuilderEnabled) features.push('🎨 Page Builder');
if (plan?.features?.customDomain) features.push('🌐 Custom Domain');
if (plan?.features?.analyticsEnabled) features.push('📊 Advanced Analytics');
if (plan?.features?.prioritySupport) features.push('⭐ Priority Support');
if (plan?.features?.apiAccess) features.push('🔌 API Access');
if (plan?.features?.maxProducts > 0) features.push(`📦 ${plan.features.maxProducts === 0 ? 'Unlimited' : plan.features.maxProducts} Products`);
if (plan?.features?.teamMembers > 1) features.push(`👥 ${plan.features.teamMembers} Team Members`);
container.innerHTML = `
${escapeHtml(plan?.name || 'Unknown Plan')}
${statusBadge}
${plan?.description || 'Unlock premium features for your portfolio'}
${features.map(f => `${f}`).join('')}
Valid Until
${endDate.toLocaleDateString()}
${!isExpired ? `
${daysLeft} days remaining
` : '
Subscription expired
'}
${subscription.isTrial ? `
You're on a free trial. Your trial ends on ${new Date(subscription.trialEndDate).toLocaleDateString()}.
` : ''}
`;
}
function renderSubscriptionHistory(history) {
const container = document.getElementById('subscription-history-list');
if (!container) return;
if (!history || history.length === 0) {
container.innerHTML = `
No subscription history found
Your subscription history will appear here
`;
return;
}
function getStatusStyle(status) {
const styles = {
active: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',
pending: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400',
expired: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',
cancelled: 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
};
return styles[status] || 'bg-gray-100 text-gray-700';
}
function getStatusIcon(status) {
const icons = {
active: '✅',
pending: '⏳',
expired: '📅',
cancelled: '❌'
};
return icons[status] || '📋';
}
container.innerHTML = history.map(sub => `
${escapeHtml(sub.planId?.name || 'Unknown Plan')}
${getStatusIcon(sub.status)} ${sub.status.charAt(0).toUpperCase() + sub.status.slice(1)}
${sub.isTrial ? '
🎁 Trial' : ''}
Started: ${new Date(sub.createdAt).toLocaleDateString()}
${sub.endDate ? `
Ended: ${new Date(sub.endDate).toLocaleDateString()}
` : ''}
${sub.amount > 0 ? `
${sub.currencySymbol || '$'}${sub.amount}
${sub.paymentMethod || 'N/A'}
` : '
Free Trial
'}
${sub.status === 'pending' ? '
Awaiting approval
' : ''}
`).join('');
}
async function cancelUserSubscription() {
if (!confirm('⚠️ Are you sure you want to cancel your subscription?\n\nYou will lose access to premium features after the current period ends.\n\nThis action can be undone by renewing your subscription.')) return;
const btn = event.target;
const originalText = btn.innerHTML;
btn.innerHTML = '';
btn.disabled = true;
try {
const csrfToken = await fetchCsrfToken();
const response = await fetch(`${cleanApiUrl}/api/subscription/cancel`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken || ''
},
body: JSON.stringify({ reason: 'Cancelled by user from settings' })
});
const result = await response.json();
if (response.ok && result.success) {
showToast('✅ Subscription cancelled. You will retain access until the end of your billing period.', 'success');
await loadUserSubscription(); // Refresh the display
} else {
throw new Error(result.error || 'Failed to cancel');
}
} catch (error) {
console.error('Error cancelling subscription:', error);
showToast(error.message || 'Failed to cancel subscription', 'error');
} finally {
btn.innerHTML = originalText;
btn.disabled = false;
}
}
// ============================================
// SUBSCRIPTION FUNCTIONS FOR SETTINGS
// ============================================
async function loadUserSubscriptionForSettings() {
if (!userToken) return;
try {
const response = await fetchWithRefresh(`${cleanApiUrl}/api/subscription/my`);
if (response.ok) {
const data = await response.json();
renderUserSubscription(data.subscription);
renderUserSubscriptionHistory(data.history);
} else if (response.status === 404) {
renderUserSubscription(null);
renderUserSubscriptionHistory([]);
}
} catch (error) {
console.error('Error loading user subscription:', error);
}
}
function renderUserSubscription(subscription) {
const container = document.getElementById('current-subscription-card');
if (!container) return;
if (!subscription || subscription.status !== 'active') {
container.innerHTML = `
No Active Subscription
Subscribe to unlock premium features and create your store
View Plans →
`;
return;
}
const plan = subscription.planId;
const endDate = new Date(subscription.endDate);
const daysLeft = Math.ceil((endDate - new Date()) / (1000 * 60 * 60 * 24));
const isExpiringSoon = daysLeft <= 7 && daysLeft > 0;
container.innerHTML = `
${escapeHtml(plan.name)}
${subscription.isTrial ? '🎁 Free Trial' : (isExpiringSoon ? '⚠️ Expiring Soon' : '✅ Active')}
${plan.description || 'Premium plan with exclusive features'}
Valid Until
${endDate.toLocaleDateString()}
${daysLeft} days remaining
`;
}
function renderUserSubscriptionHistory(history) {
const container = document.getElementById('subscription-history-list');
if (!container) return;
if (!history || history.length === 0) {
container.innerHTML = `No subscription history found
`;
return;
}
container.innerHTML = history.map(sub => `
${escapeHtml(sub.planId?.name || 'Unknown')}
${new Date(sub.createdAt).toLocaleDateString()}
${sub.currencySymbol || '$'}${sub.amount || 0}
${sub.status}
`).join('');
}
async function cancelUserSubscriptionFromSettings() {
if (!confirm('⚠️ Cancel your subscription? You will lose access to premium features after the current period ends.')) return;
try {
const csrfToken = await fetchCsrfToken();
const response = await fetch(`${cleanApiUrl}/api/subscription/cancel`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${userToken}`, 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken || '' },
body: JSON.stringify({ reason: 'Cancelled by user from settings' })
});
if (response.ok) {
showToast('✅ Subscription cancelled. You will retain access until the end of your billing period.', 'success');
loadUserSubscriptionForSettings();
} else { throw new Error('Failed to cancel'); }
} catch (error) {
showToast(error.message, 'error');
}
}
// Add to initSettings
const originalInitSettings = initSettings;
window.initSettings = async function() {
await originalInitSettings();
if (userToken && document.getElementById('my-subscription')) {
await loadUserSubscriptionForSettings();
}
};
ف شوف بقا ايه التحديثات ولا ايه الربط اللي مطلوب عشان نربطهم ببعض وبنظام الاشتراكات وكمان الدفع والاستلام والتفعيل والانتهاء
والربط الكامل المتكامل
لان احنا كمان عاملين في الباك اند
// ============================================
// 🛒 DIGITAL STORE MODELS
// ============================================
// 1. نموذج المنتج (Product)
const productSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
type: { type: String, enum: ['digital', 'project', 'service'], required: true },
title: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true, min: 0 },
currency: { type: String, default: 'USD' },
// Digital Product Fields (ملفات للتحميل)
fileUrl: { type: String },
fileSize: { type: Number },
// Service Fields (خدمات)
deliveryTime: { type: String }, // مثلاً "3-5 business days"
// Common Fields
images: [{ type: String }],
tags: [String],
isActive: { type: Boolean, default: true },
salesCount: { type: Number, default: 0 },
averageRating: { type: Number, default: 0 },
reviews: [{
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
rating: { type: Number, min: 1, max: 5 },
comment: { type: String, maxlength: 500 },
createdAt: { type: Date, default: Date.now }
}],
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
// Indexes for better performance
productSchema.index({ userId: 1, type: 1, isActive: 1 });
productSchema.index({ userId: 1, createdAt: -1 });
productSchema.index({ tags: 1 });
const Product = mongoose.model('Product', productSchema);
// 2. نموذج الطلب (Order)
const orderSchema = new mongoose.Schema({
orderNumber: { type: String, unique: true },
buyerId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
sellerId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
items: [{
productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' },
productType: { type: String },
title: { type: String },
price: { type: Number },
fileUrl: { type: String } // للتحميل بعد الدفع (للمنتجات الرقمية)
}],
trackingNumber: { type: String }, // للشحن
cancelledAt: { type: Date },
cancellationReason: { type: String },
downloadableFiles: [{
fileName: String,
fileUrl: String,
expiresAt: Date,
downloadCount: { type: Number, default: 0 }
}],
subtotal: { type: Number, required: true },
totalAmount: { type: Number, required: true },
currency: { type: String, default: 'USD' },
status: {
type: String,
enum: ['pending', 'paid', 'processing', 'completed', 'cancelled', 'refunded'],
default: 'pending'
},
paymentMethod: { type: String }, // paypal, stripe, vodafone, instapay, bank
paymentDetails: { type: mongoose.Schema.Types.Mixed },
buyerNotes: { type: String, maxlength: 500 },
sellerNotes: { type: String, maxlength: 500 },
deliveredAt: { type: Date },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
// Generate unique order number before saving
orderSchema.pre('save', async function(next) {
if (!this.orderNumber) {
const date = new Date();
const timestamp = date.getTime().toString().slice(-8);
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
this.orderNumber = `ORD-${timestamp}-${random}`;
}
next();
});
orderSchema.index({ status: 1 });
const Order = mongoose.model('Order', orderSchema);
// 3. نموذج إعدادات المتجر (Store Settings)
const storeSettingsSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, unique: true },
enabled: { type: Boolean, default: false },
storeName: { type: String, default: '' },
storeLogo: { type: String, default: '' },
storeBanner: { type: String, default: '' },
storeDescription: { type: String, default: '' },
currency: { type: String, default: 'USD' },
currencySymbol: { type: String, default: '$' },
// طرق الدفع
paymentMethods: [{
type: { type: String, enum: ['paypal', 'stripe', 'vodafone', 'instapay', 'bank'] },
label: { type: String }, // "PayPal", "Vodafone Cash", إلخ
details: { type: mongoose.Schema.Types.Mixed }, // { email, phone, accountNumber }
isActive: { type: Boolean, default: true }
}],
// Page Builder (سحب وإفلات)
pageBuilder: {
type: mongoose.Schema.Types.Mixed,
default: {
sections: [
{ id: 'hero', type: 'hero', enabled: true, order: 1, content: { title: 'Welcome to my store', subtitle: '', buttonText: 'Shop Now', buttonLink: '#products' } },
{ id: 'featured', type: 'featured', enabled: true, order: 2, title: 'Featured Products', limit: 6 },
{ id: 'products', type: 'products', enabled: true, order: 3, title: 'All Products', layout: 'grid' },
{ id: 'categories', type: 'categories', enabled: true, order: 4, title: 'Categories' },
{ id: 'testimonials', type: 'testimonials', enabled: false, order: 5, title: 'What Customers Say' },
{ id: 'faq', type: 'faq', enabled: false, order: 6, title: 'Frequently Asked Questions' },
{ id: 'contact', type: 'contact', enabled: false, order: 7, title: 'Contact Me' }
]
}
},
// Social Links (خاصة بالمتجر)
socialLinks: {
instagram: { type: String, default: '' },
facebook: { type: String, default: '' },
twitter: { type: String, default: '' },
tiktok: { type: String, default: '' }
},
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
storeSettingsSchema.index({ userId: 1 });
storeSettingsSchema.index({ storeName: 1 });
const StoreSettings = mongoose.model('StoreSettings', storeSettingsSchema);
// ============================================
// 📋 SUBSCRIPTION MODELS
// ============================================
// 1. نموذج خطط الاشتراك (Subscription Plans)
const subscriptionPlanSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true }, // e.g., "Starter", "Pro", "Business"
nameAr: { type: String }, // اسم الخطة بالعربية
description: { type: String },
descriptionAr: { type: String },
price: { type: Number, required: true, min: 0 },
currency: { type: String, default: 'USD' },
currencySymbol: { type: String, default: '$' },
duration: { type: String, enum: ['monthly', 'yearly', 'lifetime'], default: 'monthly' },
durationDays: { type: Number, default: 30 }, // عدد أيام الاشتراك
// مميزات الخطة
features: {
storeEnabled: { type: Boolean, default: false }, // هل يسمح بإنشاء متجر؟
maxProducts: { type: Number, default: 0 }, // عدد المنتجات المسموحة (0 = غير محدود)
maxDigitalProducts: { type: Number, default: 0 },
maxProjects: { type: Number, default: 0 },
maxServices: { type: Number, default: 0 },
pageBuilderEnabled: { type: Boolean, default: false }, // هل يسمح ب Page Builder؟
customDomain: { type: Boolean, default: false }, // هل يسمح بنطاق مخصص؟
analyticsEnabled: { type: Boolean, default: false }, // هل يسمح بالتحليلات المتقدمة؟
prioritySupport: { type: Boolean, default: false }, // دعم أولوية
removeBranding: { type: Boolean, default: false }, // إزالة علامة MGZon
teamMembers: { type: Number, default: 1 }, // عدد أعضاء الفريق المسموحين
apiAccess: { type: Boolean, default: false }, // وصول API
customCss: { type: Boolean, default: false }, // CSS مخصص
advancedAnalytics: { type: Boolean, default: false }, // تحليلات متقدمة
exportData: { type: Boolean, default: false } // تصدير البيانات
},
// إعدادات التجربة المجانية
hasFreeTrial: { type: Boolean, default: false },
freeTrialDays: { type: Number, default: 0 },
// إعدادات العرض
badge: { type: String, enum: ['popular', 'best-value', 'enterprise', null], default: null },
badgeColor: { type: String, default: '#10b981' },
icon: { type: String, default: 'bx-star' },
isActive: { type: Boolean, default: true },
order: { type: Number, default: 0 },
// صلاحيات مخصصة (JSON للمستقبل)
customPermissions: { type: mongoose.Schema.Types.Mixed, default: {} },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
subscriptionPlanSchema.index({ isActive: 1, order: 1 });
subscriptionPlanSchema.index({ name: 1 });
const SubscriptionPlan = mongoose.model('SubscriptionPlan', subscriptionPlanSchema);
// 2. نموذج طرق دفع الاشتراكات (Platform Payment Methods)
const platformPaymentMethodSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true }, // e.g., "Bank Misr", "Vodafone Cash"
nameAr: { type: String },
type: { type: String, enum: ['bank', 'mobile_wallet', 'online'], required: true },
icon: { type: String, default: 'bx-bank' },
instructions: { type: String }, // تعليمات الدفع
instructionsAr: { type: String },
// بيانات الحساب حسب النوع
bankDetails: {
bankName: { type: String },
accountName: { type: String },
accountNumber: { type: String },
iban: { type: String },
swiftCode: { type: String },
branch: { type: String }
},
mobileWalletDetails: {
provider: { type: String }, // Vodafone, InstaPay, etc.
phoneNumber: { type: String }
},
onlineDetails: {
provider: { type: String }, // Stripe, PayPal, etc.
apiKey: { type: String },
webhookSecret: { type: String },
isActive: { type: Boolean, default: false }
},
isActive: { type: Boolean, default: true },
order: { type: Number, default: 0 },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
const PlatformPaymentMethod = mongoose.model('PlatformPaymentMethod', platformPaymentMethodSchema);
// 3. نموذج اشتراكات المستخدمين (User Subscriptions)
const userSubscriptionSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
planId: { type: mongoose.Schema.Types.ObjectId, ref: 'SubscriptionPlan', required: true },
// تفاصيل الاشتراك
status: {
type: String,
enum: ['pending', 'active', 'expired', 'cancelled', 'refunded'],
default: 'pending'
},
startDate: { type: Date },
endDate: { type: Date },
// التجربة المجانية
isTrial: { type: Boolean, default: false },
trialEndDate: { type: Date },
// الدفع
paymentMethod: { type: String },
paymentDetails: { type: mongoose.Schema.Types.Mixed },
paymentProof: { type: String }, // رابط صورة إثبات الدفع
paymentProofPublicId: { type: String },
amount: { type: Number },
currency: { type: String, default: 'USD' },
transactionId: { type: String },
// ملاحظات الإدارة
adminNotes: { type: String },
approvedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
approvedAt: { type: Date },
// التجديد التلقائي
autoRenew: { type: Boolean, default: false },
cancelledAt: { type: Date },
cancellationReason: { type: String },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
userSubscriptionSchema.index({ userId: 1, status: 1 });
userSubscriptionSchema.index({ endDate: 1 });
userSubscriptionSchema.index({ status: 1, endDate: 1 });
const UserSubscription = mongoose.model('UserSubscription', userSubscriptionSchema);
// 4. نموذج إعدادات نظام الاشتراكات
const subscriptionSettingsSchema = new mongoose.Schema({
// إعدادات التجربة المجانية العامة
defaultFreeTrialDays: { type: Number, default: 14 },
allowFreeTrialForExistingUsers: { type: Boolean, default: true },
// إعدادات العمولة
commissionEnabled: { type: Boolean, default: false },
commissionRate: { type: Number, default: 0 }, // نسبة مئوية
// إعدادات الدفع
currency: { type: String, default: 'USD' },
currencySymbol: { type: String, default: '$' },
// إعدادات البريد الإلكتروني
sendInvoiceEmail: { type: Boolean, default: true },
sendReminderEmail: { type: Boolean, default: true },
reminderDays: { type: Number, default: 7 },
// إعدادات الإشعارات
notifyAdminOnNewSubscription: { type: Boolean, default: true },
notifyUserOnStatusChange: { type: Boolean, default: true },
updatedAt: { type: Date, default: Date.now }
});
const SubscriptionSettings = mongoose.model('SubscriptionSettings', subscriptionSettingsSchema);
// 5. نموذج أرباح الاشتراكات (Subscription Earnings)
const subscriptionEarningsSchema = new mongoose.Schema({
subscriptionId: { type: mongoose.Schema.Types.ObjectId, ref: 'UserSubscription' },
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
planId: { type: mongoose.Schema.Types.ObjectId, ref: 'SubscriptionPlan' },
amount: { type: Number },
currency: { type: String, default: 'USD' },
status: { type: String, enum: ['pending', 'completed', 'refunded'], default: 'pending' },
paymentMethod: { type: String },
transactionId: { type: String },
payoutStatus: { type: String, enum: ['pending', 'processing', 'completed', 'failed'], default: 'pending' },
payoutDate: { type: Date },
adminNotes: { type: String },
createdAt: { type: Date, default: Date.now }
});
const SubscriptionEarnings = mongoose.model('SubscriptionEarnings', subscriptionEarningsSchema);
// مؤشرات لتحسين الأداء
userSubscriptionSchema.index({ userId: 1, status: 1 });
userSubscriptionSchema.index({ endDate: 1 });
userSubscriptionSchema.index({ status: 1, endDate: 1 });
subscriptionPlanSchema.index({ isActive: 1, order: 1 });
platformPaymentMethodSchema.index({ isActive: 1, order: 1 });
subscriptionEarningsSchema.index({ createdAt: -1 });
subscriptionEarningsSchema.index({ status: 1 });
// 4. نموذج الإيرادات (Earnings)
const earningsSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, unique: true },
totalSales: { type: Number, default: 0 },
totalEarnings: { type: Number, default: 0 },
pendingPayout: { type: Number, default: 0 },
lifetimeEarnings: { type: Number, default: 0 },
transactions: [{
orderId: { type: mongoose.Schema.Types.ObjectId, ref: 'Order' },
amount: { type: Number },
type: { type: String, enum: ['sale', 'refund', 'payout'] },
status: { type: String, enum: ['pending', 'completed', 'failed'] },
notes: { type: String },
createdAt: { type: Date, default: Date.now }
}],
monthlyStats: [{
month: { type: String }, // "2024-01"
sales: { type: Number, default: 0 },
earnings: { type: Number, default: 0 }
}],
updatedAt: { type: Date, default: Date.now }
});
const Earnings = mongoose.model('Earnings', earningsSchema);
const couponSchema = new mongoose.Schema({
storeId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
code: { type: String, required: true, uppercase: true, unique: true },
discountType: { type: String, enum: ['percentage', 'fixed'], default: 'percentage' },
discountValue: { type: Number, required: true }, // 10 for 10% or 10 for $10
minPurchase: { type: Number, default: 0 },
maxDiscount: { type: Number }, // أقصى خصم للمنتجات
validFrom: { type: Date, default: Date.now },
validUntil: { type: Date },
usageLimit: { type: Number }, // عدد مرات الاستخدام
usageCount: { type: Number, default: 0 },
applicableProducts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Product' }], // empty = all products
isActive: { type: Boolean, default: true },
createdAt: { type: Date, default: Date.now }
});
const Coupon = mongoose.model('Coupon', couponSchema);
const storeFollowSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // المتابع
storeId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // صاحب المتجر
createdAt: { type: Date, default: Date.now }
});
storeFollowSchema.index({ userId: 1, storeId: 1 }, { unique: true });
const StoreFollow = mongoose.model('StoreFollow', storeFollowSchema);
// 5. نموذج عربة التسوق المؤقتة (Cart - تخزين في MongoDB كـ backup)
const cartSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, unique: true },
items: [{
productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' },
quantity: { type: Number, default: 1 }
}],
updatedAt: { type: Date, default: Date.now }
});
const Cart = mongoose.model('Cart', cartSchema);
// Schema for layout presets (if not exists, create model)
const layoutPresetSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
name: { type: String, required: true },
sectionOrder: [{ type: mongoose.Schema.Types.Mixed }],
theme: {
primaryColor: { type: String, default: '#3b82f6' },
fontFamily: { type: String, default: 'Inter' }
},
createdAt: { type: Date, default: Date.now }
});
const LayoutPreset = mongoose.models.LayoutPreset || mongoose.model('LayoutPreset', layoutPresetSchema);
const userSchema = new mongoose.Schema({
username: { type: String, sparse: true },
email: { type: String, required: true },
password: { type: String },
isAdmin: { type: Boolean, default: false },
googleId: String,
googleAccessToken: String,
projectProgress: { type: mongoose.Schema.Types.Mixed, default: {} },
googleRefreshToken: String,
facebookId: String,
facebookAccessToken: String,
facebookRefreshToken: String,
githubId: String,
githubAccessToken: String,
githubRefreshToken: String,
mgzonId: String,
mgzonAccessToken: String,
mgzonRefreshToken: String,
emailVerified: { type: Boolean, default: false },
otp: String,
otpExpires: Date,
refreshTokens: [{ token: String, createdAt: { type: Date, default: Date.now } }],
notifications: [{
message: { type: String, required: true },
type: { type: String, enum: ['info', 'warning', 'success', 'admin_report', 'admin_info', 'warning'], default: 'info' },
link: { type: String, default: '' },
read: { type: Boolean, default: false },
createdAt: { type: Date, default: Date.now },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} }
}],
profile: {
type: mongoose.Schema.Types.Mixed,
default: {
gender: { type: String, enum: ['male', 'female', 'other'], default: 'male' },
showGender: { type: Boolean, default: false },
storeEnabled: { type: Boolean, default: false },
// ✅ إعدادات إظهار/إخفاء الأقسام
sectionVisibility: {
about: true,
experience: true,
education: true,
certificates: true,
skills: true,
projects: true,
interests: true,
contactInfo: true,
socialLinks: true,
stats: true,
activity: true,
contributionGraph: true,
alsoViewed: true,
suggestions: true,
pages: true,
jobApplications: true,
analytics: true
},
sectionOrder: [],
// ✅ الأقسام المخصصة (Custom Sections)
customSections: {
type: [{
_id: { type: mongoose.Schema.Types.ObjectId, default: () => new mongoose.Types.ObjectId() },
name: { type: String, required: true },
type: { type: String, enum: ['text', 'skills', 'links'], default: 'text' },
content: { type: mongoose.Schema.Types.Mixed, default: {} },
isVisible: { type: Boolean, default: true },
settings: {
// Style settings
fontSize: { type: String, enum: ['xs', 'sm', 'base', 'lg', 'xl', '2xl'], default: 'base' },
textColor: { type: String, default: '' },
backgroundColor: { type: String, default: '' },
textAlign: { type: String, enum: ['left', 'center', 'right'], default: 'left' },
padding: { type: String, enum: ['sm', 'md', 'lg'], default: 'md' },
borderRadius: { type: String, enum: ['none', 'sm', 'md', 'lg', 'xl'], default: 'md' },
shadow: { type: String, enum: ['none', 'sm', 'md', 'lg', 'xl'], default: 'none' },
animation: { type: String, enum: ['none', 'fade-up', 'fade-down', 'zoom-in', 'slide-left', 'slide-right'], default: 'none' },
icon: { type: String, default: '' },
// Layout settings
columns: { type: Number, min: 1, max: 4, default: 1 },
showDivider: { type: Boolean, default: true },
dividerColor: { type: String, default: '' },
// Advanced
customClass: { type: String, default: '' },
customCss: { type: String, default: '' }
},
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
}],
default: []
},
designSettings: {
primaryColor: { type: String, default: '#3b82f6' },
secondaryColor: { type: String, default: '#8b5cf6' },
fontFamily: { type: String, default: 'Inter, sans-serif' },
borderRadius: { type: Number, default: 16 },
zoomLevel: { type: Number, default: 100 },
shadowType: { type: String, default: 'md' },
animationType: { type: String, default: 'none' },
glassEffect: { type: Boolean, default: false },
backgroundType: { type: String, default: 'default' }, // default, dots, grid, cross, gradient, image
backgroundValue: { type: String, default: '' },
customCss: { type: String, default: '' }
},
// ✅ إضافة إعدادات شكل لكل قسم
sectionStyleSettings: {},
sectionNames: {
type: mongoose.Schema.Types.Mixed,
default: {
about: 'About / Bio',
experience: 'Experience',
education: 'Education',
certificates: 'Licenses & certificates',
skills: 'Skills & expertise',
projects: 'Projects',
interests: 'Interests',
contactInfo: 'Contact info',
activity: 'Activity',
contributionGraph: 'Contribution Graph',
alsoViewed: 'Who viewed also viewed',
suggestions: 'People you may know',
pages: 'You might like',
analytics: 'Analytics',
jobApplications: 'Job Applications'
}
},
// ✅ إعدادات AI Bot (جوه الـ default)
aiBot: {
enabled: false, // هل البوت مفعل؟
provider: 'mgzon', // mgzon ولا custom
customApiKey: '', // مفتاح HuggingFace الخاص
customModel: 'Qwen/Qwen2.5-7B-Instruct', // الموديل المخصص
temperature: 0.7, // إبداعية الردود (0-1)
maxTokens: 400, // أقصى طول للرد
systemPrompt: '', // برومبت مخصص
totalQueries: 0, // عدد الأسئلة المستلمة
lastQueryAt: null // آخر سؤال
},
}
},
interactionsSettings: {
type: {
showToPublic: { type: Boolean, default: true },
showToLoggedIn: { type: Boolean, default: true },
showOnlyToOwner: { type: Boolean, default: false },
showToSpecificUsers: [{ type: String }],
hideProjects: [{ type: Number }],
hideComments: [{ type: mongoose.Schema.Types.ObjectId }]
},
default: {
showToPublic: true,
showToLoggedIn: true,
showOnlyToOwner: false,
showToSpecificUsers: [],
hideProjects: [],
hideComments: []
}
}
}, {
minimize: false,
});
// userSchema.index({ username: 1 }, { unique: true, sparse: true });
// userSchema.index({ 'profile.nickname': 1 }, { unique: true, sparse: true });
userSchema.index({ email: 1 }, { unique: true });
const User = mongoose.model('User', userSchema);
// Product indexes
productSchema.index({ salesCount: -1 });
productSchema.index({ createdAt: -1 });
// Order indexes
orderSchema.index({ buyerId: 1, createdAt: -1 });
orderSchema.index({ sellerId: 1, createdAt: -1 });
orderSchema.index({ orderNumber: 1 });
orderSchema.index({ status: 1, createdAt: -1 });
// Coupon indexes
couponSchema.index({ storeId: 1, code: 1 });
couponSchema.index({ validUntil: 1 });
// StoreFollow indexes
storeFollowSchema.index({ storeId: 1 });
// ============================================
// 🛒 STORE API ENDPOINTS
// ============================================
// ========== 1. إعدادات المتجر ==========
// GET /api/store/settings - جلب إعدادات المتجر
app.get('/api/store/settings', authenticateToken, async (req, res) => {
try {
let settings = await StoreSettings.findOne({ userId: req.user.userId });
if (!settings) {
// إنشاء إعدادات افتراضية
settings = new StoreSettings({ userId: req.user.userId });
await settings.save();
}
res.json({ success: true, settings });
} catch (error) {
console.error('Error fetching store settings:', error);
res.status(500).json({ error: 'Failed to fetch store settings' });
}
});
// PUT /api/store/settings - حفظ إعدادات المتجر
app.put('/api/store/settings', authenticateToken, async (req, res) => {
try {
const { storeName, storeLogo, storeBanner, storeDescription, currency, paymentMethods, pageBuilder, socialLinks } = req.body;
const settings = await StoreSettings.findOneAndUpdate(
{ userId: req.user.userId },
{
storeName,
storeLogo,
storeBanner,
storeDescription,
currency,
...(currency === 'USD' ? { currencySymbol: '$' } : currency === 'EUR' ? { currencySymbol: '€' } : currency === 'GBP' ? { currencySymbol: '£' } : { currencySymbol: 'ج.م' }),
paymentMethods: paymentMethods || [],
pageBuilder: pageBuilder || undefined,
socialLinks: socialLinks || {},
updatedAt: Date.now()
},
{ upsert: true, new: true }
);
// تحديث الـ profile بإنه المتجر مفعل
await User.findByIdAndUpdate(req.user.userId, {
'profile.storeEnabled': true
});
res.json({ success: true, settings });
} catch (error) {
console.error('Error saving store settings:', error);
res.status(500).json({ error: 'Failed to save store settings' });
}
});
// ============================================
// POST /api/store/enable - تفعيل/تعطيل المتجر (مع التحقق من الاشتراك)
// ============================================
app.post('/api/store/enable', authenticateToken, async (req, res) => {
try {
const { enabled } = req.body;
// ✅ إذا كان المستخدم يحاول تفعيل المتجر (enabled = true)
if (enabled === true || enabled === 'true') {
// ============================================
// 1. التحقق من وجود اشتراك نشط
// ============================================
const activeSubscription = await UserSubscription.findOne({
userId: req.user.userId,
status: 'active'
}).populate('planId');
if (!activeSubscription) {
return res.status(403).json({
success: false,
error: 'You need an active subscription to create a store. Please subscribe first.',
redirect: '/subscription.html',
code: 'NO_SUBSCRIPTION'
});
}
// ============================================
// 2. التحقق من أن الخطة تدعم إنشاء متجر
// ============================================
const plan = activeSubscription.planId;
if (!plan || !plan.features || !plan.features.storeEnabled) {
// جلب الخطة الحالية للمستخدم
const currentPlan = await SubscriptionPlan.findById(activeSubscription.planId);
const planName = currentPlan?.name || 'your current plan';
// البحث عن خطة تدعم المتجر
const storePlans = await SubscriptionPlan.find({
'features.storeEnabled': true,
isActive: true
}).sort({ price: 1 }).limit(3);
const upgradeOptions = storePlans.map(p => ({
name: p.name,
price: p.price,
currency: p.currencySymbol || '$'
}));
return res.status(403).json({
success: false,
error: `Your current plan "${planName}" does not include store functionality. Please upgrade your plan to start selling.`,
redirect: '/subscription.html',
code: 'STORE_NOT_IN_PLAN',
currentPlan: {
name: planName,
features: plan.features
},
upgradeOptions: upgradeOptions
});
}
// ============================================
// 3. التحقق من حدود المنتجات (إذا كانت الخطة لها حدود)
// ============================================
const storeSettings = await StoreSettings.findOne({ userId: req.user.userId });
const existingProductsCount = await Product.countDocuments({
userId: req.user.userId,
isActive: true
});
const maxProducts = plan.features.maxProducts || 0;
if (maxProducts > 0 && existingProductsCount >= maxProducts) {
return res.status(403).json({
success: false,
error: `Your plan allows up to ${maxProducts} products. You currently have ${existingProductsCount} products. Please upgrade your plan to add more products.`,
redirect: '/subscription.html',
code: 'PRODUCT_LIMIT_REACHED',
currentLimit: maxProducts,
currentCount: existingProductsCount
});
}
// ============================================
// 4. التحقق من صلاحية الاشتراك (لم تنتهي صلاحيته)
// ============================================
const now = new Date();
if (activeSubscription.endDate && new Date(activeSubscription.endDate) < now) {
// الاشتراك منتهي الصلاحية
activeSubscription.status = 'expired';
await activeSubscription.save();
return res.status(403).json({
success: false,
error: 'Your subscription has expired. Please renew to continue using store features.',
redirect: '/subscription.html',
code: 'SUBSCRIPTION_EXPIRED'
});
}
// ============================================
// 5. التحقق من التجربة المجانية (إذا كانت منتهية)
// ============================================
if (activeSubscription.isTrial && activeSubscription.trialEndDate) {
if (new Date(activeSubscription.trialEndDate) < now) {
// انتهت التجربة المجانية
return res.status(403).json({
success: false,
error: 'Your free trial has ended. Please subscribe to continue using store features.',
redirect: '/subscription.html',
code: 'TRIAL_EXPIRED'
});
}
// عرض عدد الأيام المتبقية في التجربة
const daysLeft = Math.ceil((new Date(activeSubscription.trialEndDate) - now) / (1000 * 60 * 60 * 24));
res.setHeader('X-Trial-Days-Left', daysLeft);
}
}
// ============================================
// 6. تفعيل أو تعطيل المتجر
// ============================================
await User.findByIdAndUpdate(req.user.userId, {
'profile.storeEnabled': enabled === true || enabled === 'true'
});
if (enabled === true || enabled === 'true') {
await StoreSettings.findOneAndUpdate(
{ userId: req.user.userId },
{
enabled: true,
updatedAt: Date.now(),
// إذا كانت الإعدادات غير موجودة، استخدم الافتراضيات
$setOnInsert: {
currency: 'USD',
currencySymbol: '$',
paymentMethods: [],
pageBuilder: {
sections: [
{ id: 'hero', type: 'hero', enabled: true, order: 1, content: { title: 'Welcome to my store', subtitle: '', buttonText: 'Shop Now', buttonLink: '#products' } },
{ id: 'products', type: 'products', enabled: true, order: 2, title: 'All Products', layout: 'grid' }
]
}
}
},
{ upsert: true }
);
} else {
await StoreSettings.findOneAndUpdate(
{ userId: req.user.userId },
{ enabled: false }
);
}
// ============================================
// 7. تسجيل النشاط في التحليلات
// ============================================
try {
await ProfileAnalytics.findOneAndUpdate(
{ userId: req.user.userId },
{
$push: {
activities: {
type: 'store_toggle',
action: enabled ? 'enabled' : 'disabled',
timestamp: new Date()
}
},
$inc: { storeToggles: 1 }
},
{ upsert: true }
);
} catch (analyticsError) {
console.error('Analytics error:', analyticsError);
// لا نوقف التنفيذ
}
// ============================================
// 8. إرسال الرد مع معلومات الاشتراك
// ============================================
const responseData = {
success: true,
enabled: enabled === true || enabled === 'true'
};
// إضافة معلومات الاشتراك في الرد إذا كان المتجر مفعلاً
if (enabled === true || enabled === 'true') {
const subscription = await UserSubscription.findOne({
userId: req.user.userId,
status: 'active'
}).populate('planId');
if (subscription) {
responseData.subscription = {
planName: subscription.planId?.name,
planFeatures: subscription.planId?.features,
endDate: subscription.endDate,
isTrial: subscription.isTrial,
trialEndDate: subscription.trialEndDate
};
}
}
res.json(responseData);
} catch (error) {
console.error('Error toggling store:', error);
res.status(500).json({
success: false,
error: 'Failed to toggle store. Please try again later.',
code: 'SERVER_ERROR'
});
}
});
// ============================================
// MISSING API ENDPOINTS
// ============================================
// GET /api/store/:storeId/follow-status - جلب حالة متابعة المتجر
app.get('/api/store/:storeId/follow-status', authenticateToken, async (req, res) => {
try {
const storeId = req.params.storeId;
const isFollowing = await StoreFollow.exists({ userId: req.user.userId, storeId });
const followersCount = await StoreFollow.countDocuments({ storeId });
res.json({ success: true, isFollowing: !!isFollowing, followersCount });
} catch (error) {
res.status(500).json({ error: 'Failed to fetch follow status' });
}
});
// POST /api/store/orders/:orderId/cancel - إلغاء طلب
app.post('/api/store/orders/:orderId/cancel', authenticateToken, async (req, res) => {
try {
const order = await Order.findOne({ _id: req.params.orderId, buyerId: req.user.userId });
if (!order) return res.status(404).json({ error: 'Order not found' });
if (order.status !== 'pending') {
return res.status(400).json({ error: 'Cannot cancel order in current status' });
}
order.status = 'cancelled';
order.cancelledAt = new Date();
order.cancellationReason = req.body.reason || 'Cancelled by user';
await order.save();
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: 'Failed to cancel order' });
}
});
// GET /api/subscription/settings - جلب إعدادات الاشتراكات
app.get('/api/subscription/settings', authenticateToken, async (req, res) => {
try {
let settings = await SubscriptionSettings.findOne();
if (!settings) {
settings = new SubscriptionSettings();
await settings.save();
}
res.json({ success: true, settings });
} catch (error) {
res.status(500).json({ error: 'Failed to fetch subscription settings' });
}
});
// PUT /api/subscription/settings - تحديث إعدادات الاشتراكات
app.put('/api/subscription/settings', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const settings = await SubscriptionSettings.findOneAndUpdate(
{},
req.body,
{ upsert: true, new: true }
);
res.json({ success: true, settings });
} catch (error) {
res.status(500).json({ error: 'Failed to save subscription settings' });
}
});
// ============================================
// GET /api/subscription/check - التحقق من صلاحية الاشتراك
// ============================================
app.get('/api/subscription/check', authenticateToken, async (req, res) => {
try {
const subscription = await UserSubscription.findOne({
userId: req.user.userId,
status: 'active'
}).populate('planId');
if (!subscription) {
return res.json({
success: true,
hasActiveSubscription: false,
canCreateStore: false,
message: 'No active subscription'
});
}
const plan = subscription.planId;
const now = new Date();
const isExpired = subscription.endDate && new Date(subscription.endDate) < now;
if (isExpired) {
subscription.status = 'expired';
await subscription.save();
return res.json({
success: true,
hasActiveSubscription: false,
isExpired: true,
canCreateStore: false,
message: 'Subscription expired'
});
}
const canCreateStore = plan?.features?.storeEnabled === true;
const daysLeft = Math.ceil((new Date(subscription.endDate) - now) / (1000 * 60 * 60 * 24));
res.json({
success: true,
hasActiveSubscription: true,
isTrial: subscription.isTrial || false,
trialEndDate: subscription.trialEndDate,
plan: {
id: plan._id,
name: plan.name,
features: plan.features,
price: plan.price,
currencySymbol: plan.currencySymbol || '$'
},
canCreateStore,
endDate: subscription.endDate,
daysLeft: daysLeft > 0 ? daysLeft : 0,
autoRenew: subscription.autoRenew || false
});
} catch (error) {
console.error('Error checking subscription:', error);
res.status(500).json({ error: 'Failed to check subscription' });
}
});
// ============================================
// POST /api/subscription/renew - تجديد الاشتراك
// ============================================
app.post('/api/subscription/renew', authenticateToken, upload.single('paymentProof'), async (req, res) => {
try {
const { planId, paymentMethodId, paymentDetails } = req.body;
// العثور على الاشتراك المنتهي أو الملغي
const oldSubscription = await UserSubscription.findOne({
userId: req.user.userId,
status: { $in: ['expired', 'cancelled'] }
}).sort({ createdAt: -1 });
const plan = await SubscriptionPlan.findById(planId);
if (!plan || !plan.isActive) {
return res.status(404).json({ error: 'Plan not found' });
}
const paymentMethod = await PlatformPaymentMethod.findById(paymentMethodId);
if (!paymentMethod || !paymentMethod.isActive) {
return res.status(404).json({ error: 'Payment method not found' });
}
let paymentProofUrl = null;
let paymentProofPublicId = null;
if (req.file) {
paymentProofUrl = req.file.path;
paymentProofPublicId = req.file.filename;
}
const startDate = new Date();
const endDate = new Date();
endDate.setDate(endDate.getDate() + plan.durationDays);
const newSubscription = new UserSubscription({
userId: req.user.userId,
planId,
status: plan.price === 0 ? 'active' : 'pending',
startDate,
endDate,
isTrial: false,
paymentMethod: paymentMethod.name,
paymentDetails: paymentDetails ? JSON.parse(paymentDetails) : {},
paymentProof: paymentProofUrl,
paymentProofPublicId,
amount: plan.price,
currency: plan.currency,
autoRenew: false,
createdAt: new Date()
});
await newSubscription.save();
// إذا كان السعر 0 (مجاني) فعّل مباشرة
if (plan.price === 0) {
await updateUserPermissions(req.user.userId, plan);
}
// إشعار للإدارة إذا كان مدفوع
if (plan.price > 0) {
const adminUsers = await User.find({ isAdmin: true });
for (const admin of adminUsers) {
const notification = new Notification({
userId: admin._id,
type: 'subscription_renewal',
actorId: req.user.userId,
actorName: req.user.username,
content: `Subscription renewal request from ${req.user.username} for plan ${plan.name}`,
targetId: `/admin/subscriptions/${newSubscription._id}`,
targetType: 'subscription',
read: false
});
await notification.save();
}
}
res.json({
success: true,
subscription: newSubscription,
message: plan.price === 0 ? 'Subscription renewed successfully!' : 'Renewal request submitted for approval'
});
} catch (error) {
console.error('Error renewing subscription:', error);
res.status(500).json({ error: 'Failed to renew subscription' });
}
});
// ============================================
// POST /api/subscription/cancel - إلغاء اشتراك المستخدم
// ============================================
app.post('/api/subscription/cancel', authenticateToken, async (req, res) => {
try {
const { reason } = req.body;
// العثور على الاشتراك النشط
const subscription = await UserSubscription.findOne({
userId: req.user.userId,
status: 'active'
}).populate('planId');
if (!subscription) {
return res.status(404).json({
success: false,
error: 'No active subscription found'
});
}
// تحديث حالة الاشتراك
subscription.status = 'cancelled';
subscription.cancelledAt = new Date();
subscription.cancellationReason = reason || 'Cancelled by user';
subscription.autoRenew = false;
subscription.updatedAt = new Date();
await subscription.save();
// تعطيل صلاحيات المتجر (إذا كان لديه متجر)
await User.findByIdAndUpdate(req.user.userId, {
'profile.storeEnabled': false
});
// إشعار للمستخدم
const notification = new Notification({
userId: req.user.userId,
type: 'subscription_cancelled',
content: `Your subscription to "${subscription.planId.name}" has been cancelled. You will retain access until ${new Date(subscription.endDate).toLocaleDateString()}.`,
targetId: `/subscription`,
targetType: 'subscription',
read: false
});
await notification.save();
// إرسال إيميل
const user = await User.findById(req.user.userId);
const emailHtml = `
Subscription Cancelled
Dear ${user.username},
Your subscription to "${subscription.planId.name}" has been cancelled.
Access valid until: ${new Date(subscription.endDate).toLocaleDateString()}
You can resubscribe at any time from your account settings.
View Plans →
`;
await sendStoreEmailWithBrevo(user.email, 'Subscription Cancelled', emailHtml, '');
res.json({
success: true,
message: 'Subscription cancelled successfully',
endDate: subscription.endDate
});
} catch (error) {
console.error('Error cancelling subscription:', error);
res.status(500).json({ error: 'Failed to cancel subscription' });
}
});
// ========== 2. المنتجات ==========
// GET /api/store/products - جلب منتجات المتجر الحالي
app.get('/api/store/products', authenticateToken, async (req, res) => {
try {
const products = await Product.find({ userId: req.user.userId, isActive: true })
.sort({ createdAt: -1 });
res.json({ success: true, products });
} catch (error) {
console.error('Error fetching products:', error);
res.status(500).json({ error: 'Failed to fetch products' });
}
});
// GET /api/store/:username/products - جلب منتجات متجر معين (للزوار)
app.get('/api/store/:username/products', async (req, res) => {
try {
const user = await User.findOne({
$or: [
{ 'profile.nickname': { $regex: `^${req.params.username}$`, $options: 'i' } },
{ username: { $regex: `^${req.params.username}$`, $options: 'i' } }
]
});
if (!user) {
return res.status(404).json({ error: 'Store not found' });
}
// التحقق من أن المتجر مفعل
const storeSettings = await StoreSettings.findOne({ userId: user._id });
if (!storeSettings || !storeSettings.enabled) {
return res.status(403).json({ error: 'Store is not active' });
}
const products = await Product.find({ userId: user._id, isActive: true })
.sort({ createdAt: -1 });
res.json({ success: true, products, storeSettings });
} catch (error) {
console.error('Error fetching store products:', error);
res.status(500).json({ error: 'Failed to fetch products' });
}
});
// POST /api/store/products - إضافة منتج جديد
app.post('/api/store/products', authenticateToken, upload.single('file'), async (req, res) => {
try {
const { type, title, description, price, currency, deliveryTime, tags, images } = req.body;
// ============================================
// ✅ أضف هذا الكود هنا - قبل إنشاء المنتج
// ============================================
// 1. التحقق من وجود اشتراك نشط
const activeSubscription = await UserSubscription.findOne({
userId: req.user.userId,
status: 'active'
}).populate('planId');
if (!activeSubscription) {
return res.status(403).json({
success: false,
error: 'You need an active subscription to add products. Please subscribe first.',
redirect: '/subscription.html',
code: 'NO_SUBSCRIPTION'
});
}
// 2. التحقق من أن الخطة تدعم إنشاء منتجات
const plan = activeSubscription.planId;
if (!plan || !plan.features || !plan.features.storeEnabled) {
return res.status(403).json({
success: false,
error: 'Your current plan does not allow adding products. Please upgrade your plan.',
redirect: '/subscription.html',
code: 'STORE_NOT_IN_PLAN'
});
}
// 3. التحقق من حدود المنتجات حسب النوع
const currentProductsCount = await Product.countDocuments({
userId: req.user.userId,
isActive: true
});
const maxProducts = plan.features.maxProducts || 0;
if (maxProducts > 0 && currentProductsCount >= maxProducts) {
return res.status(403).json({
success: false,
error: `Your plan allows up to ${maxProducts} products. You currently have ${currentProductsCount}. Please upgrade to add more products.`,
redirect: '/subscription.html',
code: 'PRODUCT_LIMIT_REACHED',
currentLimit: maxProducts,
currentCount: currentProductsCount
});
}
// 4. التحقق من حدود المنتجات حسب النوع (Digital/Project/Service)
if (type === 'digital') {
const maxDigital = plan.features.maxDigitalProducts || 0;
const currentDigital = await Product.countDocuments({
userId: req.user.userId,
type: 'digital',
isActive: true
});
if (maxDigital > 0 && currentDigital >= maxDigital) {
return res.status(403).json({
error: `Your plan allows up to ${maxDigital} digital products. Please upgrade to add more.`,
redirect: '/subscription.html'
});
}
} else if (type === 'project') {
const maxProjects = plan.features.maxProjects || 0;
const currentProjects = await Product.countDocuments({
userId: req.user.userId,
type: 'project',
isActive: true
});
if (maxProjects > 0 && currentProjects >= maxProjects) {
return res.status(403).json({
error: `Your plan allows up to ${maxProjects} projects. Please upgrade to add more.`,
redirect: '/subscription.html'
});
}
} else if (type === 'service') {
const maxServices = plan.features.maxServices || 0;
const currentServices = await Product.countDocuments({
userId: req.user.userId,
type: 'service',
isActive: true
});
if (maxServices > 0 && currentServices >= maxServices) {
return res.status(403).json({
error: `Your plan allows up to ${maxServices} services. Please upgrade to add more.`,
redirect: '/subscription.html'
});
}
}
// 5. التحقق من صلاحية الاشتراك (لم تنتهي)
const now = new Date();
if (activeSubscription.endDate && new Date(activeSubscription.endDate) < now) {
activeSubscription.status = 'expired';
await activeSubscription.save();
return res.status(403).json({
error: 'Your subscription has expired. Please renew to add products.',
redirect: '/subscription.html',
code: 'SUBSCRIPTION_EXPIRED'
});
}
// ============================================
// ✅ استمر في إنشاء المنتج (الكود الموجود)
// ============================================
let fileUrl = null;
let fileSize = null;
if (req.file && type === 'digital') {
fileUrl = req.file.path;
fileSize = req.file.size;
}
const product = new Product({
userId: req.user.userId,
type,
title,
description,
price: parseFloat(price),
currency: currency || 'USD',
fileUrl,
fileSize,
deliveryTime: deliveryTime || null,
tags: tags ? (Array.isArray(tags) ? tags : JSON.parse(tags)) : [],
images: images ? (Array.isArray(images) ? images : JSON.parse(images)) : []
});
await product.save();
res.status(201).json({ success: true, product });
} catch (error) {
console.error('Error creating product:', error);
res.status(500).json({ error: 'Failed to create product' });
}
});
// PUT /api/store/products/:productId - تحديث منتج
app.put('/api/store/products/:productId', authenticateToken, async (req, res) => {
try {
const { title, description, price, isActive, deliveryTime, tags, images } = req.body;
// ============================================
// ✅ جلب المنتج الحالي أولاً للتحقق من النوع
// ============================================
const existingProduct = await Product.findOne({
_id: req.params.productId,
userId: req.user.userId
});
if (!existingProduct) {
return res.status(404).json({ error: 'Product not found' });
}
// ============================================
// ✅ التحقق من الاشتراك والحدود (نفس الـ POST)
// ============================================
// 1. التحقق من وجود اشتراك نشط
const activeSubscription = await UserSubscription.findOne({
userId: req.user.userId,
status: 'active'
}).populate('planId');
if (!activeSubscription) {
return res.status(403).json({
success: false,
error: 'You need an active subscription to manage products. Please subscribe first.',
redirect: '/subscription.html',
code: 'NO_SUBSCRIPTION'
});
}
// 2. التحقق من أن الخطة تدعم إنشاء منتجات
const plan = activeSubscription.planId;
if (!plan || !plan.features || !plan.features.storeEnabled) {
return res.status(403).json({
success: false,
error: 'Your current plan does not allow managing products. Please upgrade your plan.',
redirect: '/subscription.html',
code: 'STORE_NOT_IN_PLAN'
});
}
// 3. التحقق من حدود المنتجات الكلية (لحالة تغيير isActive من false إلى true)
const oldType = existingProduct.type;
const newType = req.body.type || existingProduct.type;
// إذا كان المنتج غير نشط وسوف يصبح نشطاً، تحقق من الحدود
if (!existingProduct.isActive && isActive === true) {
const currentActiveCount = await Product.countDocuments({
userId: req.user.userId,
isActive: true
});
const maxProducts = plan.features.maxProducts || 0;
if (maxProducts > 0 && currentActiveCount >= maxProducts) {
return res.status(403).json({
success: false,
error: `Your plan allows up to ${maxProducts} active products. You have reached the limit. Please upgrade to add more products.`,
redirect: '/subscription.html',
code: 'PRODUCT_LIMIT_REACHED',
currentLimit: maxProducts,
currentCount: currentActiveCount
});
}
}
// 4. إذا تغير نوع المنتج، تحقق من حدود النوع الجديد
if (oldType !== newType) {
if (newType === 'digital') {
const maxDigital = plan.features.maxDigitalProducts || 0;
const currentDigital = await Product.countDocuments({
userId: req.user.userId,
type: 'digital',
isActive: true
});
if (maxDigital > 0 && currentDigital >= maxDigital) {
return res.status(403).json({
error: `Your plan allows up to ${maxDigital} digital products. Please upgrade to add more.`,
redirect: '/subscription.html'
});
}
} else if (newType === 'project') {
const maxProjects = plan.features.maxProjects || 0;
const currentProjects = await Product.countDocuments({
userId: req.user.userId,
type: 'project',
isActive: true
});
if (maxProjects > 0 && currentProjects >= maxProjects) {
return res.status(403).json({
error: `Your plan allows up to ${maxProjects} projects. Please upgrade to add more.`,
redirect: '/subscription.html'
});
}
} else if (newType === 'service') {
const maxServices = plan.features.maxServices || 0;
const currentServices = await Product.countDocuments({
userId: req.user.userId,
type: 'service',
isActive: true
});
if (maxServices > 0 && currentServices >= maxServices) {
return res.status(403).json({
error: `Your plan allows up to ${maxServices} services. Please upgrade to add more.`,
redirect: '/subscription.html'
});
}
}
}
// 5. التحقق من صلاحية الاشتراك (لم تنتهي)
const now = new Date();
if (activeSubscription.endDate && new Date(activeSubscription.endDate) < now) {
activeSubscription.status = 'expired';
await activeSubscription.save();
return res.status(403).json({
error: 'Your subscription has expired. Please renew to manage products.',
redirect: '/subscription.html',
code: 'SUBSCRIPTION_EXPIRED'
});
}
// ============================================
// ✅ تحديث المنتج (الكود الموجود)
// ============================================
const product = await Product.findOneAndUpdate(
{ _id: req.params.productId, userId: req.user.userId },
{
title,
description,
price: parseFloat(price),
isActive,
deliveryTime,
tags: tags ? (Array.isArray(tags) ? tags : JSON.parse(tags)) : [],
images: images ? (Array.isArray(images) ? images : JSON.parse(images)) : [],
updatedAt: Date.now()
},
{ new: true }
);
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json({ success: true, product });
} catch (error) {
console.error('Error updating product:', error);
res.status(500).json({ error: 'Failed to update product' });
}
});
// DELETE /api/store/products/:productId - حذف منتج
app.delete('/api/store/products/:productId', authenticateToken, async (req, res) => {
try {
const product = await Product.findOneAndDelete({ _id: req.params.productId, userId: req.user.userId });
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json({ success: true });
} catch (error) {
console.error('Error deleting product:', error);
res.status(500).json({ error: 'Failed to delete product' });
}
});
// ========== 3. الطلبات ==========
// POST /api/store/checkout - إنشاء طلب جديد
app.post('/api/store/checkout', authenticateToken, async (req, res) => {
try {
const { items, subtotal, totalAmount, paymentMethod, paymentDetails, buyerNotes, sellerId } = req.body;
if (!items || items.length === 0) {
return res.status(400).json({ error: 'Cart is empty' });
}
// التحقق من أن المنتجات لا تزال متاحة
for (const item of items) {
const product = await Product.findById(item.productId);
if (!product || !product.isActive) {
return res.status(400).json({ error: `Product "${item.title}" is no longer available` });
}
}
// جلب بيانات المشتري والبائع
const buyer = await User.findById(req.user.userId);
const seller = await User.findById(sellerId);
const storeSettings = await StoreSettings.findOne({ userId: sellerId });
if (!buyer || !seller) {
return res.status(404).json({ error: 'User not found' });
}
const order = new Order({
buyerId: req.user.userId,
sellerId,
items,
subtotal,
totalAmount,
paymentMethod,
paymentDetails,
buyerNotes: buyerNotes || '',
status: 'pending'
});
await order.save();
// ============================================
// 📧 1. إرسال إيميل تأكيد للمشتري
// ============================================
const buyerEmailHtml = `
Thank you for your order, ${buyer.username}! 🎉
Your order has been received and is now being processed.
Order Details
Order Number: ${order.orderNumber}
Date: ${new Date().toLocaleString()}
Payment Method: ${paymentMethod === 'vodafone' ? 'Vodafone Cash' : paymentMethod === 'instapay' ? 'InstaPay' : paymentMethod === 'paypal' ? 'PayPal' : paymentMethod === 'stripe' ? 'Credit Card' : 'Bank Transfer'}
Items
${items.map(item => `
${item.title} x1
${item.price} ${storeSettings?.currencySymbol || '$'}
`).join('')}
Subtotal
${subtotal} ${storeSettings?.currencySymbol || '$'}
${totalAmount !== subtotal ? `
Discount
-${(subtotal - totalAmount).toFixed(2)} ${storeSettings?.currencySymbol || '$'}
` : ''}
Total Amount
${totalAmount} ${storeSettings?.currencySymbol || '$'}
💰 Payment Instructions
${paymentMethod === 'vodafone' ? `
Please send the amount to this Vodafone Cash number:
${paymentDetails?.phone || 'Waiting for seller to provide'}
After payment, the seller will process your order and you'll receive download links.
` : paymentMethod === 'instapay' ? `
Please send the amount to this InstaPay account:
${paymentDetails?.phone || 'Waiting for seller to provide'}
` : paymentMethod === 'bank' ? `
Please transfer the amount to this bank account:
Bank: ${paymentDetails?.bankName || 'N/A'}
Account Name: ${paymentDetails?.accountName || 'N/A'}
Account Number: ${paymentDetails?.accountNumber || 'N/A'}
` : `
You will be redirected to complete payment securely.
`}
📦 View Order Status
If you have any questions, please contact the seller directly.
`;
// إرسال إيميل للمشتري
await sendStoreEmailWithBrevo(
buyer.email,
`✅ Order Confirmation #${order.orderNumber}`,
buyerEmailHtml,
`Thank you for your order! Order #${order.orderNumber} has been confirmed. Total: ${totalAmount} ${storeSettings?.currencySymbol || '$'}`
);
// ============================================
// 📧 2. إرسال إيميل للبائع (إشعار بطلب جديد)
// ============================================
const sellerEmailHtml = `
Congratulations, ${seller.profile?.nickname || seller.username}! 🎉
You have received a new order from ${buyer.username}.
Order Details
Order Number: ${order.orderNumber}
Customer: ${buyer.username} (${buyer.email})
Date: ${new Date().toLocaleString()}
Payment Method: ${paymentMethod === 'vodafone' ? 'Vodafone Cash' : paymentMethod === 'instapay' ? 'InstaPay' : paymentMethod === 'paypal' ? 'PayPal' : paymentMethod === 'stripe' ? 'Credit Card' : 'Bank Transfer'}
Items Ordered
${items.map(item => `
${item.title}
Type: ${item.productType}
Price: ${item.price} ${storeSettings?.currencySymbol || '$'}
`).join('')}
Total Amount: ${totalAmount} ${storeSettings?.currencySymbol || '$'}
${buyerNotes ? `
📝 Customer Notes:
${buyerNotes}
` : ''}
💡 Next Steps
- Confirm payment receipt from the customer
- Update order status to "Processing"
- For digital products: Upload files or provide download links
- Mark order as "Completed" when done
`;
// إرسال إيميل للبائع
await sendStoreEmailWithBrevo(
seller.email,
`🛍️ New Order #${order.orderNumber} from ${buyer.username}`,
sellerEmailHtml,
`New order received! Order #${order.orderNumber} from ${buyer.username}. Total: ${totalAmount} ${storeSettings?.currencySymbol || '$'}`
);
// ============================================
// 📱 3. إرسال إشعار عبر Socket.IO (في الوقت الفعلي)
// ============================================
const io = req.app.get('io');
if (io) {
// إشعار للبائع
io.to(`user_${sellerId}`).emit('new_order_notification', {
orderId: order._id,
orderNumber: order.orderNumber,
buyerName: buyer.username,
totalAmount,
currency: storeSettings?.currencySymbol || '$',
itemsCount: items.length,
timestamp: new Date()
});
// إشعار للمشتري
io.to(`user_${req.user.userId}`).emit('order_created', {
orderId: order._id,
orderNumber: order.orderNumber,
status: 'pending',
timestamp: new Date()
});
}
// ============================================
// 📝 4. إنشاء إشعار في قاعدة البيانات
// ============================================
// إشعار للبائع
const sellerNotification = new Notification({
userId: sellerId,
type: 'new_order',
actorId: req.user.userId,
actorName: buyer.username,
content: `New order #${order.orderNumber} from ${buyer.username} - ${items.length} item(s) - Total: ${totalAmount} ${storeSettings?.currencySymbol || '$'}`,
targetId: `/store/orders/${order._id}`,
targetType: 'store',
read: false
});
await sellerNotification.save();
// إشعار للمشتري
const buyerNotification = new Notification({
userId: req.user.userId,
type: 'order_status_update',
actorId: sellerId,
actorName: seller.profile?.nickname || seller.username,
content: `Your order #${order.orderNumber} has been created and is pending confirmation`,
targetId: `/store/orders/${order._id}`,
targetType: 'store',
read: false
});
await buyerNotification.save();
// ============================================
// ✅ 5. تحديث سلة التسوق (حذف العناصر المشتراة)
// ============================================
await Cart.findOneAndUpdate(
{ userId: req.user.userId },
{ $set: { items: [] } } // تفريغ السلة
);
// ============================================
// 📊 6. تحديث إحصائيات المنتجات (زيادة عدد المشاهدات)
// ============================================
for (const item of items) {
await Product.findByIdAndUpdate(item.productId, {
$inc: { salesCount: 1 }
});
}
// ============================================
// 🎉 7. إرسال الرد
// ============================================
res.json({
success: true,
orderId: order._id,
orderNumber: order.orderNumber,
message: 'Order created successfully! Check your email for confirmation.'
});
} catch (error) {
console.error('Error creating order:', error);
res.status(500).json({ error: 'Failed to create order: ' + error.message });
}
});
// GET /api/store/orders/buyer - جلب طلبات المستخدم (كمشتري)
app.get('/api/store/orders/buyer', authenticateToken, async (req, res) => {
try {
const orders = await Order.find({ buyerId: req.user.userId })
.sort({ createdAt: -1 })
.populate('sellerId', 'username profile.nickname profile.avatar');
res.json({ success: true, orders });
} catch (error) {
console.error('Error fetching buyer orders:', error);
res.status(500).json({ error: 'Failed to fetch orders' });
}
});
// GET /api/store/orders/seller - جلب طلبات المستخدم (كبائع)
app.get('/api/store/orders/seller', authenticateToken, async (req, res) => {
try {
const orders = await Order.find({ sellerId: req.user.userId })
.sort({ createdAt: -1 })
.populate('buyerId', 'username profile.nickname profile.avatar');
res.json({ success: true, orders });
} catch (error) {
console.error('Error fetching seller orders:', error);
res.status(500).json({ error: 'Failed to fetch orders' });
}
});
// PUT /api/store/orders/:orderId/status - تحديث حالة الطلب
app.put('/api/store/orders/:orderId/status', authenticateToken, async (req, res) => {
try {
const { status, sellerNotes } = req.body;
const order = await Order.findOneAndUpdate(
{ _id: req.params.orderId, sellerId: req.user.userId },
{
status,
sellerNotes: sellerNotes || '',
...(status === 'completed' ? { deliveredAt: Date.now() } : {}),
updatedAt: Date.now()
},
{ new: true }
);
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
// إذا تم إكمال الطلب، أضف للإيرادات
if (status === 'completed') {
await Earnings.findOneAndUpdate(
{ userId: req.user.userId },
{
$inc: { totalSales: 1, totalEarnings: order.totalAmount, lifetimeEarnings: order.totalAmount },
$push: {
transactions: {
orderId: order._id,
amount: order.totalAmount,
type: 'sale',
status: 'completed',
createdAt: Date.now()
}
}
},
{ upsert: true }
);
// تحديث إحصائيات الشهر
const currentMonth = new Date().toISOString().slice(0, 7);
await Earnings.findOneAndUpdate(
{ userId: req.user.userId },
{
$inc: {
'monthlyStats.$[elem].sales': 1,
'monthlyStats.$[elem].earnings': order.totalAmount
}
},
{
arrayFilters: [{ 'elem.month': currentMonth }],
upsert: true
}
);
}
res.json({ success: true, order });
} catch (error) {
console.error('Error updating order status:', error);
res.status(500).json({ error: 'Failed to update order status' });
}
});
// ========== 4. الإيرادات ==========
// GET /api/store/earnings - جلب إحصائيات الإيرادات
app.get('/api/store/earnings', authenticateToken, async (req, res) => {
try {
let earnings = await Earnings.findOne({ userId: req.user.userId });
if (!earnings) {
earnings = { totalSales: 0, totalEarnings: 0, pendingPayout: 0, transactions: [] };
}
const recentOrders = await Order.find({ sellerId: req.user.userId })
.sort({ createdAt: -1 })
.limit(10)
.populate('buyerId', 'username profile.nickname');
res.json({ success: true, earnings, recentOrders });
} catch (error) {
console.error('Error fetching earnings:', error);
res.status(500).json({ error: 'Failed to fetch earnings' });
}
});
// ========== 5. عربة التسوق (Backup) ==========
// GET /api/store/cart - جلب عربة التسوق
app.get('/api/store/cart', authenticateToken, async (req, res) => {
try {
let cart = await Cart.findOne({ userId: req.user.userId }).populate('items.productId');
if (!cart) {
cart = { items: [] };
}
res.json({ success: true, cart });
} catch (error) {
console.error('Error fetching cart:', error);
res.status(500).json({ error: 'Failed to fetch cart' });
}
});
// POST /api/store/cart - حفظ عربة التسوق (Sync)
app.post('/api/store/cart', authenticateToken, async (req, res) => {
try {
const { items } = req.body;
const cart = await Cart.findOneAndUpdate(
{ userId: req.user.userId },
{ items, updatedAt: Date.now() },
{ upsert: true, new: true }
);
res.json({ success: true, cart });
} catch (error) {
console.error('Error saving cart:', error);
res.status(500).json({ error: 'Failed to save cart' });
}
});
// ============================================
// PRODUCT REVIEWS API
// ============================================
// POST /api/store/products/:productId/review - إضافة تقييم
app.post('/api/store/products/:productId/review', authenticateToken, async (req, res) => {
try {
const { rating, comment } = req.body;
const productId = req.params.productId;
// التحقق من أن المستخدم اشترى المنتج بالفعل
const hasPurchased = await Order.findOne({
buyerId: req.user.userId,
'items.productId': productId,
status: 'completed'
});
if (!hasPurchased) {
return res.status(403).json({ error: 'You can only review products you have purchased' });
}
// التحقق من عدم وجود تقييم سابق
const product = await Product.findById(productId);
const existingReview = product.reviews.find(r => r.userId.toString() === req.user.userId);
if (existingReview) {
return res.status(400).json({ error: 'You have already reviewed this product' });
}
// إضافة التقييم
product.reviews.push({
userId: req.user.userId,
rating: parseInt(rating),
comment,
createdAt: new Date()
});
// تحديث متوسط التقييم
const totalRatings = product.reviews.length;
const sumRatings = product.reviews.reduce((sum, r) => sum + r.rating, 0);
product.averageRating = sumRatings / totalRatings;
await product.save();
// إشعار لصاحب المنتج
await sendStoreNotification(
product.userId,
'product_review',
`${req.user.username} rated your product "${product.title}" ${rating} stars`,
`/shop/${req.user.username}/product/${productId}`,
req.user.userId
);
res.json({ success: true, product });
} catch (error) {
console.error('Error adding review:', error);
res.status(500).json({ error: 'Failed to add review' });
}
});
// GET /api/store/products/:productId/reviews - جلب تقييمات المنتج
app.get('/api/store/products/:productId/reviews', async (req, res) => {
try {
const product = await Product.findById(req.params.productId)
.select('reviews averageRating totalReviews')
.populate('reviews.userId', 'username profile.nickname profile.avatar');
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json({
success: true,
averageRating: product.averageRating,
totalReviews: product.reviews.length,
reviews: product.reviews.sort((a, b) => b.createdAt - a.createdAt)
});
} catch (error) {
console.error('Error fetching reviews:', error);
res.status(500).json({ error: 'Failed to fetch reviews' });
}
});
// ============================================
// STORE FOLLOW SYSTEM
// ============================================
// POST /api/store/:storeId/follow - متابعة متجر
app.post('/api/store/:storeId/follow', authenticateToken, async (req, res) => {
try {
const storeId = req.params.storeId;
// التحقق من وجود المتجر
const store = await User.findById(storeId);
if (!store || !store.profile.storeEnabled) {
return res.status(404).json({ error: 'Store not found' });
}
const existingFollow = await StoreFollow.findOne({
userId: req.user.userId,
storeId
});
let isFollowing;
if (existingFollow) {
await existingFollow.deleteOne();
isFollowing = false;
} else {
await StoreFollow.create({
userId: req.user.userId,
storeId
});
isFollowing = true;
// إشعار لصاحب المتجر
await sendStoreNotification(
storeId,
'store_follow',
`${req.user.username} started following your store`,
`/shop/${store.profile.nickname || store.username}`,
req.user.userId
);
}
// جلب عدد المتابعين
const followersCount = await StoreFollow.countDocuments({ storeId });
res.json({ success: true, isFollowing, followersCount });
} catch (error) {
console.error('Error toggling store follow:', error);
res.status(500).json({ error: 'Failed to toggle store follow' });
}
});
// GET /api/store/:storeId/followers - جلب متابعي المتجر
app.get('/api/store/:storeId/followers', async (req, res) => {
try {
const followers = await StoreFollow.find({ storeId: req.params.storeId })
.populate('userId', 'username profile.nickname profile.avatar')
.limit(20);
res.json({ success: true, followers });
} catch (error) {
console.error('Error fetching followers:', error);
res.status(500).json({ error: 'Failed to fetch followers' });
}
});
// POST /api/store/coupons - إنشاء كوبون
app.post('/api/store/coupons', authenticateToken, async (req, res) => {
try {
const { code, discountType, discountValue, minPurchase, maxDiscount, validUntil, usageLimit, applicableProducts } = req.body;
// التحقق من أن الكوبون غير موجود
const existingCoupon = await Coupon.findOne({ code: code.toUpperCase() });
if (existingCoupon) {
return res.status(400).json({ error: 'Coupon code already exists' });
}
const coupon = new Coupon({
storeId: req.user.userId,
code: code.toUpperCase(),
discountType,
discountValue,
minPurchase: minPurchase || 0,
maxDiscount,
validUntil: validUntil ? new Date(validUntil) : null,
usageLimit,
applicableProducts: applicableProducts || []
});
await coupon.save();
res.json({ success: true, coupon });
} catch (error) {
console.error('Error creating coupon:', error);
res.status(500).json({ error: 'Failed to create coupon' });
}
});
// POST /api/store/validate-coupon - التحقق من صحة الكوبون
app.post('/api/store/validate-coupon', authenticateToken, async (req, res) => {
try {
const { code, cartTotal, storeId } = req.body;
const coupon = await Coupon.findOne({
code: code.toUpperCase(),
storeId,
isActive: true
});
if (!coupon) {
return res.status(404).json({ error: 'Invalid coupon code' });
}
// التحقق من صلاحية التاريخ
const now = new Date();
if (coupon.validFrom && now < coupon.validFrom) {
return res.status(400).json({ error: 'Coupon not yet active' });
}
if (coupon.validUntil && now > coupon.validUntil) {
return res.status(400).json({ error: 'Coupon has expired' });
}
// التحقق من الحد الأقصى للاستخدام
if (coupon.usageLimit && coupon.usageCount >= coupon.usageLimit) {
return res.status(400).json({ error: 'Coupon usage limit reached' });
}
// التحقق من الحد الأدنى للشراء
if (cartTotal < coupon.minPurchase) {
return res.status(400).json({ error: `Minimum purchase of $${coupon.minPurchase} required` });
}
// حساب الخصم
let discountAmount = 0;
if (coupon.discountType === 'percentage') {
discountAmount = (cartTotal * coupon.discountValue) / 100;
if (coupon.maxDiscount && discountAmount > coupon.maxDiscount) {
discountAmount = coupon.maxDiscount;
}
} else {
discountAmount = Math.min(coupon.discountValue, cartTotal);
}
res.json({
success: true,
coupon,
discountAmount,
finalTotal: cartTotal - discountAmount
});
} catch (error) {
console.error('Error validating coupon:', error);
res.status(500).json({ error: 'Failed to validate coupon' });
}
});
// ============================================
// STORE ANALYTICS
// ============================================
// GET /api/store/analytics - إحصائيات المتجر
app.get('/api/store/analytics', authenticateToken, async (req, res) => {
try {
const storeId = req.user.userId;
// جلب إحصائيات الطلبات
const ordersStats = await Order.aggregate([
{ $match: { sellerId: storeId } },
{ $group: {
_id: null,
totalOrders: { $sum: 1 },
completedOrders: { $sum: { $cond: [{ $eq: ['$status', 'completed'] }, 1, 0] } },
totalRevenue: { $sum: { $cond: [{ $eq: ['$status', 'completed'] }, '$totalAmount', 0] } },
pendingOrders: { $sum: { $cond: [{ $eq: ['$status', 'pending'] }, 1, 0] } }
}}
]);
// جلب إحصائيات المنتجات
const productsStats = await Product.aggregate([
{ $match: { userId: storeId } },
{ $group: {
_id: '$type',
count: { $sum: 1 },
totalSales: { $sum: '$salesCount' }
}}
]);
// جلب إحصائيات الزوار (Google Analytics)
const visitors = await ProfileAnalytics.findOne({ userId: storeId });
// جلب المنتجات الأكثر مبيعاً
const topProducts = await Product.find({ userId: storeId, isActive: true })
.sort({ salesCount: -1 })
.limit(5)
.select('title salesCount price images');
// جلب الطلبات الشهرية (آخر 6 أشهر)
const monthlyOrders = await Order.aggregate([
{ $match: { sellerId: storeId, status: 'completed' } },
{ $group: {
_id: { $dateToString: { format: '%Y-%m', date: '$createdAt' } },
count: { $sum: 1 },
revenue: { $sum: '$totalAmount' }
}},
{ $sort: { _id: -1 } },
{ $limit: 6 }
]);
res.json({
success: true,
analytics: {
orders: ordersStats[0] || { totalOrders: 0, completedOrders: 0, totalRevenue: 0, pendingOrders: 0 },
products: productsStats,
visitors: visitors?.profileViews?.length || 0,
topProducts,
monthlyOrders: monthlyOrders.reverse()
}
});
} catch (error) {
console.error('Error fetching store analytics:', error);
res.status(500).json({ error: 'Failed to fetch analytics' });
}
});
// ============================================
// PAYMENT WEBHOOKS (للدفع الآمن)
// ============================================
// Webhook لـ PayPal
app.post('/api/webhooks/paypal', express.raw({ type: 'application/json' }), async (req, res) => {
try {
const event = req.body;
if (event.event_type === 'PAYMENT.CAPTURE.COMPLETED') {
const orderId = event.resource.custom_id;
const transactionId = event.resource.id;
// تحديث حالة الطلب
await Order.findByIdAndUpdate(orderId, {
status: 'paid',
paymentDetails: {
transactionId,
provider: 'paypal',
capturedAt: new Date()
}
});
// إرسال إشعار للمشتري
const order = await Order.findById(orderId);
await sendStoreNotification(
order.buyerId,
'order_status_update',
`Your order #${order.orderNumber} has been paid successfully`,
`/store/orders/${orderId}`
);
// إرسال إشعار للبائع
await sendStoreNotification(
order.sellerId,
'new_order',
`New order #${order.orderNumber} has been paid`,
`/store/orders/${orderId}`
);
}
res.json({ received: true });
} catch (error) {
console.error('PayPal webhook error:', error);
res.status(500).json({ error: 'Webhook processing failed' });
}
});
// GET /api/store/orders/:orderId - جلب طلب محدد
app.get('/api/store/orders/:orderId', authenticateToken, async (req, res) => {
try {
const order = await Order.findById(req.params.orderId)
.populate('buyerId', 'username email profile.nickname')
.populate('sellerId', 'username profile.nickname');
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
// التحقق من أن المستخدم هو المشتري أو البائع
if (order.buyerId._id.toString() !== req.user.userId &&
order.sellerId._id.toString() !== req.user.userId) {
return res.status(403).json({ error: 'Unauthorized' });
}
// جلب إعدادات المتجر للـ currency symbol
const storeSettings = await StoreSettings.findOne({ userId: order.sellerId });
res.json({
...order.toObject(),
currencySymbol: storeSettings?.currencySymbol || '$'
});
} catch (error) {
console.error('Error fetching order:', error);
res.status(500).json({ error: 'Failed to fetch order' });
}
});
// POST /api/store/orders/:orderId/download - تحميل ملفات المنتج (للمنتجات الرقمية)
app.post('/api/store/orders/:orderId/download/:productId', authenticateToken, async (req, res) => {
try {
const order = await Order.findById(req.params.orderId);
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
// التحقق من أن المستخدم هو المشتري والطلب مكتمل
if (order.buyerId.toString() !== req.user.userId || order.status !== 'completed') {
return res.status(403).json({ error: 'Unauthorized or order not completed' });
}
const product = order.items.find(i => i.productId.toString() === req.params.productId);
if (!product || !product.fileUrl) {
return res.status(404).json({ error: 'File not found' });
}
// تحديث عدد مرات التحميل
await Order.findByIdAndUpdate(req.params.orderId, {
$inc: { 'items.$[item].downloadCount': 1 }
}, {
arrayFilters: [{ 'item.productId': req.params.productId }]
});
// إعادة رابط التحميل (أو إعادة توجيه)
res.json({ downloadUrl: product.fileUrl });
} catch (error) {
console.error('Error downloading file:', error);
res.status(500).json({ error: 'Failed to download file' });
}
});
// Webhook لـ Stripe
app.post('/api/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['stripe-signature'];
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
try {
const event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
const orderId = session.metadata.orderId;
await Order.findByIdAndUpdate(orderId, {
status: 'paid',
paymentDetails: {
sessionId: session.id,
provider: 'stripe',
paymentIntent: session.payment_intent
}
});
// تحديث حالة الطلب وإرسال الإشعارات...
}
res.json({ received: true });
} catch (error) {
console.error('Stripe webhook error:', error);
res.status(400).send(`Webhook Error: ${error.message}`);
}
});
// GET /api/admin/stores - جلب جميع المتاجر المفعلة
app.get('/api/admin/stores', authenticateToken, async (req, res) => {
try {
// التحقق من صلاحيات الأدمن
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const stores = await StoreSettings.find({ enabled: true })
.populate('userId', 'username profile.nickname profile.avatar')
.sort({ createdAt: -1 });
res.json({ success: true, stores });
} catch (error) {
console.error('Error fetching stores:', error);
res.status(500).json({ error: 'Failed to fetch stores' });
}
});
// ============================================
// 💳 SUBSCRIPTION API ENDPOINTS
// ============================================
// ========== 1. إدارة خطط الاشتراك (Admin Only) ==========
// GET /api/admin/subscription/plans - جلب كل الخطط
app.get('/api/admin/subscription/plans', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const plans = await SubscriptionPlan.find().sort({ order: 1, price: 1 });
res.json({ success: true, plans });
} catch (error) {
console.error('Error fetching plans:', error);
res.status(500).json({ error: 'Failed to fetch plans' });
}
});
// POST /api/admin/subscription/plans - إنشاء خطة جديدة
app.post('/api/admin/subscription/plans', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const { name, nameAr, description, descriptionAr, price, currency, duration, features, hasFreeTrial, freeTrialDays, badge, icon, order } = req.body;
// التحقق من عدم وجود خطة بنفس الاسم
const existingPlan = await SubscriptionPlan.findOne({ name });
if (existingPlan) {
return res.status(400).json({ error: 'Plan name already exists' });
}
const plan = new SubscriptionPlan({
name,
nameAr: nameAr || '',
description: description || '',
descriptionAr: descriptionAr || '',
price: parseFloat(price),
currency: currency || 'USD',
currencySymbol: currency === 'USD' ? '$' : currency === 'EUR' ? '€' : currency === 'GBP' ? '£' : 'ج.م',
duration: duration || 'monthly',
durationDays: duration === 'monthly' ? 30 : duration === 'yearly' ? 365 : 0,
features: {
storeEnabled: features?.storeEnabled || false,
maxProducts: features?.maxProducts || 0,
maxDigitalProducts: features?.maxDigitalProducts || 0,
maxProjects: features?.maxProjects || 0,
maxServices: features?.maxServices || 0,
pageBuilderEnabled: features?.pageBuilderEnabled || false,
customDomain: features?.customDomain || false,
analyticsEnabled: features?.analyticsEnabled || false,
prioritySupport: features?.prioritySupport || false,
removeBranding: features?.removeBranding || false,
teamMembers: features?.teamMembers || 1,
apiAccess: features?.apiAccess || false,
customCss: features?.customCss || false,
advancedAnalytics: features?.advancedAnalytics || false,
exportData: features?.exportData || false
},
hasFreeTrial: hasFreeTrial || false,
freeTrialDays: freeTrialDays || 0,
badge: badge || null,
icon: icon || 'bx-star',
isActive: true,
order: order || 0
});
await plan.save();
res.status(201).json({ success: true, plan });
} catch (error) {
console.error('Error creating plan:', error);
res.status(500).json({ error: 'Failed to create plan' });
}
});
// PUT /api/admin/subscription/plans/:planId - تحديث خطة
app.put('/api/admin/subscription/plans/:planId', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const { name, nameAr, description, descriptionAr, price, currency, duration, features, hasFreeTrial, freeTrialDays, badge, icon, isActive, order } = req.body;
const plan = await SubscriptionPlan.findById(req.params.planId);
if (!plan) {
return res.status(404).json({ error: 'Plan not found' });
}
// تحديث الحقول
if (name) plan.name = name;
if (nameAr !== undefined) plan.nameAr = nameAr;
if (description !== undefined) plan.description = description;
if (descriptionAr !== undefined) plan.descriptionAr = descriptionAr;
if (price !== undefined) plan.price = parseFloat(price);
if (currency) plan.currency = currency;
if (duration) {
plan.duration = duration;
plan.durationDays = duration === 'monthly' ? 30 : duration === 'yearly' ? 365 : 0;
}
if (features) {
plan.features = { ...plan.features, ...features };
}
if (hasFreeTrial !== undefined) plan.hasFreeTrial = hasFreeTrial;
if (freeTrialDays !== undefined) plan.freeTrialDays = freeTrialDays;
if (badge !== undefined) plan.badge = badge;
if (icon) plan.icon = icon;
if (isActive !== undefined) plan.isActive = isActive;
if (order !== undefined) plan.order = order;
plan.updatedAt = Date.now();
await plan.save();
res.json({ success: true, plan });
} catch (error) {
console.error('Error updating plan:', error);
res.status(500).json({ error: 'Failed to update plan' });
}
});
// DELETE /api/admin/subscription/plans/:planId - حذف خطة
app.delete('/api/admin/subscription/plans/:planId', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
// التحقق من عدم وجود اشتراكات نشطة بهذه الخطة
const activeSubscriptions = await UserSubscription.countDocuments({
planId: req.params.planId,
status: { $in: ['active', 'pending'] }
});
if (activeSubscriptions > 0) {
return res.status(400).json({
error: `Cannot delete plan with ${activeSubscriptions} active subscriptions. Deactivate it instead.`
});
}
await SubscriptionPlan.findByIdAndDelete(req.params.planId);
res.json({ success: true });
} catch (error) {
console.error('Error deleting plan:', error);
res.status(500).json({ error: 'Failed to delete plan' });
}
});
// GET /api/subscription/plans - جلب الخطط النشطة للمستخدمين
app.get('/api/subscription/plans', async (req, res) => {
try {
const plans = await SubscriptionPlan.find({ isActive: true }).sort({ order: 1, price: 1 });
// إضافة معلومات عن اشتراك المستخدم الحالي إذا كان مسجلاً
let currentSubscription = null;
if (req.user && req.user.userId) {
currentSubscription = await UserSubscription.findOne({
userId: req.user.userId,
status: 'active'
}).populate('planId');
}
res.json({ success: true, plans, currentSubscription });
} catch (error) {
console.error('Error fetching active plans:', error);
res.status(500).json({ error: 'Failed to fetch plans' });
}
});
// ========== 2. إدارة طرق الدفع (Admin Only) ==========
// GET /api/admin/subscription/payment-methods - جلب طرق الدفع
app.get('/api/admin/subscription/payment-methods', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const methods = await PlatformPaymentMethod.find().sort({ order: 1 });
res.json({ success: true, methods });
} catch (error) {
console.error('Error fetching payment methods:', error);
res.status(500).json({ error: 'Failed to fetch payment methods' });
}
});
// POST /api/admin/subscription/payment-methods - إضافة طريقة دفع جديدة
app.post('/api/admin/subscription/payment-methods', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const { name, nameAr, type, icon, instructions, instructionsAr, bankDetails, mobileWalletDetails, onlineDetails, order } = req.body;
const method = new PlatformPaymentMethod({
name,
nameAr: nameAr || '',
type,
icon: icon || (type === 'bank' ? 'bx-bank' : type === 'mobile_wallet' ? 'bx-mobile-alt' : 'bx-credit-card'),
instructions: instructions || '',
instructionsAr: instructionsAr || '',
bankDetails: bankDetails || {},
mobileWalletDetails: mobileWalletDetails || {},
onlineDetails: onlineDetails || {},
isActive: true,
order: order || 0
});
await method.save();
res.status(201).json({ success: true, method });
} catch (error) {
console.error('Error creating payment method:', error);
res.status(500).json({ error: 'Failed to create payment method' });
}
});
// PUT /api/admin/subscription/payment-methods/:methodId - تحديث طريقة دفع
app.put('/api/admin/subscription/payment-methods/:methodId', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const method = await PlatformPaymentMethod.findById(req.params.methodId);
if (!method) {
return res.status(404).json({ error: 'Payment method not found' });
}
const { name, nameAr, instructions, instructionsAr, bankDetails, mobileWalletDetails, onlineDetails, isActive, order } = req.body;
if (name) method.name = name;
if (nameAr !== undefined) method.nameAr = nameAr;
if (instructions !== undefined) method.instructions = instructions;
if (instructionsAr !== undefined) method.instructionsAr = instructionsAr;
if (bankDetails) method.bankDetails = { ...method.bankDetails, ...bankDetails };
if (mobileWalletDetails) method.mobileWalletDetails = { ...method.mobileWalletDetails, ...mobileWalletDetails };
if (onlineDetails) method.onlineDetails = { ...method.onlineDetails, ...onlineDetails };
if (isActive !== undefined) method.isActive = isActive;
if (order !== undefined) method.order = order;
method.updatedAt = Date.now();
await method.save();
res.json({ success: true, method });
} catch (error) {
console.error('Error updating payment method:', error);
res.status(500).json({ error: 'Failed to update payment method' });
}
});
// DELETE /api/admin/subscription/payment-methods/:methodId - حذف طريقة دفع
app.delete('/api/admin/subscription/payment-methods/:methodId', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
await PlatformPaymentMethod.findByIdAndDelete(req.params.methodId);
res.json({ success: true });
} catch (error) {
console.error('Error deleting payment method:', error);
res.status(500).json({ error: 'Failed to delete payment method' });
}
});
// GET /api/subscription/payment-methods - جلب طرق الدفع النشطة للمستخدمين
app.get('/api/subscription/payment-methods', async (req, res) => {
try {
const methods = await PlatformPaymentMethod.find({ isActive: true }).sort({ order: 1 });
res.json({ success: true, methods });
} catch (error) {
console.error('Error fetching payment methods:', error);
res.status(500).json({ error: 'Failed to fetch payment methods' });
}
});
// ========== 3. إدارة اشتراكات المستخدمين ==========
// POST /api/subscription/subscribe - إنشاء اشتراك جديد
app.post('/api/subscription/subscribe', authenticateToken, upload.single('paymentProof'), async (req, res) => {
try {
const { planId, paymentMethodId, paymentDetails, autoRenew } = req.body;
// التحقق من وجود الخطة
const plan = await SubscriptionPlan.findById(planId);
if (!plan || !plan.isActive) {
return res.status(404).json({ error: 'Plan not found or inactive' });
}
// التحقق من وجود طريقة الدفع
const paymentMethod = await PlatformPaymentMethod.findById(paymentMethodId);
if (!paymentMethod || !paymentMethod.isActive) {
return res.status(404).json({ error: 'Payment method not found' });
}
// التحقق من عدم وجود اشتراك نشط للمستخدم
const existingSubscription = await UserSubscription.findOne({
userId: req.user.userId,
status: { $in: ['active', 'pending'] }
});
let isTrial = false;
let trialEndDate = null;
// التحقق من التجربة المجانية
if (plan.hasFreeTrial && !existingSubscription) {
isTrial = true;
trialEndDate = new Date();
trialEndDate.setDate(trialEndDate.getDate() + plan.freeTrialDays);
}
let paymentProofUrl = null;
let paymentProofPublicId = null;
// رفع إثبات الدفع إذا وجد
if (req.file) {
paymentProofUrl = req.file.path;
paymentProofPublicId = req.file.filename;
}
const startDate = isTrial ? new Date() : null;
const endDate = isTrial ? trialEndDate : null;
const subscription = new UserSubscription({
userId: req.user.userId,
planId,
status: isTrial ? 'active' : 'pending',
startDate,
endDate,
isTrial,
trialEndDate,
paymentMethod: paymentMethod.name,
paymentDetails: paymentDetails ? JSON.parse(paymentDetails) : {},
paymentProof: paymentProofUrl,
paymentProofPublicId,
amount: isTrial ? 0 : plan.price,
currency: plan.currency,
autoRenew: autoRenew === 'true' || autoRenew === true,
createdAt: new Date()
});
await subscription.save();
// إشعار للإدارة (إذا كان الاشتراك مدفوع)
if (!isTrial) {
const adminUsers = await User.find({ isAdmin: true });
for (const admin of adminUsers) {
const notification = new Notification({
userId: admin._id,
type: 'subscription_pending',
actorId: req.user.userId,
actorName: req.user.username,
content: `New subscription request from ${req.user.username} for plan ${plan.name}`,
targetId: `/admin/subscriptions/${subscription._id}`,
targetType: 'subscription',
read: false
});
await notification.save();
}
}
// إرسال إيميل تأكيد
const user = await User.findById(req.user.userId);
const emailHtml = `
Subscription Request Received
Dear ${user.username},
Your subscription request for plan "${plan.name}" has been received.
${isTrial ? `You are on a ${plan.freeTrialDays}-day free trial starting now.
` : 'Your request is pending admin approval. You will be notified once approved.
'}
Thank you for choosing MGZon!
`;
await sendStoreEmailWithBrevo(user.email, `Subscription Request - ${plan.name}`, emailHtml, '');
res.status(201).json({
success: true,
subscription,
isTrial,
message: isTrial ? 'Free trial started successfully!' : 'Subscription request submitted for approval'
});
} catch (error) {
console.error('Error creating subscription:', error);
res.status(500).json({ error: 'Failed to create subscription' });
}
});
// GET /api/subscription/my - جلب اشتراك المستخدم الحالي
app.get('/api/subscription/my', authenticateToken, async (req, res) => {
try {
const subscription = await UserSubscription.findOne({
userId: req.user.userId,
status: { $in: ['active', 'pending'] }
}).populate('planId').sort({ createdAt: -1 });
const history = await UserSubscription.find({
userId: req.user.userId
}).populate('planId').sort({ createdAt: -1 }).limit(10);
res.json({ success: true, subscription, history });
} catch (error) {
console.error('Error fetching user subscription:', error);
res.status(500).json({ error: 'Failed to fetch subscription' });
}
});
// ========== 4. إدارة الاشتراكات (Admin Only) ==========
// GET /api/admin/subscriptions - جلب كل الاشتراكات
app.get('/api/admin/subscriptions', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const { status, page = 1, limit = 20 } = req.query;
const query = {};
if (status && status !== 'all') query.status = status;
const subscriptions = await UserSubscription.find(query)
.populate('userId', 'username email profile.nickname profile.avatar')
.populate('planId')
.sort({ createdAt: -1 })
.skip((page - 1) * limit)
.limit(parseInt(limit));
const total = await UserSubscription.countDocuments(query);
res.json({
success: true,
subscriptions,
pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) }
});
} catch (error) {
console.error('Error fetching subscriptions:', error);
res.status(500).json({ error: 'Failed to fetch subscriptions' });
}
});
// PUT /api/admin/subscriptions/:subId/approve - الموافقة على اشتراك
app.put('/api/admin/subscriptions/:subId/approve', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const { adminNotes } = req.body;
const subscription = await UserSubscription.findById(req.params.subId).populate('planId');
if (!subscription) {
return res.status(404).json({ error: 'Subscription not found' });
}
const startDate = new Date();
const endDate = new Date();
endDate.setDate(endDate.getDate() + subscription.planId.durationDays);
subscription.status = 'active';
subscription.startDate = startDate;
subscription.endDate = endDate;
subscription.adminNotes = adminNotes || '';
subscription.approvedBy = req.user.userId;
subscription.approvedAt = new Date();
subscription.updatedAt = new Date();
await subscription.save();
// تسجيل الإيرادات
if (subscription.amount > 0) {
const earning = new SubscriptionEarnings({
subscriptionId: subscription._id,
userId: subscription.userId,
planId: subscription.planId._id,
amount: subscription.amount,
currency: subscription.currency,
status: 'completed',
paymentMethod: subscription.paymentMethod,
transactionId: subscription.transactionId,
payoutStatus: 'pending'
});
await earning.save();
}
// تحديث صلاحيات المستخدم بناءً على الخطة
await updateUserPermissions(subscription.userId, subscription.planId);
// إشعار المستخدم
const user = await User.findById(subscription.userId);
const notification = new Notification({
userId: subscription.userId,
type: 'subscription_approved',
actorId: req.user.userId,
actorName: admin.username,
content: `Your subscription to "${subscription.planId.name}" has been approved!`,
targetId: `/subscription`,
targetType: 'subscription',
read: false
});
await notification.save();
// إرسال إيميل
const emailHtml = `
Subscription Approved! 🎉
Dear ${user.username},
Your subscription to "${subscription.planId.name}" has been approved and is now active.
Start Date: ${startDate.toLocaleDateString()}
End Date: ${endDate.toLocaleDateString()}
You can now access all the features included in your plan.
Go to Dashboard
`;
await sendStoreEmailWithBrevo(user.email, `Subscription Approved - ${subscription.planId.name}`, emailHtml, '');
res.json({ success: true, subscription });
} catch (error) {
console.error('Error approving subscription:', error);
res.status(500).json({ error: 'Failed to approve subscription' });
}
});
// PUT /api/admin/subscriptions/:subId/reject - رفض اشتراك
app.put('/api/admin/subscriptions/:subId/reject', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const { rejectionReason } = req.body;
const subscription = await UserSubscription.findById(req.params.subId).populate('planId');
if (!subscription) {
return res.status(404).json({ error: 'Subscription not found' });
}
subscription.status = 'cancelled';
subscription.cancelledAt = new Date();
subscription.cancellationReason = rejectionReason || 'Rejected by admin';
subscription.adminNotes = rejectionReason || '';
subscription.updatedAt = new Date();
await subscription.save();
// إشعار المستخدم
const user = await User.findById(subscription.userId);
const notification = new Notification({
userId: subscription.userId,
type: 'subscription_rejected',
actorId: req.user.userId,
actorName: admin.username,
content: `Your subscription to "${subscription.planId.name}" was rejected. Reason: ${rejectionReason || 'Not specified'}`,
targetId: `/subscription`,
targetType: 'subscription',
read: false
});
await notification.save();
res.json({ success: true });
} catch (error) {
console.error('Error rejecting subscription:', error);
res.status(500).json({ error: 'Failed to reject subscription' });
}
});
// PUT /api/admin/subscriptions/:subId/cancel - إلغاء اشتراك نشط
app.put('/api/admin/subscriptions/:subId/cancel', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
const { reason } = req.body;
const subscription = await UserSubscription.findById(req.params.subId).populate('planId');
if (!subscription) {
return res.status(404).json({ error: 'Subscription not found' });
}
subscription.status = 'cancelled';
subscription.cancelledAt = new Date();
subscription.cancellationReason = reason || 'Cancelled by admin';
subscription.updatedAt = new Date();
await subscription.save();
// تحديث صلاحيات المستخدم (تعطيل)
await updateUserPermissions(subscription.userId, null);
res.json({ success: true });
} catch (error) {
console.error('Error cancelling subscription:', error);
res.status(500).json({ error: 'Failed to cancel subscription' });
}
});
// ========== 5. إحصائيات الاشتراكات (Admin Dashboard) ==========
// GET /api/admin/subscription/stats - إحصائيات الاشتراكات
app.get('/api/admin/subscription/stats', authenticateToken, async (req, res) => {
try {
const admin = await User.findById(req.user.userId);
if (!admin || !admin.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
// إحصائيات عامة
const totalSubscriptions = await UserSubscription.countDocuments();
const activeSubscriptions = await UserSubscription.countDocuments({ status: 'active' });
const pendingSubscriptions = await UserSubscription.countDocuments({ status: 'pending' });
const expiredSubscriptions = await UserSubscription.countDocuments({ status: 'expired' });
const cancelledSubscriptions = await UserSubscription.countDocuments({ status: 'cancelled' });
// إحصائيات الإيرادات
const earnings = await SubscriptionEarnings.aggregate([
{ $match: { status: 'completed' } },
{ $group: { _id: null, total: { $sum: '$amount' } } }
]);
const monthlyEarnings = await SubscriptionEarnings.aggregate([
{ $match: { status: 'completed' } },
{ $group: {
_id: { $dateToString: { format: '%Y-%m', date: '$createdAt' } },
total: { $sum: '$amount' },
count: { $sum: 1 }
}},
{ $sort: { _id: -1 } },
{ $limit: 12 }
]);
// أكثر الخطط مبيعاً
const topPlans = await UserSubscription.aggregate([
{ $match: { status: 'active' } },
{ $group: { _id: '$planId', count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 5 }
]);
// جلب تفاصيل الخطط
for (const plan of topPlans) {
const planDetails = await SubscriptionPlan.findById(plan._id);
if (planDetails) {
plan.name = planDetails.name;
plan.price = planDetails.price;
}
}
// الاشتراكات التي تنتظر المراجعة
const pendingList = await UserSubscription.find({ status: 'pending' })
.populate('userId', 'username email')
.populate('planId')
.limit(10);
res.json({
success: true,
stats: {
total: totalSubscriptions,
active: activeSubscriptions,
pending: pendingSubscriptions,
expired: expiredSubscriptions,
cancelled: cancelledSubscriptions,
totalRevenue: earnings[0]?.total || 0,
monthlyEarnings: monthlyEarnings.reverse()
},
topPlans,
pendingSubscriptionsList: pendingList
});
} catch (error) {
console.error('Error fetching subscription stats:', error);
res.status(500).json({ error: 'Failed to fetch stats' });
}
});
// ========== 6. دالة مساعدة لتحديث صلاحيات المستخدم ==========
async function updateUserPermissions(userId, plan) {
const user = await User.findById(userId);
if (!user) return;
if (!plan) {
user.profile.storeEnabled = false;
user.profile.maxProducts = 0;
user.profile.maxDigitalProducts = 0;
user.profile.maxProjects = 0;
user.profile.maxServices = 0;
user.profile.pageBuilderEnabled = false;
user.profile.customDomain = false;
user.profile.analyticsEnabled = false;
user.profile.prioritySupport = false;
user.profile.removeBranding = false;
user.profile.teamMembers = 1;
user.profile.apiAccess = false;
user.profile.customCss = false;
user.profile.advancedAnalytics = false;
user.profile.exportData = false;
} else {
user.profile.storeEnabled = plan.features?.storeEnabled || false;
user.profile.maxProducts = plan.features?.maxProducts || 0;
user.profile.maxDigitalProducts = plan.features?.maxDigitalProducts || 0;
user.profile.maxProjects = plan.features?.maxProjects || 0;
user.profile.maxServices = plan.features?.maxServices || 0;
user.profile.pageBuilderEnabled = plan.features?.pageBuilderEnabled || false;
user.profile.customDomain = plan.features?.customDomain || false;
user.profile.analyticsEnabled = plan.features?.analyticsEnabled || false;
user.profile.prioritySupport = plan.features?.prioritySupport || false;
user.profile.removeBranding = plan.features?.removeBranding || false;
user.profile.teamMembers = plan.features?.teamMembers || 1;
user.profile.apiAccess = plan.features?.apiAccess || false;
user.profile.customCss = plan.features?.customCss || false;
user.profile.advancedAnalytics = plan.features?.advancedAnalytics || false;
user.profile.exportData = plan.features?.exportData || false;
}
await user.save();
}
// ========== 7. سيرفر مهام لتحديث صلاحية الاشتراكات ==========
// يمكن تشغيل هذا الـ cron job يومياً
const checkExpiredSubscriptions = async () => {
const now = new Date();
// العثور على الاشتراكات المنتهية
const expired = await UserSubscription.find({
status: 'active',
endDate: { $lt: now }
});
for (const sub of expired) {
sub.status = 'expired';
sub.updatedAt = now;
await sub.save();
// تعطيل صلاحيات المستخدم
await updateUserPermissions(sub.userId, null);
// إشعار المستخدم
const user = await User.findById(sub.userId);
if (user) {
const notification = new Notification({
userId: sub.userId,
type: 'subscription_expired',
content: 'Your subscription has expired. Please renew to continue accessing premium features.',
targetId: `/subscription`,
targetType: 'subscription',
read: false
});
await notification.save();
}
}
console.log(`Expired ${expired.length} subscriptions`);
};
// تشغيل المهمة يومياً (يمكن إضافتها في مكان آخر)
setInterval(checkExpiredSubscriptions, 24 * 60 * 60 * 1000);
// ============================================
// CRON JOBS FOR SUBSCRIPTIONS
// ============================================
// تشغيل عند بدء السيرفر
async function initSubscriptionCronJobs() {
console.log('🔄 Initializing subscription cron jobs...');
await checkExpiredSubscriptions();
await sendExpirationReminders();
// تشغيل كل 6 ساعات
setInterval(async () => {
console.log('🔄 Running subscription maintenance...');
await checkExpiredSubscriptions();
await sendExpirationReminders();
await updateSubscriptionMetrics();
}, 6 * 60 * 60 * 1000);
}
// إرسال تذكيرات قبل انتهاء الاشتراك
async function sendExpirationReminders() {
const now = new Date();
const reminderDays = [7, 3, 1]; // قبل 7, 3, 1 يوم
for (const days of reminderDays) {
const targetDate = new Date();
targetDate.setDate(targetDate.getDate() + days);
const startOfDay = new Date(targetDate.setHours(0, 0, 0, 0));
const endOfDay = new Date(targetDate.setHours(23, 59, 59, 999));
const expiringSubscriptions = await UserSubscription.find({
status: 'active',
endDate: { $gte: startOfDay, $lte: endOfDay }
}).populate('planId').populate('userId');
for (const sub of expiringSubscriptions) {
// تجنب إرسال أكثر من تذكير لنفس المستخدم
const existingNotification = await Notification.findOne({
userId: sub.userId._id,
type: 'subscription_expiring',
createdAt: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
});
if (!existingNotification) {
const notification = new Notification({
userId: sub.userId._id,
type: 'subscription_expiring',
content: `Your subscription to "${sub.planId.name}" will expire in ${days} day${days !== 1 ? 's' : ''}. Renew now to continue enjoying premium features.`,
targetId: `/subscription`,
targetType: 'subscription',
read: false
});
await notification.save();
// إرسال إيميل
const user = sub.userId;
const emailHtml = `
Subscription Expiring Soon
Dear ${user.username},
Your subscription to "${sub.planId.name}" will expire in ${days} day${days !== 1 ? 's' : ''} (${new Date(sub.endDate).toLocaleDateString()}).
Renew now to continue enjoying premium features.
Renew Now →
`;
await sendStoreEmailWithBrevo(user.email, `Subscription Expiring Soon - ${sub.planId.name}`, emailHtml, '');
}
}
}
}
// تحديث إحصائيات الاشتراكات
async function updateSubscriptionMetrics() {
const today = new Date().toISOString().slice(0, 7);
// تسجيل إحصائيات يومية
const activeCount = await UserSubscription.countDocuments({ status: 'active' });
const totalRevenue = await SubscriptionEarnings.aggregate([
{ $match: { status: 'completed' } },
{ $group: { _id: null, total: { $sum: '$amount' } } }
]);
console.log(`📊 Subscription Metrics: Active: ${activeCount}, Revenue: ${totalRevenue[0]?.total || 0}`);
}
// تشغيل cron jobs عند بدء السيرفر
initSubscriptionCronJobs();
ف شوف بقا ايه الربط الكامل وايه اللي ناقص عشان الدايره تكتمل