diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..fab3f3a7
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+node_modules
+npm-debug.log
+.git
+.gitignore
+.env
+dist
+build
diff --git a/analytics-dashboard.js b/analytics-dashboard.js
deleted file mode 100644
index d05c24b1..00000000
--- a/analytics-dashboard.js
+++ /dev/null
@@ -1,1370 +0,0 @@
-// Analytics Dashboard Feature for ExpenseFlow
-var ANALYTICS_API_URL = '/api/analytics';
-
-// State management
-let analyticsData = {
- trends: null,
- categoryBreakdown: null,
- insights: null,
- predictions: null,
- velocity: null,
- forecast: null
-};
-
-const getAnalyticsLocale = () => (window.i18n?.getLocale?.() && window.i18n.getLocale()) || 'en-US';
-const getAnalyticsCurrency = () => (window.i18n?.getCurrency?.() && window.i18n.getCurrency()) || 'INR';
-
-function formatAnalyticsCurrency(value, options = {}) {
- const currency = options.currency || getAnalyticsCurrency();
- if (window.i18n?.formatCurrency) {
- return window.i18n.formatCurrency(value, {
- currency,
- locale: getAnalyticsLocale(),
- minimumFractionDigits: options.minimumFractionDigits ?? 0,
- maximumFractionDigits: options.maximumFractionDigits ?? 0
- });
- }
-
- const amount = Number(value || 0);
- return `${currency} ${amount.toLocaleString(undefined, { maximumFractionDigits: 2 })}`;
-}
-
-// ========================
-// API Functions
-// ========================
-
-async function getAuthHeaders() {
- // Accept either 'token' or legacy 'authToken' key used by auth integration
- const token = localStorage.getItem('token') || localStorage.getItem('authToken');
- const headers = { 'Content-Type': 'application/json' };
- if (token) headers['Authorization'] = `Bearer ${token}`;
- return headers;
-}
-
-/**
- * Fetch spending trends
- */
-async function fetchSpendingTrends(period = 'monthly', months = 6) {
- try {
- const token = localStorage.getItem('token');
- if (!token) return { data: [] };
-
- const response = await fetch(
- `${ANALYTICS_API_URL}/spending-trends?period=${period}&months=${months}`,
- { headers: await getAuthHeaders() }
- );
- if (!response.ok) throw new Error('Failed to fetch trends');
- const data = await response.json();
- analyticsData.trends = data.data;
- return data.data;
- } catch (error) {
- console.error('Error fetching spending trends:', error);
- throw error;
- }
-}
-
-/**
- * Fetch category breakdown
- */
-async function fetchCategoryBreakdown(type = 'expense', startDate = null, endDate = null) {
- try {
- let url = `${ANALYTICS_API_URL}/category-breakdown?type=${type}`;
- if (startDate) url += `&startDate=${startDate}`;
- if (endDate) url += `&endDate=${endDate}`;
-
- const response = await fetch(url, { headers: await getAuthHeaders() });
- if (!response.ok) throw new Error('Failed to fetch category breakdown');
- const data = await response.json();
- analyticsData.categoryBreakdown = data.data;
- return data.data;
- } catch (error) {
- console.error('Error fetching category breakdown:', error);
- throw error;
- }
-}
-
-/**
- * Fetch insights
- */
-async function fetchInsights() {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/insights`, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch insights');
- const data = await response.json();
- analyticsData.insights = data.data;
- return data.data;
- } catch (error) {
- console.error('Error fetching insights:', error);
- throw error;
- }
-}
-
-/**
- * Fetch predictions
- */
-async function fetchPredictions() {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/predictions`, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch predictions');
- const data = await response.json();
- analyticsData.predictions = data.data;
- return data.data;
- } catch (error) {
- console.error('Error fetching predictions:', error);
- throw error;
- }
-}
-
-/**
- * Fetch spending velocity
- */
-async function fetchSpendingVelocity() {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/velocity`, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch velocity');
- const data = await response.json();
- analyticsData.velocity = data.data;
- return data.data;
- } catch (error) {
- console.error('Error fetching velocity:', error);
- throw error;
- }
-}
-
-/**
- * Fetch financial forecast
- */
-async function fetchForecast() {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/forecast`, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch forecast');
- const data = await response.json();
- analyticsData.forecast = data.data;
- return data.data;
- } catch (error) {
- console.error('Error fetching forecast:', error);
- throw error;
- }
-}
-
-/**
- * Fetch month-over-month comparison
- */
-async function fetchComparison(months = 3) {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/comparison?months=${months}`, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch comparison');
- const data = await response.json();
- return data.data;
- } catch (error) {
- console.error('Error fetching comparison:', error);
- throw error;
- }
-}
-
-/**
- * Fetch complete analytics summary
- */
-async function fetchAnalyticsSummary() {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/summary`, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch summary');
- const data = await response.json();
- return data.data;
- } catch (error) {
- console.error('Error fetching summary:', error);
- throw error;
- }
-}
-
-// ========================
-// UI Rendering Functions
-// ========================
-
-/**
- * Render spending velocity widget
- */
-function renderVelocityWidget(velocity) {
- const container = document.getElementById('velocity-widget');
- if (!container) return;
-
- const progressPercent = Math.min(100, (velocity.dayOfMonth / 30) * 100);
-
- container.innerHTML = `
-
-
-
- ${formatAnalyticsCurrency(velocity.currentSpent)}
- Spent this month
-
-
- ${formatAnalyticsCurrency(velocity.dailyAverage)}
- Daily average
-
-
- ${formatAnalyticsCurrency(velocity.projectedMonthEnd)}
- Projected month end
-
-
-
-
-
${velocity.daysRemaining} days remaining
-
- `;
-}
-
-/**
- * Render category breakdown chart
- */
-function renderCategoryChart(breakdown) {
- const container = document.getElementById('category-chart');
- if (!container) return;
-
- if (!breakdown || breakdown.categories.length === 0) {
- container.innerHTML = 'No expense data available
';
- return;
- }
-
- // Clear previous chart
- container.innerHTML = '';
-
- const categoryColors = {
- food: '#FF6B6B',
- transport: '#4ECDC4',
- entertainment: '#96CEB4',
- utilities: '#FECA57',
- healthcare: '#FF9FF3',
- shopping: '#45B7D1',
- other: '#A55EEA'
- };
-
- const categoryIcons = {
- food: '🍽️',
- transport: '🚗',
- entertainment: '🎬',
- utilities: '💡',
- healthcare: '🏥',
- shopping: '🛒',
- other: '📋'
- };
-
- // Create chart header
- const header = document.createElement('div');
- header.className = 'category-chart-header';
- header.innerHTML = `
- Category Breakdown
- Total: ₹${breakdown.grandTotal.toLocaleString()}
- `;
- container.appendChild(header);
-
- // Create canvas for Chart.js
- const canvas = document.createElement('canvas');
- canvas.id = 'category-pie-chart';
- canvas.style.maxWidth = '100%';
- canvas.style.height = '300px';
- container.appendChild(canvas);
-
- // Prepare data for Chart.js
- const chartData = {
- labels: breakdown.categories.map(cat => `${categoryIcons[cat.category] || '📋'} ${capitalizeFirst(cat.category)}`),
- datasets: [{
- data: breakdown.categories.map(cat => cat.total),
- backgroundColor: breakdown.categories.map(cat => categoryColors[cat.category] || '#999'),
- borderColor: breakdown.categories.map(cat => categoryColors[cat.category] || '#999'),
- borderWidth: 2,
- hoverOffset: 10
- }]
- };
-
- // Create pie chart
- new Chart(canvas, {
- type: 'pie',
- data: chartData,
- options: {
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- legend: {
- position: 'bottom',
- labels: {
- padding: 20,
- usePointStyle: true,
- font: {
- size: 12,
- family: 'Inter, sans-serif'
- },
- color: '#ffffff'
- }
- },
- tooltip: {
- callbacks: {
- label: function(context) {
- const value = context.raw;
- const percentage = breakdown.categories[context.dataIndex].percentage;
- return `₹${value.toLocaleString()} (${percentage}%)`;
- }
- },
- backgroundColor: 'rgba(15, 15, 35, 0.9)',
- titleColor: '#64ffda',
- bodyColor: '#ffffff',
- borderColor: 'rgba(100, 255, 218, 0.3)',
- borderWidth: 1
- }
- }
- }
- });
-}
-
-/**
- * Render spending trends chart
- */
-function renderTrendsChart(trends) {
- const container = document.getElementById('trends-chart');
- if (!container) return;
-
- if (!trends || trends.data.length === 0) {
- container.innerHTML = 'Not enough data for trends
';
- return;
- }
-
- // Clear previous chart
- container.innerHTML = '';
-
- // Create chart header
- const header = document.createElement('div');
- header.className = 'trends-header';
- header.innerHTML = `
- Spending Trends
- `;
- container.appendChild(header);
-
- // Create canvas for Chart.js
- const canvas = document.createElement('canvas');
- canvas.id = 'trends-line-chart';
- canvas.style.maxWidth = '100%';
- canvas.style.height = '300px';
- container.appendChild(canvas);
-
- // Prepare data for Chart.js
- const chartData = {
- labels: trends.data.map(item => formatPeriodLabel(item.period)),
- datasets: [
- {
- label: 'Income',
- data: trends.data.map(item => item.income),
- borderColor: '#00e676',
- backgroundColor: 'rgba(0, 230, 118, 0.1)',
- borderWidth: 3,
- fill: false,
- tension: 0.4,
- pointBackgroundColor: '#00e676',
- pointBorderColor: '#ffffff',
- pointBorderWidth: 2,
- pointRadius: 6,
- pointHoverRadius: 8
- },
- {
- label: 'Expense',
- data: trends.data.map(item => item.expense),
- borderColor: '#ff5722',
- backgroundColor: 'rgba(255, 87, 34, 0.1)',
- borderWidth: 3,
- fill: false,
- tension: 0.4,
- pointBackgroundColor: '#ff5722',
- pointBorderColor: '#ffffff',
- pointBorderWidth: 2,
- pointRadius: 6,
- pointHoverRadius: 8
- }
- ]
- };
-
- // Create line chart
- new Chart(canvas, {
- type: 'line',
- data: chartData,
- options: {
- responsive: true,
- maintainAspectRatio: false,
- interaction: {
- mode: 'index',
- intersect: false
- },
- plugins: {
- legend: {
- position: 'top',
- labels: {
- padding: 20,
- usePointStyle: true,
- font: {
- size: 12,
- family: 'Inter, sans-serif'
- },
- color: '#ffffff'
- }
- },
- tooltip: {
- callbacks: {
- label: function(context) {
- return `${context.dataset.label}: ₹${context.raw.toLocaleString()}`;
- }
- },
- backgroundColor: 'rgba(15, 15, 35, 0.9)',
- titleColor: '#64ffda',
- bodyColor: '#ffffff',
- borderColor: 'rgba(100, 255, 218, 0.3)',
- borderWidth: 1
- }
- },
- scales: {
- x: {
- grid: {
- color: 'rgba(255, 255, 255, 0.1)'
- },
- ticks: {
- color: '#b4b4b4',
- font: {
- size: 11
- }
- }
- },
- y: {
- beginAtZero: true,
- grid: {
- color: 'rgba(255, 255, 255, 0.1)'
- },
- ticks: {
- color: '#b4b4b4',
- font: {
- size: 11
- },
- callback: function(value) {
- return '₹' + value.toLocaleString();
- }
- }
- }
- }
- }
- });
-
- // Add summary section
- if (trends.summary) {
- const summary = document.createElement('div');
- summary.className = 'trends-summary';
- summary.innerHTML = `
-
- Avg Monthly Expense
- ₹${trends.summary.avgMonthlyExpense.toLocaleString()}
-
-
- Savings Rate
- ${trends.summary.avgSavingsRate}%
-
-
- Trend
-
- ${trends.summary.spendingTrend === 'decreasing' ? '↓' : '↑'} ${capitalizeFirst(trends.summary.spendingTrend)}
-
-
- `;
- container.appendChild(summary);
- }
-}
-
-/**
- * Render insights cards
- */
-function renderInsights(insights) {
- const container = document.getElementById('insights-container');
- if (!container) return;
-
- if (!insights || insights.insights.length === 0) {
- container.innerHTML = 'No insights available yet
';
- return;
- }
-
- const insightIcons = {
- savings: 'piggy-bank',
- category: 'tags',
- trend: 'chart-line',
- anomaly: 'exclamation-triangle',
- info: 'info-circle'
- };
-
- const statusClasses = {
- good: 'success',
- moderate: 'warning',
- warning: 'warning',
- critical: 'danger'
- };
-
- container.innerHTML = `
-
-
- ${insights.insights.map(insight => `
-
-
-
-
-
-
${insight.title || capitalizeFirst(insight.type)}
-
${insight.message}
- ${insight.suggestion ? `
${insight.suggestion} ` : ''}
-
-
- `).join('')}
-
- `;
-}
-
-/**
- * Render predictions widget
- */
-function renderPredictions(predictions) {
- const container = document.getElementById('predictions-widget');
- if (!container) return;
-
- if (!predictions || !predictions.nextMonthPrediction) {
- container.innerHTML = 'Need more data for predictions
';
- return;
- }
-
- const trendIcon = predictions.trend === 'increasing' ? 'arrow-up' :
- predictions.trend === 'decreasing' ? 'arrow-down' : 'minus';
- const trendClass = predictions.trend === 'decreasing' ? 'positive' : 'negative';
-
- container.innerHTML = `
-
-
- Next Month Forecast
- ${formatAnalyticsCurrency(predictions.nextMonthPrediction)}
-
-
- ${capitalizeFirst(predictions.trend)}
-
-
-
-
- Historical Avg
- ${formatAnalyticsCurrency(predictions.historicalAverage)}
-
-
- Moving Avg
- ${formatAnalyticsCurrency(predictions.movingAverage)}
-
-
- Based on
- ${predictions.basedOnMonths} months
-
-
- `;
-}
-
-/**
- * Render forecast widget (Safe-to-Spend)
- */
-function renderForecastWidget(forecast) {
- const container = document.getElementById('forecast-widget');
- if (!container) return;
-
- const sts = forecast.safe_to_spend || forecast.safeToSpend;
-
- container.innerHTML = `
-
-
-
- Daily Limit
- ₹${sts.daily.toLocaleString()}
-
-
- Total Available
- ₹${sts.total.toLocaleString()}
-
-
-
-
- Recurring Bills
- ₹${sts.commitments.recurring.toLocaleString()}
-
-
- Goal Targets
- ₹${sts.commitments.goals.toLocaleString()}
-
-
-
- ${forecast.anomalies && forecast.anomalies.length > 0 ? `
-
-
- ${forecast.anomalies[0].message}
-
- ` : `
-
-
- No spending anomalies detected this week
-
- `}
-
- `;
-}
-
-// ========================
-// Helper Functions
-// ========================
-
-function capitalizeFirst(str) {
- if (!str) return '';
- return str.charAt(0).toUpperCase() + str.slice(1);
-}
-
-function formatPeriodLabel(period) {
- // Format: 2024-01 to Jan
- if (period.includes('-W')) {
- return `W${period.split('-W')[1]}`;
- }
- const parts = period.split('-');
- if (parts.length === 2) {
- const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
- return months[parseInt(parts[1]) - 1] || period;
- }
- return period;
-}
-
-function showAnalyticsNotification(message, type = 'info') {
- if (typeof showNotification === 'function') {
- showNotification(message, type);
- return;
- }
- console.log(`[${type}] ${message}`);
-}
-
-// ========================
-// Dashboard Loading
-// ========================
-
-async function loadAnalyticsDashboard() {
- const dashboardContainer = document.getElementById('analytics-dashboard');
- if (!dashboardContainer) return;
-
- const token = localStorage.getItem('token');
- if (!token) return;
-
- try {
- // Show loading state
- dashboardContainer.classList.add('loading');
-
- // Fetch all analytics data in parallel
- const [velocity, breakdown, trends, insights, predictions, forecast] = await Promise.all([
- fetchSpendingVelocity().catch(() => null),
- fetchCategoryBreakdown().catch(() => null),
- fetchSpendingTrends().catch(() => null),
- fetchInsights().catch(() => null),
- fetchPredictions().catch(() => null),
- fetchForecast().catch(() => null)
- ]);
-
- // Render all widgets
- if (velocity) renderVelocityWidget(velocity);
- if (breakdown) renderCategoryChart(breakdown);
- if (trends) renderTrendsChart(trends);
- if (insights) renderInsights(insights);
- if (predictions) renderPredictions(predictions);
- if (forecast) renderForecastWidget(forecast);
-
- dashboardContainer.classList.remove('loading');
- } catch (error) {
- console.error('Error loading analytics dashboard:', error);
- showAnalyticsNotification('Failed to load analytics', 'error');
- dashboardContainer.classList.remove('loading');
- }
-}
-
-// ========================
-// Initialization
-// ========================
-
-function initAnalyticsDashboard() {
- const dashboardContainer = document.getElementById('analytics-dashboard');
- if (!dashboardContainer) return;
-
- // Refresh button
- const refreshBtn = document.getElementById('refresh-analytics');
- if (refreshBtn) {
- refreshBtn.addEventListener('click', loadAnalyticsDashboard);
- }
-
- // Period selector for trends
- const periodSelect = document.getElementById('trends-period');
- if (periodSelect) {
- periodSelect.addEventListener('change', async (e) => {
- const trends = await fetchSpendingTrends(e.target.value);
- renderTrendsChart(trends);
- });
- }
-
- // Load initial data
- loadAnalyticsDashboard();
-}
-
-// Initialize when DOM is ready
-if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initAnalyticsDashboard);
-} else {
- initAnalyticsDashboard();
-}
-
-// ========================
-// Health Score & Gamification UI (Issue #421)
-// ========================
-
-let gamificationData = {
- healthScore: null,
- profile: null,
- badges: null,
- leaderboard: null
-};
-
-/**
- * Fetch complete health score data
- */
-async function fetchHealthScore() {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/gamification/health-score`, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch health score');
- const data = await response.json();
- gamificationData.healthScore = data.data;
- return data.data;
- } catch (error) {
- console.error('Error fetching health score:', error);
- throw error;
- }
-}
-
-/**
- * Fetch gamification profile
- */
-async function fetchGamificationProfile() {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/gamification/profile`, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch profile');
- const data = await response.json();
- gamificationData.profile = data.data;
- return data.data;
- } catch (error) {
- console.error('Error fetching gamification profile:', error);
- throw error;
- }
-}
-
-/**
- * Fetch all badges
- */
-async function fetchBadges() {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/gamification/badges`, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch badges');
- const data = await response.json();
- gamificationData.badges = data.data;
- return data.data;
- } catch (error) {
- console.error('Error fetching badges:', error);
- throw error;
- }
-}
-
-/**
- * Fetch leaderboard
- */
-async function fetchLeaderboard(type = 'health') {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/gamification/leaderboard?type=${type}&limit=10`, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch leaderboard');
- const data = await response.json();
- gamificationData.leaderboard = data.data;
- return data.data;
- } catch (error) {
- console.error('Error fetching leaderboard:', error);
- throw error;
- }
-}
-
-/**
- * Update financial profile
- */
-async function updateFinancialProfile(profileData) {
- try {
- const response = await fetch(`${ANALYTICS_API_URL}/gamification/financial-profile`, {
- method: 'PUT',
- headers: await getAuthHeaders(),
- body: JSON.stringify(profileData)
- });
- if (!response.ok) throw new Error('Failed to update profile');
- const data = await response.json();
- showAnalyticsNotification('Financial profile updated!', 'success');
- return data.data;
- } catch (error) {
- console.error('Error updating financial profile:', error);
- showAnalyticsNotification('Failed to update profile', 'error');
- throw error;
- }
-}
-
-/**
- * Render Health Score Dashboard
- */
-function renderHealthScoreDashboard(healthData) {
- const container = document.getElementById('health-score-dashboard');
- if (!container) return;
-
- const { score, grade, components, communityComparison, insights } = healthData;
-
- // Get score color based on grade
- const scoreColors = {
- 'A+': '#00e676', 'A': '#00c853', 'B+': '#4caf50', 'B': '#8bc34a',
- 'C+': '#ffc107', 'C': '#ff9800', 'D': '#ff5722', 'F': '#f44336'
- };
- const scoreColor = scoreColors[grade] || '#ffc107';
-
- container.innerHTML = `
-
-
-
-
-
-
-
-
-
- ${score}
- ${grade}
-
-
-
Financial Health Score
-
-
- ${communityComparison.rank} of users
-
-
-
-
-
-
Score Breakdown
- ${renderScoreComponent('Savings Rate', components.savingsRate, '💰', 20)}
- ${renderScoreComponent('Budget Discipline', components.budgetDiscipline, '📊', 25)}
- ${renderScoreComponent('Debt-to-Income', components.debtToIncome, '💳', 20)}
- ${renderScoreComponent('Emergency Fund', components.emergencyFund, '🛡️', 15)}
- ${renderScoreComponent('Investment', components.investmentConsistency, '📈', 20)}
-
-
-
-
-
-
-
-
-
Personalized Insights
-
- ${insights.strengths.length > 0 ? `
-
-
💪 Your Strengths
- ${insights.strengths.map(s => `
-
- ${s.icon}
- ${s.message}
-
- `).join('')}
-
- ` : ''}
-
- ${insights.improvements.length > 0 ? `
-
-
📈 Areas to Improve
- ${insights.improvements.map(i => `
-
-
${i.icon}
-
- ${i.message}
- ${i.priority}
-
-
- `).join('')}
-
- ` : ''}
-
-
-
-
-
Score History
-
-
- `;
-
- // Render history chart if data exists
- if (healthData.history && healthData.history.length > 0) {
- renderHealthHistoryChart(healthData.history);
- }
-}
-
-/**
- * Render individual score component bar
- */
-function renderScoreComponent(name, data, icon, weight) {
- const score = data.score;
- const barColor = score >= 70 ? '#00e676' : score >= 50 ? '#ffc107' : '#ff5722';
-
- return `
-
- `;
-}
-
-/**
- * Render health history chart
- */
-function renderHealthHistoryChart(history) {
- const canvas = document.getElementById('health-history-chart');
- if (!canvas || !history.length) return;
-
- const labels = history.map(h => {
- const d = new Date(h.date);
- return `${d.toLocaleString('default', { month: 'short' })}`;
- });
-
- new Chart(canvas, {
- type: 'line',
- data: {
- labels,
- datasets: [{
- label: 'Health Score',
- data: history.map(h => h.score),
- borderColor: '#64ffda',
- backgroundColor: 'rgba(100, 255, 218, 0.1)',
- borderWidth: 3,
- fill: true,
- tension: 0.4,
- pointBackgroundColor: '#64ffda',
- pointBorderColor: '#fff',
- pointRadius: 6
- }]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- legend: { display: false }
- },
- scales: {
- y: {
- min: 0,
- max: 100,
- grid: { color: 'rgba(255,255,255,0.1)' },
- ticks: { color: '#b4b4b4' }
- },
- x: {
- grid: { color: 'rgba(255,255,255,0.1)' },
- ticks: { color: '#b4b4b4' }
- }
- }
- }
- });
-}
-
-/**
- * Render Gamification Profile (Level, XP, Badges)
- */
-function renderGamificationProfile(profile) {
- const container = document.getElementById('gamification-profile');
- if (!container) return;
-
- const xpProgress = profile.xpToNextLevel > 0
- ? (profile.currentLevelXp / profile.xpToNextLevel) * 100
- : 0;
-
- container.innerHTML = `
-
- `;
-}
-
-/**
- * Render Badges Grid
- */
-function renderBadgesGrid(badges) {
- const container = document.getElementById('badges-grid');
- if (!container) return;
-
- // Separate earned and unearned
- const earned = badges.filter(b => b.earned);
- const unearned = badges.filter(b => !b.earned);
-
- const tierColors = {
- bronze: '#CD7F32',
- silver: '#C0C0C0',
- gold: '#FFD700',
- platinum: '#E5E4E2',
- diamond: '#B9F2FF'
- };
-
- container.innerHTML = `
-
-
Earned Badges (${earned.length})
-
- ${earned.map(badge => `
-
- ${badge.icon}
- ${badge.name}
- ${badge.tier}
- ${new Date(badge.earnedAt).toLocaleDateString()}
-
- `).join('')}
- ${earned.length === 0 ? '
Complete challenges to earn badges!
' : ''}
-
-
-
-
-
Locked Badges (${unearned.length})
-
- ${unearned.map(badge => `
-
- 🔒
- ${badge.name}
- ${badge.tier}
-
- `).join('')}
-
-
- `;
-}
-
-/**
- * Render Leaderboard
- */
-function renderLeaderboard(leaderboard) {
- const container = document.getElementById('leaderboard');
- if (!container) return;
-
- container.innerHTML = `
-
-
- ${leaderboard.map((user, idx) => `
-
- ${idx === 0 ? '🥇' : idx === 1 ? '🥈' : idx === 2 ? '🥉' : '#' + (idx + 1)}
- ${user.name}
- ${user.healthScore}
- ${user.healthGrade}
-
- `).join('')}
-
- `;
-}
-
-/**
- * Switch leaderboard type
- */
-async function switchLeaderboard(type) {
- const tabs = document.querySelectorAll('.lb-tab');
- tabs.forEach(t => t.classList.remove('active'));
- event.target.classList.add('active');
-
- const leaderboard = await fetchLeaderboard(type);
- renderLeaderboard(leaderboard);
-}
-
-/**
- * Render Financial Profile Form
- */
-function renderFinancialProfileForm(currentProfile = {}) {
- const container = document.getElementById('financial-profile-form');
- if (!container) return;
-
- container.innerHTML = `
- Your Financial Profile
- Update these values for accurate health score calculation
-
- `;
-
- // Handle form submission
- container.querySelector('#profile-update-form').addEventListener('submit', async (e) => {
- e.preventDefault();
- const formData = new FormData(e.target);
- const data = {};
- for (const [key, value] of formData.entries()) {
- if (value) data[key] = parseFloat(value);
- }
- await updateFinancialProfile(data);
- await loadHealthDashboard(); // Refresh scores
- });
-}
-
-/**
- * Load complete Health & Achievements dashboard
- */
-async function loadHealthDashboard() {
- const container = document.getElementById('health-achievements-section');
- if (!container) return;
-
- const token = localStorage.getItem('authToken');
- if (!token) return;
-
- try {
- container.classList.add('loading');
-
- // Fetch all gamification data in parallel
- const [healthScore, profile, badges, leaderboard] = await Promise.all([
- fetchHealthScore().catch(() => null),
- fetchGamificationProfile().catch(() => null),
- fetchBadges().catch(() => null),
- fetchLeaderboard().catch(() => null)
- ]);
-
- // Render all components
- if (healthScore) renderHealthScoreDashboard(healthScore);
- if (profile) renderGamificationProfile(profile);
- if (badges) renderBadgesGrid(badges);
- if (leaderboard) renderLeaderboard(leaderboard);
-
- // Render financial profile form with current values
- renderFinancialProfileForm(healthScore?.components?.debtToIncome?.details || {});
-
- container.classList.remove('loading');
- } catch (error) {
- console.error('Error loading health dashboard:', error);
- showAnalyticsNotification('Failed to load health dashboard', 'error');
- container.classList.remove('loading');
- }
-}
-
-/**
- * Generate social share preview
- */
-function generateSharePreview(healthData, profile) {
- return {
- title: `My Financial Health Score: ${healthData.score} (${healthData.grade})`,
- description: `I'm Level ${profile.level} (${profile.levelName}) with ${profile.badgeCount} badges! Check your financial health on ExpenseFlow.`,
- image: null, // Could generate a canvas image
- url: window.location.origin
- };
-}
-
-/**
- * Share health score
- */
-async function shareHealthScore() {
- if (!gamificationData.healthScore || !gamificationData.profile) {
- showAnalyticsNotification('Calculate your health score first!', 'warning');
- return;
- }
-
- const shareData = generateSharePreview(gamificationData.healthScore, gamificationData.profile);
-
- if (navigator.share) {
- try {
- await navigator.share({
- title: shareData.title,
- text: shareData.description,
- url: shareData.url
- });
- } catch (err) {
- console.log('Share cancelled');
- }
- } else {
- // Fallback: copy to clipboard
- const text = `${shareData.title}\n${shareData.description}\n${shareData.url}`;
- navigator.clipboard.writeText(text);
- showAnalyticsNotification('Copied to clipboard!', 'success');
- }
-}
-
-/**
- * Initialize Health & Achievements tab
- */
-function initHealthAchievements() {
- const healthTab = document.getElementById('health-tab');
- if (healthTab) {
- healthTab.addEventListener('click', (e) => {
- e.preventDefault();
- showHealthSection();
- loadHealthDashboard();
- });
- }
-
- // Share button
- const shareBtn = document.getElementById('share-health-btn');
- if (shareBtn) {
- shareBtn.addEventListener('click', shareHealthScore);
- }
-
- // Refresh button
- const refreshBtn = document.getElementById('refresh-health-btn');
- if (refreshBtn) {
- refreshBtn.addEventListener('click', loadHealthDashboard);
- }
-}
-
-/**
- * Show health section and hide others
- */
-function showHealthSection() {
- // Hide all main sections
- const sections = ['dashboard', 'analytics', 'goals', 'settings', 'health'];
- sections.forEach(id => {
- const section = document.getElementById(id);
- if (section) {
- section.style.display = id === 'health' ? 'block' : 'none';
- }
- });
-
- // Update active nav link
- document.querySelectorAll('.nav-link').forEach(link => {
- link.classList.remove('active');
- if (link.getAttribute('href') === '#health') {
- link.classList.add('active');
- }
- });
-}
-
-// Initialize health achievements when DOM is ready
-if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initHealthAchievements);
-} else {
- initHealthAchievements();
-}
-
-// Export for use in other modules
-if (typeof module !== 'undefined' && module.exports) {
- module.exports = {
- fetchSpendingTrends,
- fetchCategoryBreakdown,
- fetchInsights,
- fetchPredictions,
- fetchSpendingVelocity,
- fetchComparison,
- fetchAnalyticsSummary,
- loadAnalyticsDashboard,
- // Gamification exports
- fetchHealthScore,
- fetchGamificationProfile,
- fetchBadges,
- fetchLeaderboard,
- loadHealthDashboard,
- shareHealthScore
- };
-}
diff --git a/analytics.html b/analytics.html
deleted file mode 100644
index 31328d94..00000000
--- a/analytics.html
+++ /dev/null
@@ -1,1117 +0,0 @@
-
-
-
-
-
-
- Analytics - ExpenseFlow
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Time Period
-
- Last 7 Days
- Last 30 Days
- Last 3 Months
- Last Year
- Custom Range
-
-
-
- From
-
-
-
- To
-
-
-
- Category
-
- All Categories
- 🍽️ Food & Dining
- 🚗 Transportation
- 🛒 Shopping
- 💡 Bills & Utilities
- 🎬 Entertainment
- 🏥 Healthcare
- 📚 Education
- ✈️ Travel
- 📋 Other
-
-
-
Apply Filters
-
-
-
-
-
-
-
-
₹0
-
-
- 0% vs last period
-
-
-
-
-
-
₹0
-
-
- 5% vs last period
-
-
-
-
-
-
0
-
- 0 this period
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Top Spending Categories
-
-
-
-
-
-
-
-
-
-
-
Smart Insights
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/2fa-manage.css b/client/2fa-manage.css
similarity index 100%
rename from 2fa-manage.css
rename to client/2fa-manage.css
diff --git a/2fa-manage.html b/client/2fa-manage.html
similarity index 100%
rename from 2fa-manage.html
rename to client/2fa-manage.html
diff --git a/2fa-manage.js b/client/2fa-manage.js
similarity index 100%
rename from 2fa-manage.js
rename to client/2fa-manage.js
diff --git a/2fa-setup.css b/client/2fa-setup.css
similarity index 100%
rename from 2fa-setup.css
rename to client/2fa-setup.css
diff --git a/2fa-setup.html b/client/2fa-setup.html
similarity index 100%
rename from 2fa-setup.html
rename to client/2fa-setup.html
diff --git a/2fa-setup.js b/client/2fa-setup.js
similarity index 100%
rename from 2fa-setup.js
rename to client/2fa-setup.js
diff --git a/public/AboutUs.html b/client/AboutUs.html
similarity index 100%
rename from public/AboutUs.html
rename to client/AboutUs.html
diff --git a/public/Community.html b/client/Community.html
similarity index 100%
rename from public/Community.html
rename to client/Community.html
diff --git a/public/CurrencyConverter.html b/client/CurrencyConverter.html
similarity index 100%
rename from public/CurrencyConverter.html
rename to client/CurrencyConverter.html
diff --git a/client/Dockerfile b/client/Dockerfile
new file mode 100644
index 00000000..d2a291fd
--- /dev/null
+++ b/client/Dockerfile
@@ -0,0 +1,17 @@
+# Use Nginx as the base image
+FROM nginx:alpine
+
+# Remove default nginx static assets
+RUN rm -rf /usr/share/nginx/html/*
+
+# Copy custom nginx config
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+
+# Copy static assets to nginx html directory
+COPY . /usr/share/nginx/html/
+
+# Expose port 80
+EXPOSE 80
+
+# Start Nginx
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/public/ExpenseSplitcalc.css b/client/ExpenseSplitcalc.css
similarity index 100%
rename from public/ExpenseSplitcalc.css
rename to client/ExpenseSplitcalc.css
diff --git a/public/ExpenseSplitcalc.html b/client/ExpenseSplitcalc.html
similarity index 100%
rename from public/ExpenseSplitcalc.html
rename to client/ExpenseSplitcalc.html
diff --git a/public/ExpenseSplitcalc.js b/client/ExpenseSplitcalc.js
similarity index 100%
rename from public/ExpenseSplitcalc.js
rename to client/ExpenseSplitcalc.js
diff --git a/public/Help-Center.html b/client/Help-Center.html
similarity index 100%
rename from public/Help-Center.html
rename to client/Help-Center.html
diff --git a/public/Monthlysummary.css b/client/Monthlysummary.css
similarity index 100%
rename from public/Monthlysummary.css
rename to client/Monthlysummary.css
diff --git a/public/Monthlysummary.html b/client/Monthlysummary.html
similarity index 100%
rename from public/Monthlysummary.html
rename to client/Monthlysummary.html
diff --git a/public/Monthlysummary.js b/client/Monthlysummary.js
similarity index 100%
rename from public/Monthlysummary.js
rename to client/Monthlysummary.js
diff --git a/public/PrivacyPolicy.html b/client/PrivacyPolicy.html
similarity index 100%
rename from public/PrivacyPolicy.html
rename to client/PrivacyPolicy.html
diff --git a/public/REPORTS_ACCESSIBILITY.md b/client/REPORTS_ACCESSIBILITY.md
similarity index 100%
rename from public/REPORTS_ACCESSIBILITY.md
rename to client/REPORTS_ACCESSIBILITY.md
diff --git a/public/aboutus.css b/client/aboutus.css
similarity index 100%
rename from public/aboutus.css
rename to client/aboutus.css
diff --git a/public/accounts-dashboard.css b/client/accounts-dashboard.css
similarity index 100%
rename from public/accounts-dashboard.css
rename to client/accounts-dashboard.css
diff --git a/public/accounts-dashboard.js b/client/accounts-dashboard.js
similarity index 100%
rename from public/accounts-dashboard.js
rename to client/accounts-dashboard.js
diff --git a/public/accounts.html b/client/accounts.html
similarity index 100%
rename from public/accounts.html
rename to client/accounts.html
diff --git a/public/analytics-dashboard.js b/client/analytics-dashboard.js
similarity index 100%
rename from public/analytics-dashboard.js
rename to client/analytics-dashboard.js
diff --git a/analytics-local.js b/client/analytics-local.js
similarity index 100%
rename from analytics-local.js
rename to client/analytics-local.js
diff --git a/public/analytics.css b/client/analytics.css
similarity index 100%
rename from public/analytics.css
rename to client/analytics.css
diff --git a/public/analytics.html b/client/analytics.html
similarity index 100%
rename from public/analytics.html
rename to client/analytics.html
diff --git a/public/analytics.js b/client/analytics.js
similarity index 100%
rename from public/analytics.js
rename to client/analytics.js
diff --git a/api-integration.js b/client/api-integration.js
similarity index 100%
rename from api-integration.js
rename to client/api-integration.js
diff --git a/public/approval-dashboard.html b/client/approval-dashboard.html
similarity index 100%
rename from public/approval-dashboard.html
rename to client/approval-dashboard.html
diff --git a/public/asset-inventory.html b/client/asset-inventory.html
similarity index 100%
rename from public/asset-inventory.html
rename to client/asset-inventory.html
diff --git a/auth-integration.js b/client/auth-integration.js
similarity index 100%
rename from auth-integration.js
rename to client/auth-integration.js
diff --git a/public/auth.js b/client/auth.js
similarity index 100%
rename from public/auth.js
rename to client/auth.js
diff --git a/budget-goals.js b/client/budget-goals.js
similarity index 100%
rename from budget-goals.js
rename to client/budget-goals.js
diff --git a/public/budget-planner.html b/client/budget-planner.html
similarity index 100%
rename from public/budget-planner.html
rename to client/budget-planner.html
diff --git a/public/budget.css b/client/budget.css
similarity index 100%
rename from public/budget.css
rename to client/budget.css
diff --git a/public/budget.html b/client/budget.html
similarity index 100%
rename from public/budget.html
rename to client/budget.html
diff --git a/public/budget.js b/client/budget.js
similarity index 100%
rename from public/budget.js
rename to client/budget.js
diff --git a/public/challenges.html b/client/challenges.html
similarity index 100%
rename from public/challenges.html
rename to client/challenges.html
diff --git a/public/chat-assistant.js b/client/chat-assistant.js
similarity index 100%
rename from public/chat-assistant.js
rename to client/chat-assistant.js
diff --git a/public/community.css b/client/community.css
similarity index 100%
rename from public/community.css
rename to client/community.css
diff --git a/public/community.js b/client/community.js
similarity index 100%
rename from public/community.js
rename to client/community.js
diff --git a/public/compliance-center.html b/client/compliance-center.html
similarity index 100%
rename from public/compliance-center.html
rename to client/compliance-center.html
diff --git a/public/contact.css b/client/contact.css
similarity index 100%
rename from public/contact.css
rename to client/contact.css
diff --git a/public/contact.html b/client/contact.html
similarity index 100%
rename from public/contact.html
rename to client/contact.html
diff --git a/public/contact.js b/client/contact.js
similarity index 100%
rename from public/contact.js
rename to client/contact.js
diff --git a/public/contribute.css b/client/contribute.css
similarity index 100%
rename from public/contribute.css
rename to client/contribute.css
diff --git a/public/contribute.html b/client/contribute.html
similarity index 100%
rename from public/contribute.html
rename to client/contribute.html
diff --git a/public/contributor.html b/client/contributor.html
similarity index 100%
rename from public/contributor.html
rename to client/contributor.html
diff --git a/public/cookiepolicy.html b/client/cookiepolicy.html
similarity index 100%
rename from public/cookiepolicy.html
rename to client/cookiepolicy.html
diff --git a/public/css/folders.css b/client/css/folders.css
similarity index 100%
rename from public/css/folders.css
rename to client/css/folders.css
diff --git a/public/currency-settings.html b/client/currency-settings.html
similarity index 100%
rename from public/currency-settings.html
rename to client/currency-settings.html
diff --git a/public/currencyConverter.css b/client/currencyConverter.css
similarity index 100%
rename from public/currencyConverter.css
rename to client/currencyConverter.css
diff --git a/public/currencyConverter.js b/client/currencyConverter.js
similarity index 100%
rename from public/currencyConverter.js
rename to client/currencyConverter.js
diff --git a/public/dashboard-clean.html b/client/dashboard-clean.html
similarity index 100%
rename from public/dashboard-clean.html
rename to client/dashboard-clean.html
diff --git a/public/dashboard.css b/client/dashboard.css
similarity index 100%
rename from public/dashboard.css
rename to client/dashboard.css
diff --git a/public/dashboard.html b/client/dashboard.html
similarity index 100%
rename from public/dashboard.html
rename to client/dashboard.html
diff --git a/public/dashboard.js b/client/dashboard.js
similarity index 100%
rename from public/dashboard.js
rename to client/dashboard.js
diff --git a/public/db-manager.js b/client/db-manager.js
similarity index 100%
rename from public/db-manager.js
rename to client/db-manager.js
diff --git a/public/debt-dashboard.html b/client/debt-dashboard.html
similarity index 100%
rename from public/debt-dashboard.html
rename to client/debt-dashboard.html
diff --git a/debug-auth.html b/client/debug-auth.html
similarity index 100%
rename from debug-auth.html
rename to client/debug-auth.html
diff --git a/public/expensetracker.css b/client/expensetracker.css
similarity index 100%
rename from public/expensetracker.css
rename to client/expensetracker.css
diff --git a/export-feature.js b/client/export-feature.js
similarity index 100%
rename from export-feature.js
rename to client/export-feature.js
diff --git a/public/faq.css b/client/faq.css
similarity index 100%
rename from public/faq.css
rename to client/faq.css
diff --git a/public/faq.html b/client/faq.html
similarity index 100%
rename from public/faq.html
rename to client/faq.html
diff --git a/public/faq.js b/client/faq.js
similarity index 100%
rename from public/faq.js
rename to client/faq.js
diff --git a/public/feedback.css b/client/feedback.css
similarity index 100%
rename from public/feedback.css
rename to client/feedback.css
diff --git a/public/feedback.html b/client/feedback.html
similarity index 100%
rename from public/feedback.html
rename to client/feedback.html
diff --git a/public/feedback.js b/client/feedback.js
similarity index 100%
rename from public/feedback.js
rename to client/feedback.js
diff --git a/public/finance-tips.css b/client/finance-tips.css
similarity index 100%
rename from public/finance-tips.css
rename to client/finance-tips.css
diff --git a/public/finance-tips.html b/client/finance-tips.html
similarity index 100%
rename from public/finance-tips.html
rename to client/finance-tips.html
diff --git a/public/finance-tips.js b/client/finance-tips.js
similarity index 100%
rename from public/finance-tips.js
rename to client/finance-tips.js
diff --git a/public/forecasting.html b/client/forecasting.html
similarity index 100%
rename from public/forecasting.html
rename to client/forecasting.html
diff --git a/public/gamification.js b/client/gamification.js
similarity index 100%
rename from public/gamification.js
rename to client/gamification.js
diff --git a/public/goal-feature.js b/client/goal-feature.js
similarity index 100%
rename from public/goal-feature.js
rename to client/goal-feature.js
diff --git a/public/goals.css b/client/goals.css
similarity index 100%
rename from public/goals.css
rename to client/goals.css
diff --git a/public/goals.html b/client/goals.html
similarity index 100%
rename from public/goals.html
rename to client/goals.html
diff --git a/public/goals.js b/client/goals.js
similarity index 100%
rename from public/goals.js
rename to client/goals.js
diff --git a/groups.html b/client/groups.html
similarity index 100%
rename from groups.html
rename to client/groups.html
diff --git a/groups.js b/client/groups.js
similarity index 100%
rename from groups.js
rename to client/groups.js
diff --git a/public/helpcenter.css b/client/helpcenter.css
similarity index 100%
rename from public/helpcenter.css
rename to client/helpcenter.css
diff --git a/public/helpcenter.js b/client/helpcenter.js
similarity index 100%
rename from public/helpcenter.js
rename to client/helpcenter.js
diff --git a/public/i18n.js b/client/i18n.js
similarity index 100%
rename from public/i18n.js
rename to client/i18n.js
diff --git a/public/index.css b/client/index.css
similarity index 100%
rename from public/index.css
rename to client/index.css
diff --git a/public/index.css.bak b/client/index.css.bak
similarity index 100%
rename from public/index.css.bak
rename to client/index.css.bak
diff --git a/public/index.html b/client/index.html
similarity index 100%
rename from public/index.html
rename to client/index.html
diff --git a/public/index.js b/client/index.js
similarity index 100%
rename from public/index.js
rename to client/index.js
diff --git a/public/inventory-hub.html b/client/inventory-hub.html
similarity index 100%
rename from public/inventory-hub.html
rename to client/inventory-hub.html
diff --git a/public/investment-portfolio.js b/client/investment-portfolio.js
similarity index 100%
rename from public/investment-portfolio.js
rename to client/investment-portfolio.js
diff --git a/join-workspace.html b/client/join-workspace.html
similarity index 100%
rename from join-workspace.html
rename to client/join-workspace.html
diff --git a/public/js/approval-controller.js b/client/js/approval-controller.js
similarity index 100%
rename from public/js/approval-controller.js
rename to client/js/approval-controller.js
diff --git a/public/js/asset-controller.js b/client/js/asset-controller.js
similarity index 100%
rename from public/js/asset-controller.js
rename to client/js/asset-controller.js
diff --git a/public/js/budget-controller.js b/client/js/budget-controller.js
similarity index 100%
rename from public/js/budget-controller.js
rename to client/js/budget-controller.js
diff --git a/public/js/compliance-controller.js b/client/js/compliance-controller.js
similarity index 100%
rename from public/js/compliance-controller.js
rename to client/js/compliance-controller.js
diff --git a/public/js/currency-intelligence.js b/client/js/currency-intelligence.js
similarity index 100%
rename from public/js/currency-intelligence.js
rename to client/js/currency-intelligence.js
diff --git a/public/js/debt-logic.js b/client/js/debt-logic.js
similarity index 100%
rename from public/js/debt-logic.js
rename to client/js/debt-logic.js
diff --git a/public/js/folders.js b/client/js/folders.js
similarity index 100%
rename from public/js/folders.js
rename to client/js/folders.js
diff --git a/public/js/forecast-ctrl.js b/client/js/forecast-ctrl.js
similarity index 100%
rename from public/js/forecast-ctrl.js
rename to client/js/forecast-ctrl.js
diff --git a/public/js/inventory-controller.js b/client/js/inventory-controller.js
similarity index 100%
rename from public/js/inventory-controller.js
rename to client/js/inventory-controller.js
diff --git a/public/js/payroll-controller.js b/client/js/payroll-controller.js
similarity index 100%
rename from public/js/payroll-controller.js
rename to client/js/payroll-controller.js
diff --git a/public/js/project-billing-controller.js b/client/js/project-billing-controller.js
similarity index 100%
rename from public/js/project-billing-controller.js
rename to client/js/project-billing-controller.js
diff --git a/public/js/tag-controller.js b/client/js/tag-controller.js
similarity index 100%
rename from public/js/tag-controller.js
rename to client/js/tag-controller.js
diff --git a/public/js/team-controller.js b/client/js/team-controller.js
similarity index 100%
rename from public/js/team-controller.js
rename to client/js/team-controller.js
diff --git a/public/js/treasury-controller.js b/client/js/treasury-controller.js
similarity index 100%
rename from public/js/treasury-controller.js
rename to client/js/treasury-controller.js
diff --git a/public/loanCalculator.css b/client/loanCalculator.css
similarity index 100%
rename from public/loanCalculator.css
rename to client/loanCalculator.css
diff --git a/public/loanCalculator.html b/client/loanCalculator.html
similarity index 100%
rename from public/loanCalculator.html
rename to client/loanCalculator.html
diff --git a/public/loanCalculator.js b/client/loanCalculator.js
similarity index 100%
rename from public/loanCalculator.js
rename to client/loanCalculator.js
diff --git a/login-signup.html b/client/login-signup.html
similarity index 96%
rename from login-signup.html
rename to client/login-signup.html
index c9cb55de..98700d3b 100644
--- a/login-signup.html
+++ b/client/login-signup.html
@@ -1,305 +1,305 @@
-
-
-
-
-
- Login / Signup – All in One
-
-
-
-
-
-
-
-
Login
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Login
-
-
- Don't have an account? Sign Up
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ Login / Signup – All in One
+
+
+
+
+
+
+
+
Login
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Login
+
+
+ Don't have an account? Sign Up
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/login.css b/client/login.css
similarity index 100%
rename from public/login.css
rename to client/login.css
diff --git a/public/login.html b/client/login.html
similarity index 100%
rename from public/login.html
rename to client/login.html
diff --git a/public/login.js b/client/login.js
similarity index 100%
rename from public/login.js
rename to client/login.js
diff --git a/public/manifest.json b/client/manifest.json
similarity index 100%
rename from public/manifest.json
rename to client/manifest.json
diff --git a/modal-test.html b/client/modal-test.html
similarity index 100%
rename from modal-test.html
rename to client/modal-test.html
diff --git a/client/nginx.conf b/client/nginx.conf
new file mode 100644
index 00000000..07594845
--- /dev/null
+++ b/client/nginx.conf
@@ -0,0 +1,29 @@
+server {
+ listen 80;
+
+ # Serve static files
+ location / {
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ try_files $uri $uri/ =404;
+ }
+
+ # Proxy API requests to backend
+ location /api {
+ proxy_pass http://server:5000;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_cache_bypass $http_upgrade;
+ }
+
+ # Proxy Socket.IO requests
+ location /socket.io/ {
+ proxy_pass http://server:5000;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_set_header Host $host;
+ }
+}
diff --git a/notification-center.js b/client/notification-center.js
similarity index 100%
rename from notification-center.js
rename to client/notification-center.js
diff --git a/public/payroll-management.html b/client/payroll-management.html
similarity index 100%
rename from public/payroll-management.html
rename to client/payroll-management.html
diff --git a/public/performance-min.css b/client/performance-min.css
similarity index 100%
rename from public/performance-min.css
rename to client/performance-min.css
diff --git a/public/performance.html b/client/performance.html
similarity index 100%
rename from public/performance.html
rename to client/performance.html
diff --git a/public/performance.js b/client/performance.js
similarity index 100%
rename from public/performance.js
rename to client/performance.js
diff --git a/public/profile.css b/client/profile.css
similarity index 100%
rename from public/profile.css
rename to client/profile.css
diff --git a/public/profile.html b/client/profile.html
similarity index 100%
rename from public/profile.html
rename to client/profile.html
diff --git a/public/profile.js b/client/profile.js
similarity index 100%
rename from public/profile.js
rename to client/profile.js
diff --git a/public/project-billing.html b/client/project-billing.html
similarity index 100%
rename from public/project-billing.html
rename to client/project-billing.html
diff --git a/public/protect.js b/client/protect.js
similarity index 100%
rename from public/protect.js
rename to client/protect.js
diff --git a/public/pwa-features.html b/client/pwa-features.html
similarity index 100%
rename from public/pwa-features.html
rename to client/pwa-features.html
diff --git a/public/pwa-features.js b/client/pwa-features.js
similarity index 100%
rename from public/pwa-features.js
rename to client/pwa-features.js
diff --git a/public/pwa-min.css b/client/pwa-min.css
similarity index 100%
rename from public/pwa-min.css
rename to client/pwa-min.css
diff --git a/realtime-sync.js b/client/realtime-sync.js
similarity index 100%
rename from realtime-sync.js
rename to client/realtime-sync.js
diff --git a/public/receipt-ocr.js b/client/receipt-ocr.js
similarity index 100%
rename from public/receipt-ocr.js
rename to client/receipt-ocr.js
diff --git a/receipt-upload.js b/client/receipt-upload.js
similarity index 100%
rename from receipt-upload.js
rename to client/receipt-upload.js
diff --git a/recurring-expenses.js b/client/recurring-expenses.js
similarity index 100%
rename from recurring-expenses.js
rename to client/recurring-expenses.js
diff --git a/public/report-generator.js b/client/report-generator.js
similarity index 100%
rename from public/report-generator.js
rename to client/report-generator.js
diff --git a/public/reports-critical.js b/client/reports-critical.js
similarity index 100%
rename from public/reports-critical.js
rename to client/reports-critical.js
diff --git a/public/reports-min.css b/client/reports-min.css
similarity index 100%
rename from public/reports-min.css
rename to client/reports-min.css
diff --git a/public/reports-optimized.css b/client/reports-optimized.css
similarity index 100%
rename from public/reports-optimized.css
rename to client/reports-optimized.css
diff --git a/public/reports-performance.html b/client/reports-performance.html
similarity index 100%
rename from public/reports-performance.html
rename to client/reports-performance.html
diff --git a/public/reports-simple.html b/client/reports-simple.html
similarity index 100%
rename from public/reports-simple.html
rename to client/reports-simple.html
diff --git a/public/reports-sw.js b/client/reports-sw.js
similarity index 100%
rename from public/reports-sw.js
rename to client/reports-sw.js
diff --git a/public/reports.css b/client/reports.css
similarity index 100%
rename from public/reports.css
rename to client/reports.css
diff --git a/public/reports.html b/client/reports.html
similarity index 100%
rename from public/reports.html
rename to client/reports.html
diff --git a/public/reports.js b/client/reports.js
similarity index 100%
rename from public/reports.js
rename to client/reports.js
diff --git a/public/schemes.html b/client/schemes.html
similarity index 100%
rename from public/schemes.html
rename to client/schemes.html
diff --git a/public/security-dashboard.html b/client/security-dashboard.html
similarity index 100%
rename from public/security-dashboard.html
rename to client/security-dashboard.html
diff --git a/public/security-dashboard.js b/client/security-dashboard.js
similarity index 100%
rename from public/security-dashboard.js
rename to client/security-dashboard.js
diff --git a/public/settings.css b/client/settings.css
similarity index 100%
rename from public/settings.css
rename to client/settings.css
diff --git a/public/settings.html b/client/settings.html
similarity index 100%
rename from public/settings.html
rename to client/settings.html
diff --git a/public/settings.js b/client/settings.js
similarity index 100%
rename from public/settings.js
rename to client/settings.js
diff --git a/public/signup.html b/client/signup.html
similarity index 100%
rename from public/signup.html
rename to client/signup.html
diff --git a/public/smart-triggers.js b/client/smart-triggers.js
similarity index 100%
rename from public/smart-triggers.js
rename to client/smart-triggers.js
diff --git a/public/subscription-manager.js b/client/subscription-manager.js
similarity index 100%
rename from public/subscription-manager.js
rename to client/subscription-manager.js
diff --git a/public/sw-notifications.js b/client/sw-notifications.js
similarity index 100%
rename from public/sw-notifications.js
rename to client/sw-notifications.js
diff --git a/public/sw.js b/client/sw.js
similarity index 100%
rename from public/sw.js
rename to client/sw.js
diff --git a/public/tag-manager.html b/client/tag-manager.html
similarity index 100%
rename from public/tag-manager.html
rename to client/tag-manager.html
diff --git a/public/tax-reports.html b/client/tax-reports.html
similarity index 100%
rename from public/tax-reports.html
rename to client/tax-reports.html
diff --git a/public/tax-reports.js b/client/tax-reports.js
similarity index 100%
rename from public/tax-reports.js
rename to client/tax-reports.js
diff --git a/public/team-settings.html b/client/team-settings.html
similarity index 100%
rename from public/team-settings.html
rename to client/team-settings.html
diff --git a/public/terms_service.html b/client/terms_service.html
similarity index 100%
rename from public/terms_service.html
rename to client/terms_service.html
diff --git a/test-analytics.html b/client/test-analytics.html
similarity index 100%
rename from test-analytics.html
rename to client/test-analytics.html
diff --git a/test-login.html b/client/test-login.html
similarity index 100%
rename from test-login.html
rename to client/test-login.html
diff --git a/test-modal.html b/client/test-modal.html
similarity index 100%
rename from test-modal.html
rename to client/test-modal.html
diff --git a/theme.js b/client/theme.js
similarity index 100%
rename from theme.js
rename to client/theme.js
diff --git a/public/trackerscript.js b/client/trackerscript.js
similarity index 100%
rename from public/trackerscript.js
rename to client/trackerscript.js
diff --git a/public/transactions.css b/client/transactions.css
similarity index 100%
rename from public/transactions.css
rename to client/transactions.css
diff --git a/public/transactions.html b/client/transactions.html
similarity index 100%
rename from public/transactions.html
rename to client/transactions.html
diff --git a/public/transactions.js b/client/transactions.js
similarity index 100%
rename from public/transactions.js
rename to client/transactions.js
diff --git a/public/treasury-dashboard.html b/client/treasury-dashboard.html
similarity index 100%
rename from public/treasury-dashboard.html
rename to client/treasury-dashboard.html
diff --git a/working-modal-demo.html b/client/working-modal-demo.html
similarity index 100%
rename from working-modal-demo.html
rename to client/working-modal-demo.html
diff --git a/public/workspace-feature.js b/client/workspace-feature.js
similarity index 100%
rename from public/workspace-feature.js
rename to client/workspace-feature.js
diff --git a/docker-compose.yml b/docker-compose.yml
index 7ea6444e..bd1e0c12 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,9 +1,48 @@
-version: "3.9"
+version: '3.8'
services:
- expenseflow:
- build: .
- image: expenseflow:latest
+ client:
+ build: ./client
ports:
- - "8080:80"
- restart: unless-stopped
+ - "3000:80"
+ depends_on:
+ - server
+ networks:
+ - mern-network
+
+ server:
+ build: ./server
+ ports:
+ - "5000:5000"
+ environment:
+ - PORT=5000
+ - MONGODB_URI=mongodb://db:27017/expenseflow
+ - FRONTEND_URL=http://localhost:3000
+ # Use the .env file for other variables, but override DB URI for docker network
+ env_file:
+ - ./server/.env.example # Fallback if .env doesn't exist, user should copy .env.example to .env
+ depends_on:
+ - db
+ volumes:
+ - server-data:/app/data
+ - server-backups:/app/backups
+ networks:
+ - mern-network
+
+ db:
+ image: mongo:latest
+ ports:
+ - "27017:27017" # Optional: Expose 27017 to host if you want to connect with Compass
+ volumes:
+ - mongo-data:/data/db
+ networks:
+ - mern-network
+
+networks:
+ mern-network:
+ driver: bridge
+
+volumes:
+ mongo-data:
+ server-data:
+ server-backups:
diff --git a/expensetracker.css b/expensetracker.css
deleted file mode 100644
index 414944f8..00000000
--- a/expensetracker.css
+++ /dev/null
@@ -1,4389 +0,0 @@
- :root {
- /* Color Palette */
- --primary-gradient: linear-gradient(135deg, #309b81 0%, #31319a 100%);
- --secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
- --success-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- --warning-gradient: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
- --dark-gradient: linear-gradient(135deg, #232526 0%, #414345 100%);
-
- /* Background Colors */
- --bg-primary: #0f0f23;
- --bg-secondary: #1a1a2e;
- --bg-tertiary: #16213e;
- --bg-glass: rgba(255, 255, 255, 0.1);
-
- /* Text Colors */
- --text-primary: #f5f4f4;
- --text-secondary: #a3a0a0;
- --text-accent: #40fcd0;
-
- /* Accent Colors */
- --accent-primary: #40fcd0;
- --accent-secondary: #ff6b9d;
- --success: #00b75e;
- --error: #ff855e;
- --warning: #ffc107;
-
- /* Shadows */
- --shadow-glass: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
- --shadow-hover: 0 15px 35px rgba(0, 0, 0, 0.1);
- --shadow-card: 0 8px 16px rgba(0, 0, 0, 0.3);
-}
-
-/* Light Mode Theme */
-[data-theme="light"] {
- /* Color Palette */
- --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- --secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
- --success-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- --warning-gradient: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
- --dark-gradient: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
-
- /* Background Colors */
- --bg-primary: #ffffff;
- --bg-secondary: #f5f5f5;
- --bg-tertiary: #efefef;
- --bg-glass: rgba(0, 0, 0, 0.05);
-
- /* Text Colors */
- --text-primary: #1a1a1a;
- --text-secondary: #666666;
- --text-accent: #667eea;
-
- /* Accent Colors */
- --accent-primary: #667eea;
- --accent-secondary: #764ba2;
- --success: #00c853;
- --error: #d32f2f;
- --warning: #ffa000;
-
- /* Shadows */
- --shadow-glass: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
- --shadow-hover: 0 15px 35px rgba(0, 0, 0, 0.15);
- --shadow-card: 0 8px 16px rgba(0, 0, 0, 0.1);
-}
-
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-*,
-*::before,
-*::after {
- box-sizing: border-box;
-}
-
-html, body {
- width: 100%;
- max-width: 100%;
- overflow-x: hidden;
-}
-
-html {
- scroll-behavior: smooth;
-}
-
-body {
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- background: var(--bg-primary);
- background-image:
- radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
- radial-gradient(circle at 80% 20%, rgba(255, 107, 157, 0.3) 0%, transparent 50%),
- radial-gradient(circle at 40% 40%, rgba(100, 255, 218, 0.2) 0%, transparent 50%);
- min-height: 100vh;
- color: var(--text-primary);
- line-height: 1.6;
- overflow-x: hidden;
-}
-
-/* Header Styles */
-.header {
- position: fixed;
- top: 0;
- width: 100%;
- z-index: 1000;
- backdrop-filter: blur(20px);
- background: rgba(15, 15, 35, 0.8);
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.navbar {
- padding: 1rem 0;
-}
-
-.nav-container {
- max-width: 1200px;
- margin: 0 auto;
- padding: 0 2rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.nav-logo {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- font-size: 1.5rem;
- font-weight: 700;
- color: var(--accent-primary);
-}
-
-.nav-logo i {
- font-size: 2rem;
- background: var(--primary-gradient);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
-}
-
-.nav-menu {
- display: flex;
- gap: 2rem;
- list-style: none;
-}
-
-.nav-link {
- color: var(--text-secondary);
- text-decoration: none;
- font-weight: 500;
- padding: 0.5rem 1rem;
- border-radius: 0.5rem;
- transition: all 0.3s ease;
- position: relative;
-}
-
-.nav-link:hover,
-.nav-link.active {
- color: var(--accent-primary);
- background: rgba(100, 255, 218, 0.1);
-}
-
-.user-profile {
- display: flex;
- align-items: center;
- gap: 0.75rem;
-}
-
-.theme-toggle-btn {
- background: rgba(255, 255, 255, 0.1);
- border: 1px solid rgba(100, 255, 218, 0.3);
- color: var(--accent-primary);
- width: 40px;
- height: 40px;
- border-radius: 50%;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 1.2rem;
- transition: all 0.3s ease;
- margin-right: 1rem;
-}
-
-.theme-toggle-btn:hover {
- background: rgba(100, 255, 218, 0.2);
- border-color: var(--accent-primary);
- transform: scale(1.1);
-}
-
-.theme-toggle-btn.light-mode {
- color: #ffd700;
-}
-
-.profile-img {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- border: 2px solid var(--accent-primary);
-}
-
-.username {
- font-weight: 500;
- color: var(--text-primary);
-}
-
-.nav-toggle {
- display: none;
- flex-direction: column;
- cursor: pointer;
-}
-
-.bar {
- width: 25px;
- height: 3px;
- background: var(--accent-primary);
- margin: 3px 0;
- transition: 0.3s;
- border-radius: 5px;
-}
-
-/* Main Content */
-.main-content {
- padding-top: 100px;
- min-height: 100vh;
-}
-
-.hero-section {
- text-align: center;
- padding: 3rem 2rem;
- max-width: 800px;
- margin: 0 auto;
-}
-
-.hero-title {
- font-size: 3.5rem;
- font-weight: 700;
- background: var(--primary-gradient);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
- margin-bottom: 1rem;
- animation: fadeInUp 1s ease-out;
-}
-
-.hero-subtitle {
- font-size: 1.2rem;
- color: var(--text-secondary);
- margin-bottom: 2rem;
- animation: fadeInUp 1s ease-out 0.2s both;
-}
-
-.container {
- width: 100%;
- max-width: 1100px;
- margin: 0 auto;
-}
-
-/* Balance Card */
-.balance-card {
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(20px);
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- padding: 2rem;
- text-align: center;
- box-shadow: var(--shadow-glass);
- transition: transform 0.3s ease, box-shadow 0.3s ease;
- animation: fadeInUp 1s ease-out 0.4s both;
-}
-
-.balance-card:hover {
- transform: translateY(-5px);
- box-shadow: var(--shadow-hover);
-}
-
-.balance-header {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 0.5rem;
- margin-bottom: 1rem;
-}
-
-.balance-header i {
- color: var(--accent-primary);
- font-size: 1.5rem;
-}
-
-.balance-header h4 {
- color: var(--text-secondary);
- font-weight: 500;
- text-transform: uppercase;
- letter-spacing: 1px;
- font-size: 0.9rem;
-}
-
-.balance-amount {
- font-size: 3rem;
- font-weight: 700;
- color: var(--text-primary);
- margin: 1rem 0;
- text-shadow: 0 0 20px rgba(100, 255, 218, 0.3);
-}
-
-.balance-trend {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 0.5rem;
-}
-
-.trend-indicator {
- font-size: 1.5rem;
- font-weight: bold;
-}
-
-.trend-indicator.positive {
- color: var(--success);
-}
-
-.trend-text {
- color: var(--text-secondary);
- font-size: 0.9rem;
-}
-
-/* Income & Expense Cards */
-.inc-exp-container {
- display: flex;
- width: 100%;
- max-width: 100%;
- gap: 1rem;
-}
-
-.income-card,
-.expense-card {
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(20px);
- border-radius: 15px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- padding: 1.5rem;
- display: flex;
- align-items: center;
- gap: 1rem;
- box-shadow: var(--shadow-glass);
- transition: all 0.3s ease;
-}
-
-.income-card:hover,
-.expense-card:hover {
- transform: translateY(-3px);
- box-shadow: var(--shadow-hover);
-}
-
-.card-icon {
- width: 60px;
- height: 60px;
- border-radius: 15px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 1.5rem;
- color: white;
-}
-
-.income-icon {
- background: var(--success-gradient);
-}
-
-.expense-icon {
- background: var(--warning-gradient);
-}
-
-.card-content h4 {
- color: var(--text-secondary);
- font-size: 0.9rem;
- font-weight: 500;
- text-transform: uppercase;
- letter-spacing: 1px;
- margin-bottom: 0.5rem;
-}
-
-.money-plus {
- color: var(--success);
- font-size: 1.5rem;
- font-weight: 600;
-}
-
-.money-minus {
- color: var(--error);
- font-size: 1.5rem;
- font-weight: 600;
-}
-
-/* History Section */
-.history-section {
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(20px);
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- padding: 2rem;
- box-shadow: var(--shadow-glass);
- animation: fadeInUp 1s ease-out 0.8s both;
-}
-
-.section-header {
- display: flex;
- flex-direction: column;
- margin-bottom: 1.5rem;
- gap: 1rem;
-}
-
-.section-header h3 {
- color: var(--text-primary);
- font-size: 1.3rem;
- font-weight: 600;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- border-bottom: none;
- padding-bottom: 0;
- margin: 0;
-}
-
-.section-header h3 i {
- color: var(--accent-primary);
-}
-
-.filter-controls {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.search-container {
- position: relative;
- max-width: 300px;
-}
-
-.search-input {
- width: 100%;
- padding: 0.75rem 1rem 0.75rem 2.5rem;
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 25px;
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(10px);
- color: var(--text-primary);
- font-size: 0.9rem;
- transition: all 0.3s ease;
-}
-
-.search-input:focus {
- outline: none;
- border-color: var(--accent-primary);
- box-shadow: 0 0 0 3px rgba(100, 255, 218, 0.1);
-}
-
-.search-input::placeholder {
- color: var(--text-secondary);
-}
-
-.search-icon {
- position: absolute;
- left: 0.75rem;
- top: 50%;
- transform: translateY(-50%);
- color: var(--text-secondary);
- font-size: 0.9rem;
-}
-
-.filter-buttons {
- display: flex;
- gap: 0.5rem;
- align-items: center;
- flex-wrap: wrap;
-}
-
-.advanced-filters {
- display: flex;
- gap: 1rem;
- align-items: center;
- flex-wrap: wrap;
- padding: 1rem;
- background: rgba(255, 255, 255, 0.03);
- border-radius: 15px;
- border: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.category-select,
-.date-input,
-.amount-input {
- padding: 0.5rem 1rem;
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 8px;
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(10px);
- color: var(--text-primary);
- font-size: 0.9rem;
- cursor: pointer;
- transition: all 0.3s ease;
- min-width: 120px;
-}
-
-.category-select:focus,
-.date-input:focus,
-.amount-input:focus {
- outline: none;
- border-color: var(--accent-primary);
- background: rgba(100, 255, 218, 0.1);
-}
-
-.category-select option {
- background: var(--bg-secondary);
- color: var(--text-primary);
-}
-
-.date-filters,
-.amount-filters {
- display: flex;
- gap: 0.5rem;
- align-items: center;
-}
-
-.date-input,
-.amount-input {
- cursor: text;
-}
-
-.amount-input::placeholder,
-.date-input::placeholder {
- color: var(--text-secondary);
-}
-
-.clear-filters-btn {
- padding: 0.5rem 1rem;
- background: rgba(255, 87, 34, 0.2);
- color: var(--error);
- border: 1px solid rgba(255, 87, 34, 0.3);
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.3s ease;
- font-size: 0.9rem;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.clear-filters-btn:hover {
- background: rgba(255, 87, 34, 0.3);
- border-color: var(--error);
-}
-
-.filter-btn {
- padding: 0.5rem 1rem;
- border: 1px solid rgba(255, 255, 255, 0.2);
- background: transparent;
- color: var(--text-secondary);
- border-radius: 25px;
- cursor: pointer;
- transition: all 0.3s ease;
- font-size: 0.9rem;
- font-weight: 500;
-}
-
-.filter-btn:hover,
-.filter-btn.active {
- background: var(--accent-primary);
- color: var(--bg-primary);
- border-color: var(--accent-primary);
-}
-
-.list {
- list-style: none;
- padding: 0;
- max-height: 400px;
- overflow-y: auto;
- scrollbar-width: thin;
- scrollbar-color: var(--accent-primary) transparent;
-}
-
-.list::-webkit-scrollbar {
- width: 8px;
-}
-
-.list::-webkit-scrollbar-track {
- background: rgba(255, 255, 255, 0.1);
- border-radius: 4px;
-}
-
-.list::-webkit-scrollbar-thumb {
- background: var(--accent-primary);
- border-radius: 4px;
-}
-
-.list li {
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(10px);
- border-radius: 12px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- padding: 1rem;
- margin-bottom: 0.5rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
- position: relative;
- transition: all 0.3s ease;
- animation: slideInLeft 0.3s ease-out;
- max-width: 100%;
- word-break: break-word;
-}
-
-.list li:hover {
- transform: translateX(5px);
- background: rgba(255, 255, 255, 0.1);
-}
-
-.list li.plus {
- border-left: 4px solid var(--success);
-}
-
-.list li.minus {
- border-left: 4px solid var(--error);
-}
-
-.delete-btn {
- position: absolute;
- left: -40px;
- top: 50%;
- transform: translateY(-50%);
- background: var(--error);
- color: white;
- border: none;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- cursor: pointer;
- opacity: 0;
- transition: all 0.3s ease;
- font-size: 0.9rem;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.list li:hover .delete-btn {
- opacity: 1;
-}
-
-.delete-btn:hover {
- background: #d32f2f;
- transform: translateY(-50%) scale(1.1);
-}
-
-/* Data Management Section */
-.data-management-section {
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(20px);
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- padding: 2rem;
- box-shadow: var(--shadow-glass);
- animation: fadeInUp 1s ease-out 0.9s both;
-}
-
-.data-management-section h3 {
- color: var(--text-primary);
- font-size: 1.3rem;
- font-weight: 600;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- margin-bottom: 1.5rem;
-}
-
-.data-management-section h3 i {
- color: var(--accent-primary);
-}
-
-.data-actions {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 2rem;
-}
-
-.export-section,
-.import-section {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.export-section h4,
-.import-section h4 {
- color: var(--text-secondary);
- font-size: 1rem;
- font-weight: 500;
- margin-bottom: 0.5rem;
-}
-
-.export-buttons {
- display: flex;
- gap: 1rem;
-}
-
-.export-btn {
- padding: 0.75rem 1.5rem;
- background: var(--success-gradient);
- color: white;
- border: none;
- border-radius: 10px;
- font-size: 0.9rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.export-btn:hover {
- transform: translateY(-2px);
- box-shadow: 0 8px 25px rgba(76, 175, 80, 0.3);
-}
-
-.import-controls {
- display: flex;
- gap: 1rem;
- align-items: center;
-}
-
-.file-input {
- display: none;
-}
-
-.file-label {
- padding: 0.75rem 1.5rem;
- background: rgba(255, 255, 255, 0.1);
- color: var(--text-primary);
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 10px;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- font-size: 0.9rem;
- font-weight: 500;
-}
-
-.file-label:hover {
- background: rgba(255, 255, 255, 0.15);
- border-color: var(--accent-primary);
-}
-
-.import-btn {
- padding: 0.75rem 1.5rem;
- background: var(--primary-gradient);
- color: white;
- border: none;
- border-radius: 10px;
- font-size: 0.9rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.import-btn:disabled {
- background: rgba(255, 255, 255, 0.1);
- color: var(--text-secondary);
- cursor: not-allowed;
-}
-
-.import-btn:not(:disabled):hover {
- transform: translateY(-2px);
- box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
-}
-
-.import-options {
- margin-top: 1rem;
-}
-
-.checkbox-label {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- color: var(--text-secondary);
- font-size: 0.9rem;
- cursor: pointer;
-}
-
-.checkbox-label input[type="checkbox"] {
- width: 18px;
- height: 18px;
- accent-color: var(--accent-primary);
-}
-
-/* Form Section */
-.form-section {
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(20px);
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- padding: 2rem;
- box-shadow: var(--shadow-glass);
- animation: fadeInUp 1s ease-out 1s both;
-}
-
-.form-section h3 {
- color: var(--text-primary);
- font-size: 1.3rem;
- font-weight: 600;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- margin-bottom: 1.5rem;
- border-bottom: none;
- padding-bottom: 0;
-}
-
-.form-section h3 i {
- color: var(--accent-primary);
-}
-
-.transaction-form {
- display: flex;
- flex-direction: column;
- gap: 1.5rem;
-}
-
-.form-row {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 1rem;
-}
-
-.form-control {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
-}
-
-.form-control label {
- color: var(--text-secondary);
- font-weight: 500;
- font-size: 0.9rem;
- text-transform: uppercase;
- letter-spacing: 1px;
-}
-
-.form-control input {
- padding: 1rem;
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 10px;
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(10px);
- color: var(--text-primary);
- font-size: 1rem;
- transition: all 0.3s ease;
-}
-
-.form-control input:focus {
- outline: none;
- border-color: var(--accent-primary);
- box-shadow: 0 0 0 3px rgba(100, 255, 218, 0.1);
-}
-
-.form-control input::placeholder {
- color: var(--text-secondary);
-}
-
-.form-control select {
- padding: 1rem;
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 10px;
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(10px);
- color: var(--text-primary);
- font-size: 1rem;
- transition: all 0.3s ease;
- cursor: pointer;
-}
-
-.form-control select:focus {
- outline: none;
- border-color: var(--accent-primary);
- box-shadow: 0 0 0 3px rgba(100, 255, 218, 0.1);
-}
-
-.form-control select option {
- background: var(--bg-secondary);
- color: var(--text-primary);
- padding: 0.5rem;
-}
-
-.form-control small {
- color: var(--text-secondary);
- font-size: 0.8rem;
- font-style: italic;
-}
-
-.btn-submit {
- padding: 1rem 2rem;
- background: var(--primary-gradient);
- color: white;
- border: none;
- border-radius: 10px;
- font-size: 1rem;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 0.5rem;
- text-transform: uppercase;
- letter-spacing: 1px;
-}
-
-.btn-submit:hover {
- transform: translateY(-2px);
- box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
-}
-
-.btn-submit:active {
- transform: translateY(0);
-}
-
-/* Footer */
-.footer {
- background: rgba(15, 15, 35, 0.9);
- backdrop-filter: blur(20px);
- border-top: 1px solid rgba(255, 255, 255, 0.1);
- padding: 3rem 0 1rem;
- margin-top: 4rem;
-}
-
-.footer-container {
- max-width: 1200px;
- margin: 0 auto;
- padding: 0 2rem;
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
- gap: 2rem;
-}
-
-.footer-section {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.footer-logo {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- font-size: 1.5rem;
- font-weight: 700;
- color: var(--accent-primary);
- margin-bottom: 0.5rem;
-}
-
-.footer-logo i {
- font-size: 2rem;
- background: var(--primary-gradient);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
-}
-
-.footer-section p {
- color: var(--text-secondary);
- line-height: 1.6;
-}
-
-.footer-section h4 {
- color: var(--text-primary);
- font-weight: 600;
- margin-bottom: 0.5rem;
-}
-
-.footer-links {
- list-style: none;
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
-}
-
-.footer-links a {
- color: var(--text-secondary);
- text-decoration: none;
- transition: color 0.3s ease;
-}
-
-.footer-links a:hover {
- color: var(--accent-primary);
-}
-
-.social-links {
- display: flex;
- gap: 1rem;
-}
-
-.social-link {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- background: rgba(255, 255, 255, 0.1);
- border-radius: 50%;
- color: var(--text-secondary);
- text-decoration: none;
- transition: all 0.3s ease;
-}
-
-.social-link:hover {
- background: var(--accent-primary);
- color: var(--bg-primary);
- transform: translateY(-2px);
-}
-
-.footer-bottom {
- border-top: 1px solid rgba(255, 255, 255, 0.1);
- margin-top: 2rem;
- padding-top: 1rem;
-}
-
-.footer-bottom .footer-container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-wrap: wrap;
- gap: 1rem;
- grid-template-columns: 1fr;
-}
-
-.footer-bottom p {
- color: var(--text-secondary);
- font-size: 0.9rem;
-}
-
-.footer-stats {
- display: flex;
- gap: 1rem;
- flex-wrap: wrap;
-}
-
-.footer-stats span {
- font-size: 0.8rem;
- color: var(--text-secondary);
- background: rgba(255, 255, 255, 0.05);
- padding: 0.25rem 0.75rem;
- border-radius: 15px;
-}
-
-/* Animations */
-@keyframes fadeInUp {
- from {
- opacity: 0;
- transform: translateY(30px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-@keyframes slideInLeft {
- from {
- opacity: 0;
- transform: translateX(-20px);
- }
- to {
- opacity: 1;
- transform: translateX(0);
- }
-}
-
-@keyframes pulse {
- 0%, 100% {
- transform: scale(1);
- }
- 50% {
- transform: scale(1.05);
- }
-}
-
-/* Transaction Display Improvements */
-.transaction-content {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- flex: 1;
-}
-
-.transaction-main {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.transaction-text {
- font-weight: 500;
- color: var(--text-primary);
-}
-
-.transaction-amount {
- font-weight: 600;
- font-size: 1.1rem;
-}
-
-.transaction-date {
- font-size: 0.8rem;
- color: var(--text-secondary);
- opacity: 0.7;
-}
-
-.transaction-category {
- font-size: 0.8rem;
- color: var(--accent-primary);
- background: rgba(100, 255, 218, 0.1);
- padding: 0.2rem 0.6rem;
- border-radius: 15px;
- margin-top: 0.25rem;
- display: inline-block;
-}
-
-/* Balance Card States */
-.balance-card.positive .balance-amount {
- color: var(--success);
- text-shadow: 0 0 20px rgba(0, 230, 118, 0.3);
-}
-
-.balance-card.negative .balance-amount {
- color: var(--error);
- text-shadow: 0 0 20px rgba(255, 87, 34, 0.3);
-}
-
-.balance-card.neutral .balance-amount {
- color: var(--text-secondary);
- text-shadow: none;
-}
-
-/* Mobile Navigation */
-@media (max-width: 768px) {
- .nav-menu {
- position: fixed;
- top: 80px;
- left: -100%;
- width: 100%;
- height: calc(100vh - 80px);
- background: rgba(15, 15, 35, 0.95);
- backdrop-filter: blur(20px);
- flex-direction: column;
- justify-content: flex-start;
- align-items: center;
- padding: 2rem 0;
- transition: left 0.3s ease;
- }
-
- .nav-menu.active {
- left: 0;
- display: flex;
- }
-
- .nav-link {
- font-size: 1.2rem;
- padding: 1rem 2rem;
- width: 80%;
- text-align: center;
- margin: 0.5rem 0;
- }
-
- .nav-toggle {
- display: flex;
- }
-
- .nav-toggle.active .bar:nth-child(2) {
- opacity: 0;
- }
-
- .nav-toggle.active .bar:nth-child(1) {
- transform: translateY(8px) rotate(45deg);
- }
-
- .nav-toggle.active .bar:nth-child(3) {
- transform: translateY(-8px) rotate(-45deg);
- }
-
- .user-profile {
- display: none;
- }
-}
-
-/* Responsive Design */
-@media (max-width: 768px) {
- .hero-title {
- font-size: 2.5rem;
- }
-
- .container {
- padding: 0 1rem;
- }
-
- .inc-exp-container {
- grid-template-columns: 1fr;
- }
-
- .form-row {
- grid-template-columns: 1fr;
- }
-
- .section-header {
- flex-direction: column;
- align-items: stretch;
- }
-
- .footer-bottom .footer-container {
- flex-direction: column;
- text-align: center;
- }
-
- .footer-stats {
- justify-content: center;
- }
-}
-
-@media (max-width: 480px) {
- .hero-title {
- font-size: 2rem;
- }
-
- .balance-amount {
- font-size: 2rem;
- }
-
- .income-card,
- .expense-card {
- flex-direction: column;
- text-align: center;
- }
-
- .card-icon {
- width: 50px;
- height: 50px;
- }
-
- .advanced-filters {
- flex-direction: column;
- align-items: stretch;
- }
-
- .date-filters,
- .amount-filters {
- justify-content: stretch;
- }
-
- .date-input,
- .amount-input,
- .category-select {
- flex: 1;
- }
-
- .data-actions {
- grid-template-columns: 1fr;
- }
-}
-
-/* PWA Install Prompt */
-.install-prompt {
- position: fixed;
- bottom: 20px;
- left: 20px;
- right: 20px;
- z-index: 10000;
- animation: slideInUp 0.3s ease-out;
-}
-
-.install-prompt-content {
- background: rgba(15, 15, 35, 0.95);
- backdrop-filter: blur(20px);
- border-radius: 15px;
- border: 1px solid rgba(255, 255, 255, 0.2);
- padding: 1.5rem;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
-}
-
-.install-prompt-header {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- margin-bottom: 0.75rem;
-}
-
-.install-prompt-header i {
- color: var(--accent-primary);
- font-size: 1.5rem;
-}
-
-.install-prompt-header h3 {
- color: var(--text-primary);
- font-size: 1.1rem;
- font-weight: 600;
- margin: 0;
-}
-
-.install-prompt p {
- color: var(--text-secondary);
- margin-bottom: 1rem;
- font-size: 0.9rem;
-}
-
-.install-prompt-actions {
- display: flex;
- gap: 1rem;
-}
-
-.install-btn {
- padding: 0.75rem 1.5rem;
- background: var(--primary-gradient);
- color: white;
- border: none;
- border-radius: 8px;
- font-size: 0.9rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- flex: 1;
- justify-content: center;
-}
-
-.install-btn:hover {
- transform: translateY(-1px);
- box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
-}
-
-.dismiss-btn {
- padding: 0.75rem 1.5rem;
- background: transparent;
- color: var(--text-secondary);
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 8px;
- font-size: 0.9rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- justify-content: center;
-}
-
-.dismiss-btn:hover {
- background: rgba(255, 255, 255, 0.1);
- color: var(--text-primary);
-}
-
-/* Offline Indicator */
-.offline-indicator {
- position: fixed;
- top: 80px;
- left: 50%;
- transform: translateX(-50%);
- background: rgba(255, 152, 0, 0.9);
- color: white;
- padding: 0.75rem 1.5rem;
- border-radius: 25px;
- font-size: 0.9rem;
- font-weight: 500;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- z-index: 9999;
- backdrop-filter: blur(10px);
- box-shadow: 0 4px 20px rgba(255, 152, 0, 0.3);
- animation: slideInDown 0.3s ease-out;
-}
-
-/* Update Notification */
-.update-notification {
- position: fixed;
- top: 20px;
- right: 20px;
- z-index: 10000;
- animation: slideInRight 0.3s ease-out;
-}
-
-.update-content {
- background: rgba(76, 175, 80, 0.9);
- color: white;
- padding: 1rem 1.5rem;
- border-radius: 10px;
- display: flex;
- align-items: center;
- gap: 1rem;
- backdrop-filter: blur(10px);
- box-shadow: 0 4px 20px rgba(76, 175, 80, 0.3);
-}
-
-.update-btn {
- background: white;
- color: #4CAF50;
- border: none;
- padding: 0.5rem 1rem;
- border-radius: 5px;
- font-size: 0.9rem;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.3s ease;
-}
-
-.update-btn:hover {
- transform: translateY(-1px);
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
-}
-
-/* Touch and gesture improvements */
-@media (hover: none) and (pointer: coarse) {
- /* Touch device specific styles */
- .filter-btn,
- .export-btn,
- .import-btn,
- .btn-submit {
- min-height: 44px;
- padding: 0.75rem 1.5rem;
- }
-
- .delete-btn {
- min-width: 44px;
- min-height: 44px;
- }
-
- .nav-link {
- padding: 1rem 1.5rem;
- }
-
- /* Larger tap targets for mobile */
- .category-select,
- .date-input,
- .amount-input,
- .search-input {
- min-height: 48px;
- font-size: 16px; /* Prevents zoom on iOS */
- }
-
- .form-control input,
- .form-control select {
- min-height: 48px;
- font-size: 16px;
- }
-}
-
-/* Animations */
-@keyframes slideInUp {
- from {
- transform: translateY(100%);
- opacity: 0;
- }
- to {
- transform: translateY(0);
- opacity: 1;
- }
-}
-
-@keyframes slideInDown {
- from {
- transform: translateX(-50%) translateY(-100%);
- opacity: 0;
- }
- to {
- transform: translateX(-50%) translateY(0);
- opacity: 1;
- }
-}
-
-@keyframes slideInRight {
- from {
- transform: translateX(100%);
- opacity: 0;
- }
- to {
- transform: translateX(0);
- opacity: 1;
- }
-}
-
-/* Safe area insets for devices with notches */
-@supports (padding: max(0px)) {
- .header {
- padding-left: max(0px, env(safe-area-inset-left));
- padding-right: max(0px, env(safe-area-inset-right));
- }
-
- .main-content {
- padding-left: max(2rem, env(safe-area-inset-left) + 2rem);
- padding-right: max(2rem, env(safe-area-inset-right) + 2rem);
- padding-bottom: max(2rem, env(safe-area-inset-bottom) + 2rem);
- }
-
- .install-prompt {
- bottom: max(20px, env(safe-area-inset-bottom) + 20px);
- left: max(20px, env(safe-area-inset-left) + 20px);
- right: max(20px, env(safe-area-inset-right) + 20px);
- }
-}
-
-@media (max-width: 768px) {
-
- body {
- overflow-x: hidden;
- }
-
- .nav-toggle {
- display: block;
- }
-
- .nav-menu {
- position: absolute;
- top: 64px;
- left: 0;
- width: 100%;
- background: #0f0f23;
- flex-direction: column;
- display: none;
- }
-
- .nav-menu.active {
- display: flex;
- }
-
- .user-profile {
- display: none;
- }
-
- .inc-exp-container {
- flex-direction: column;
- align-items: center;
- }
-
- .income-card,
- .expense-card {
- width: 100%;
- max-width: 100%;
- }
-
- form {
- display: flex;
- flex-direction: column;
- gap: 1rem;
- }
-}
-
-/* =========================
- HAMBURGER MENU FIX
- ========================= */
-
-/* Default: hide toggle */
-.nav-toggle {
- display: none;
-}
-
-/* Hamburger bars */
-.nav-toggle .bar {
- width: 25px;
- height: 3px;
- background-color: #64ffda;
- margin: 5px 0;
- display: block;
- transition: 0.3s ease;
-}
-
-/* Show hamburger on tablets & phones */
-@media (max-width: 1024px) {
- .nav-toggle {
- display: block;
- cursor: pointer;
- z-index: 1001;
- }
-
- .nav-menu {
- position: absolute;
- top: 64px;
- left: 0;
- width: 100%;
- background: #0f0f23;
- flex-direction: column;
- align-items: center;
- display: none;
- z-index: 1000;
- }
-
- .nav-menu.active {
- display: flex;
- }
-
- .nav-link {
- padding: 1rem;
- width: 100%;
- text-align: center;
- }
-
- .user-profile {
- display: none;
- }
-}
-
-/* Extra-small devices (<400px) */
-@media (max-width: 400px) {
- .hero-title {
- font-size: 1.5rem;
- }
-
- .hero-subtitle {
- font-size: 0.9rem;
- }
-}
-
-/* ULTRA SMALL DEVICES (320px)*/
-@media (max-width: 360px) {
-
- /* Global */
- html, body {
- overflow-x: hidden;
- }
-
- body {
- font-size: 14px;
- }
-
- .container {
- padding: 0.75rem;
- margin-left: auto;
- margin-right: auto;
- }
-
- /* Navbar */
- .nav-logo span {
- font-size: 0.9rem;
- }
-
- .nav-toggle .bar {
- width: 22px;
- height: 2.5px;
- }
-
- /* Hero */
- .hero-title {
- font-size: 1.4rem;
- line-height: 1.3;
- }
-
- .hero-subtitle {
- font-size: 0.85rem;
- }
-
- /* Balance */
- .balance-card {
- padding: 1rem;
- }
-
- .balance-amount {
- font-size: 1.5rem;
- }
-
- /* Income / Expense cards */
- .income-card,
- .expense-card {
- padding: 0.75rem;
- }
-
- /* Filters */
- .search-input,
- .category-select,
- .date-input,
- .amount-input,
- .filter-btn,
- .clear-filters-btn {
- width: 100%;
- min-width: 0;
- font-size: 0.85rem;
- }
-
- .filter-buttons {
- flex-wrap: wrap;
- gap: 0.5rem;
- }
-
- /* Transaction list */
- .list li {
- flex-direction: column;
- align-items: flex-start;
- gap: 0.25rem;
- }
-
- /* Form */
- .transaction-form {
- padding: 1rem;
- }
-
- .transaction-form input,
- .transaction-form select,
- .btn-submit {
- font-size: 0.9rem;
- }
-
- /* Footer */
- .footer-section h4 {
- font-size: 1rem;
- }
-
- .footer-links a {
- font-size: 0.85rem;
- }
- .nav-container {
- padding: 0 0.75rem;
- }
-
- .nav-logo span {
- font-size: 0.85rem;
- }
-}
-
-#scrollToTopBtn svg {
- display: block;
-}
-input,
-select,
-button {
- max-width: 100%;
- min-width: 0;
-}
-
-/* ============================================
- Security Dashboard Styles (Issue #338)
- ============================================ */
-
-/* Security Overview */
-.security-overview {
- display: flex;
- gap: 2rem;
- margin-bottom: 2rem;
- padding: 1.5rem;
- background: var(--bg-glass);
- border-radius: 12px;
- backdrop-filter: blur(10px);
- border: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.security-score-card {
- text-align: center;
- padding: 1rem;
-}
-
-.score-circle {
- width: 100px;
- height: 100px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- margin: 0 auto 0.5rem;
- font-size: 2rem;
- font-weight: bold;
-}
-
-.score-circle.excellent { background: linear-gradient(135deg, #00c853 0%, #00f5a0 100%); color: white; }
-.score-circle.good { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; }
-.score-circle.fair { background: linear-gradient(135deg, #ffa726 0%, #ffcc02 100%); color: #333; }
-.score-circle.poor { background: linear-gradient(135deg, #ff5252 0%, #f48fb1 100%); color: white; }
-
-.score-label {
- font-size: 0.875rem;
- color: var(--text-secondary);
-}
-
-.security-stats {
- display: flex;
- gap: 2rem;
- align-items: center;
- flex-wrap: wrap;
-}
-
-.stat-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 0.25rem;
- padding: 1rem;
- min-width: 100px;
-}
-
-.stat-item i {
- font-size: 1.5rem;
- color: var(--accent-primary);
-}
-
-.stat-item i.enabled { color: var(--success); }
-.stat-item i.disabled { color: var(--error); }
-
-.stat-label {
- font-size: 0.75rem;
- color: var(--text-secondary);
- text-transform: uppercase;
-}
-
-.stat-value {
- font-size: 1.125rem;
- font-weight: 600;
- color: var(--text-primary);
-}
-
-/* Security Sections */
-.security-sections {
- display: flex;
- flex-direction: column;
- gap: 1.5rem;
-}
-
-.security-section {
- background: var(--bg-glass);
- border-radius: 12px;
- padding: 1.5rem;
- backdrop-filter: blur(10px);
- border: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.security-section h3 {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- margin-bottom: 1rem;
- color: var(--text-primary);
- font-size: 1.125rem;
-}
-
-.security-section h3 i {
- color: var(--accent-primary);
-}
-
-.security-section h3 .btn-link {
- margin-left: auto;
- font-size: 0.875rem;
- background: none;
- border: none;
- color: var(--accent-secondary);
- cursor: pointer;
- text-decoration: underline;
-}
-
-/* 2FA Section */
-.twofa-enabled, .twofa-disabled {
- padding: 1rem;
-}
-
-.status-badge {
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.5rem 1rem;
- border-radius: 20px;
- font-size: 0.875rem;
- margin-bottom: 1rem;
-}
-
-.status-badge.success {
- background: rgba(0, 200, 83, 0.2);
- color: var(--success);
-}
-
-.status-badge.warning {
- background: rgba(255, 167, 38, 0.2);
- color: var(--warning);
-}
-
-.twofa-actions {
- display: flex;
- gap: 1rem;
- margin-top: 1rem;
- flex-wrap: wrap;
-}
-
-/* Sessions List */
-.sessions-list {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
-}
-
-.session-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 1rem;
- background: var(--bg-secondary);
- border-radius: 8px;
- border: 1px solid transparent;
- transition: border-color 0.2s;
-}
-
-.session-item.current {
- border-color: var(--accent-primary);
-}
-
-.session-device {
- display: flex;
- align-items: center;
- gap: 1rem;
-}
-
-.session-device i {
- font-size: 1.5rem;
- color: var(--text-secondary);
-}
-
-.device-info {
- display: flex;
- flex-direction: column;
-}
-
-.device-info strong {
- color: var(--text-primary);
-}
-
-.session-location {
- font-size: 0.75rem;
- color: var(--text-secondary);
-}
-
-.session-meta {
- display: flex;
- align-items: center;
- gap: 0.75rem;
-}
-
-.session-time {
- font-size: 0.75rem;
- color: var(--text-secondary);
-}
-
-.badge-2fa {
- background: var(--success);
- color: white;
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- font-size: 0.625rem;
-}
-
-.badge-current {
- background: var(--accent-primary);
- color: var(--bg-primary);
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- font-size: 0.75rem;
- font-weight: 600;
-}
-
-.btn-revoke {
- background: transparent;
- border: 1px solid var(--error);
- color: var(--error);
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.2s;
-}
-
-.btn-revoke:hover {
- background: var(--error);
- color: white;
-}
-
-/* Audit Trail */
-.audit-list {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- max-height: 400px;
- overflow-y: auto;
-}
-
-.audit-item {
- display: flex;
- align-items: center;
- gap: 1rem;
- padding: 0.75rem;
- background: var(--bg-secondary);
- border-radius: 8px;
- border-left: 3px solid transparent;
-}
-
-.audit-item.failed {
- border-left-color: var(--error);
- background: rgba(255, 82, 82, 0.1);
-}
-
-.audit-icon {
- width: 32px;
- height: 32px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
-}
-
-.audit-icon.success { background: rgba(0, 200, 83, 0.2); color: var(--success); }
-.audit-icon.danger { background: rgba(255, 82, 82, 0.2); color: var(--error); }
-.audit-icon.warning { background: rgba(255, 167, 38, 0.2); color: var(--warning); }
-.audit-icon.info { background: rgba(79, 172, 254, 0.2); color: var(--accent-primary); }
-
-.audit-content {
- flex: 1;
- min-width: 0;
-}
-
-.audit-content strong {
- display: block;
- color: var(--text-primary);
- font-size: 0.875rem;
-}
-
-.audit-meta {
- font-size: 0.75rem;
- color: var(--text-secondary);
-}
-
-.audit-status {
- width: 24px;
- height: 24px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
-}
-
-.audit-status.success { background: var(--success); color: white; }
-.audit-status.failure { background: var(--error); color: white; }
-
-/* Password Form */
-.password-form {
- max-width: 400px;
-}
-
-.password-form .form-group {
- margin-bottom: 1rem;
-}
-
-.password-form label {
- display: block;
- margin-bottom: 0.25rem;
- font-size: 0.875rem;
- color: var(--text-secondary);
-}
-
-.password-form input {
- width: 100%;
- padding: 0.75rem;
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 6px;
- background: var(--bg-secondary);
- color: var(--text-primary);
- font-size: 1rem;
-}
-
-.password-form input:focus {
- outline: none;
- border-color: var(--accent-primary);
-}
-
-/* Buttons */
-.btn-primary {
- background: var(--primary-gradient);
- color: white;
- border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 8px;
- font-size: 0.875rem;
- font-weight: 600;
- cursor: pointer;
- transition: transform 0.2s, box-shadow 0.2s;
-}
-
-.btn-primary:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
-}
-
-.btn-secondary {
- background: transparent;
- color: var(--text-primary);
- border: 1px solid rgba(255, 255, 255, 0.3);
- padding: 0.75rem 1.5rem;
- border-radius: 8px;
- font-size: 0.875rem;
- cursor: pointer;
- transition: all 0.2s;
-}
-
-.btn-secondary:hover {
- background: rgba(255, 255, 255, 0.1);
- border-color: var(--accent-primary);
-}
-
-.btn-danger {
- background: var(--error);
- color: white;
- border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 8px;
- font-size: 0.875rem;
- cursor: pointer;
- transition: transform 0.2s, opacity 0.2s;
-}
-
-.btn-danger:hover {
- opacity: 0.9;
- transform: translateY(-2px);
-}
-
-/* Security Modal */
-.security-modal {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 10000;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.modal-overlay {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.7);
- backdrop-filter: blur(4px);
-}
-
-.security-modal .modal-content {
- position: relative;
- background: var(--bg-secondary);
- border-radius: 16px;
- padding: 2rem;
- max-width: 500px;
- width: 90%;
- max-height: 90vh;
- overflow-y: auto;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
- border: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.modal-close {
- position: absolute;
- top: 1rem;
- right: 1rem;
- background: transparent;
- border: none;
- color: var(--text-secondary);
- font-size: 1.5rem;
- cursor: pointer;
- transition: color 0.2s;
-}
-
-.modal-close:hover {
- color: var(--text-primary);
-}
-
-/* 2FA Setup */
-.setup-2fa-content h2 {
- margin-bottom: 1.5rem;
- color: var(--text-primary);
-}
-
-.setup-steps .step {
- margin-bottom: 1.5rem;
- padding-bottom: 1.5rem;
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.setup-steps .step:last-child {
- border-bottom: none;
-}
-
-.setup-steps h3 {
- font-size: 1rem;
- color: var(--accent-primary);
- margin-bottom: 0.5rem;
-}
-
-.qr-code {
- display: flex;
- justify-content: center;
- margin: 1rem 0;
- padding: 1rem;
- background: white;
- border-radius: 8px;
- width: fit-content;
- margin: 1rem auto;
-}
-
-.qr-code img {
- width: 200px;
- height: 200px;
-}
-
-.secret-key {
- display: block;
- padding: 0.75rem;
- background: var(--bg-tertiary);
- border-radius: 6px;
- font-family: 'Courier New', monospace;
- font-size: 1rem;
- letter-spacing: 0.1em;
- word-break: break-all;
- text-align: center;
-}
-
-.totp-input {
- width: 100%;
- padding: 1rem;
- font-size: 1.5rem;
- text-align: center;
- letter-spacing: 0.5em;
- font-weight: bold;
- border: 2px solid rgba(255, 255, 255, 0.2);
- border-radius: 8px;
- background: var(--bg-secondary);
- color: var(--text-primary);
- margin-bottom: 1rem;
-}
-
-.totp-input:focus {
- outline: none;
- border-color: var(--accent-primary);
-}
-
-/* Backup Codes */
-.backup-codes-content h2 {
- margin-bottom: 0.5rem;
- color: var(--text-primary);
-}
-
-.backup-codes-content .warning {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- padding: 1rem;
- background: rgba(255, 167, 38, 0.2);
- border-radius: 8px;
- margin-bottom: 1.5rem;
- font-size: 0.875rem;
- color: var(--warning);
-}
-
-.backup-codes-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 0.5rem;
- margin-bottom: 1.5rem;
-}
-
-.backup-code {
- padding: 0.75rem;
- background: var(--bg-tertiary);
- border-radius: 6px;
- font-family: 'Courier New', monospace;
- font-size: 0.875rem;
- text-align: center;
-}
-
-.backup-actions {
- display: flex;
- gap: 1rem;
- margin-bottom: 1rem;
-}
-
-.modal-actions {
- display: flex;
- gap: 1rem;
- margin-top: 1.5rem;
-}
-
-/* Empty State */
-.empty-state {
- text-align: center;
- padding: 2rem;
- color: var(--text-secondary);
- font-style: italic;
-}
-
-/* User Controls */
-.user-controls {
- display: flex;
- align-items: center;
- gap: 0.75rem;
-}
-
-.user-controls .security-btn {
- background: transparent;
- border: 1px solid var(--accent-primary);
- color: var(--accent-primary);
- padding: 0.5rem;
- border-radius: 6px;
- cursor: pointer;
- transition: all 0.2s;
-}
-
-.user-controls .security-btn:hover {
- background: var(--accent-primary);
- color: var(--bg-primary);
-}
-
-.user-controls .user-name {
- font-size: 0.875rem;
- color: var(--text-secondary);
-}
-
-.user-controls .logout-btn {
- background: var(--error);
- color: white;
- border: none;
- padding: 0.5rem 1rem;
- border-radius: 6px;
- cursor: pointer;
- font-size: 0.875rem;
- transition: opacity 0.2s;
-}
-
-.user-controls .logout-btn:hover {
- opacity: 0.9;
-}
-
-/* Responsive Security Dashboard */
-@media (max-width: 768px) {
- .security-overview {
- flex-direction: column;
- gap: 1.5rem;
- }
-
- .security-stats {
- justify-content: center;
- }
-
- .session-item {
- flex-direction: column;
- align-items: flex-start;
- gap: 0.75rem;
- }
-
- .session-meta {
- width: 100%;
- justify-content: space-between;
- }
-
- .twofa-actions {
- flex-direction: column;
- }
-
- .backup-codes-grid {
- grid-template-columns: 1fr;
- }
-
- .backup-actions {
- flex-direction: column;
- }
-
- .modal-actions {
- flex-direction: column;
- }
-}
-
-
-/* ============================================
- AI-DRIVEN BUDGET INTELLIGENCE DASHBOARD
- Z-Score Anomaly Detection & Self-Healing
- Issue #339
- ============================================ */
-
-.intelligence-section {
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(20px);
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- padding: 2rem;
- box-shadow: var(--shadow-glass);
- margin-bottom: 2rem;
-}
-
-.intelligence-section .section-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 1.5rem;
-}
-
-.intelligence-section h3 {
- color: var(--text-primary);
- font-size: 1.3rem;
- font-weight: 600;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- margin: 0;
-}
-
-.intelligence-section h3 i {
- color: var(--accent-primary);
-}
-
-.intelligence-controls .refresh-btn {
- background: rgba(255, 255, 255, 0.1);
- border: 1px solid rgba(255, 255, 255, 0.2);
- color: var(--text-primary);
- padding: 0.5rem 1rem;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.3s ease;
-}
-
-.intelligence-controls .refresh-btn:hover {
- background: var(--accent-primary);
- color: var(--bg-primary);
-}
-
-/* Intelligence Loader */
-.intelligence-loader {
- display: none;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 2rem;
- gap: 1rem;
-}
-
-.intelligence-loader.active {
- display: flex;
-}
-
-.loader-spinner {
- width: 40px;
- height: 40px;
- border: 3px solid rgba(255, 255, 255, 0.1);
- border-top-color: var(--accent-primary);
- border-radius: 50%;
- animation: spin 1s linear infinite;
-}
-
-@keyframes spin {
- to { transform: rotate(360deg); }
-}
-
-/* Intelligence Cards */
-.intelligence-cards {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 1rem;
- margin-bottom: 1.5rem;
-}
-
-.intel-card {
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 15px;
- padding: 1.25rem;
- display: flex;
- align-items: center;
- gap: 1rem;
- transition: all 0.3s ease;
-}
-
-.intel-card:hover {
- transform: translateY(-3px);
- box-shadow: var(--shadow-hover);
-}
-
-.intel-card-icon {
- width: 50px;
- height: 50px;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 1.25rem;
-}
-
-.intel-card-icon.anomaly {
- background: linear-gradient(135deg, #ff6b6b, #ee5a5a);
- color: white;
-}
-
-.intel-card-icon.volatility {
- background: linear-gradient(135deg, #feca57, #ff9f43);
- color: white;
-}
-
-.intel-card-icon.volatility.high {
- background: linear-gradient(135deg, #ff6b6b, #ee5a5a);
-}
-
-.intel-card-icon.volatility.medium {
- background: linear-gradient(135deg, #feca57, #ff9f43);
-}
-
-.intel-card-icon.volatility.low {
- background: linear-gradient(135deg, #1dd1a1, #10ac84);
-}
-
-.intel-card-icon.reallocation {
- background: linear-gradient(135deg, #54a0ff, #5f27cd);
- color: white;
-}
-
-.intel-card-icon.health {
- background: linear-gradient(135deg, #1dd1a1, #10ac84);
- color: white;
-}
-
-.intel-card-icon.health.excellent {
- background: linear-gradient(135deg, #1dd1a1, #10ac84);
-}
-
-.intel-card-icon.health.good {
- background: linear-gradient(135deg, #54a0ff, #2e86de);
-}
-
-.intel-card-icon.health.fair {
- background: linear-gradient(135deg, #feca57, #ff9f43);
-}
-
-.intel-card-icon.health.poor {
- background: linear-gradient(135deg, #ff6b6b, #ee5a5a);
-}
-
-.intel-card-content {
- display: flex;
- flex-direction: column;
-}
-
-.intel-card-value {
- font-size: 1.5rem;
- font-weight: 700;
- color: var(--text-primary);
-}
-
-.intel-card-label {
- font-size: 0.85rem;
- color: var(--text-secondary);
-}
-
-/* Intelligence Tabs */
-.intelligence-tabs {
- display: flex;
- gap: 0.5rem;
- margin-bottom: 1.5rem;
- flex-wrap: wrap;
-}
-
-.intelligence-tab {
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.1);
- color: var(--text-secondary);
- padding: 0.75rem 1.25rem;
- border-radius: 10px;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- font-size: 0.9rem;
-}
-
-.intelligence-tab:hover {
- background: rgba(255, 255, 255, 0.1);
- color: var(--text-primary);
-}
-
-.intelligence-tab.active {
- background: var(--accent-primary);
- color: var(--bg-primary);
- border-color: var(--accent-primary);
-}
-
-/* Intelligence Panels */
-.intelligence-panels {
- margin-bottom: 1.5rem;
-}
-
-.intelligence-panel {
- display: none;
- background: rgba(255, 255, 255, 0.03);
- border-radius: 15px;
- padding: 1.5rem;
- border: 1px solid rgba(255, 255, 255, 0.05);
-}
-
-.intelligence-panel.active {
- display: block;
-}
-
-.intelligence-panel h4 {
- color: var(--text-primary);
- margin-bottom: 0.5rem;
- font-size: 1.1rem;
-}
-
-.panel-description {
- color: var(--text-secondary);
- font-size: 0.85rem;
- margin-bottom: 1rem;
-}
-
-/* Empty State */
-.empty-state {
- text-align: center;
- padding: 2rem;
- color: var(--text-secondary);
-}
-
-.empty-state i {
- font-size: 3rem;
- margin-bottom: 1rem;
- opacity: 0.5;
-}
-
-.empty-state p {
- margin-bottom: 0.5rem;
-}
-
-.empty-state small {
- font-size: 0.8rem;
- opacity: 0.7;
-}
-
-/* Anomaly Timeline */
-.anomaly-list {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
-}
-
-.anomaly-item {
- display: flex;
- gap: 1rem;
- padding: 1rem;
- background: rgba(255, 255, 255, 0.03);
- border-radius: 10px;
- border-left: 4px solid var(--warning);
- transition: all 0.3s ease;
-}
-
-.anomaly-item:hover {
- background: rgba(255, 255, 255, 0.05);
-}
-
-.anomaly-item.critical {
- border-left-color: #ff6b6b;
-}
-
-.anomaly-item.high {
- border-left-color: #ff9f43;
-}
-
-.anomaly-item.medium {
- border-left-color: #feca57;
-}
-
-.anomaly-indicator {
- width: 12px;
- height: 12px;
- border-radius: 50%;
- background: currentColor;
- margin-top: 0.25rem;
-}
-
-.anomaly-item.critical .anomaly-indicator {
- background: #ff6b6b;
- box-shadow: 0 0 10px rgba(255, 107, 107, 0.5);
-}
-
-.anomaly-item.high .anomaly-indicator {
- background: #ff9f43;
- box-shadow: 0 0 10px rgba(255, 159, 67, 0.5);
-}
-
-.anomaly-item.medium .anomaly-indicator {
- background: #feca57;
-}
-
-.anomaly-content {
- flex: 1;
-}
-
-.anomaly-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 0.5rem;
-}
-
-.anomaly-category {
- font-weight: 600;
- color: var(--text-primary);
-}
-
-.anomaly-zscore {
- font-size: 0.8rem;
- background: rgba(255, 107, 107, 0.2);
- color: #ff6b6b;
- padding: 0.25rem 0.5rem;
- border-radius: 5px;
-}
-
-.anomaly-details {
- display: flex;
- gap: 1rem;
- margin-bottom: 0.5rem;
-}
-
-.anomaly-amount {
- font-weight: 600;
- color: var(--error);
-}
-
-.anomaly-deviation {
- font-size: 0.85rem;
- color: var(--text-secondary);
-}
-
-.anomaly-meta {
- display: flex;
- gap: 1rem;
- font-size: 0.8rem;
- color: var(--text-secondary);
-}
-
-/* Volatility Chart */
-.volatility-bars {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.volatility-bar-item {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
-}
-
-.volatility-bar-label {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.category-name {
- font-weight: 500;
- color: var(--text-primary);
-}
-
-.risk-badge {
- font-size: 0.7rem;
- padding: 0.2rem 0.5rem;
- border-radius: 10px;
- text-transform: uppercase;
- font-weight: 600;
-}
-
-.risk-badge.low {
- background: rgba(29, 209, 161, 0.2);
- color: #1dd1a1;
-}
-
-.risk-badge.medium {
- background: rgba(254, 202, 87, 0.2);
- color: #feca57;
-}
-
-.risk-badge.high {
- background: rgba(255, 107, 107, 0.2);
- color: #ff6b6b;
-}
-
-.volatility-bar-container {
- position: relative;
- height: 8px;
- background: rgba(255, 255, 255, 0.1);
- border-radius: 4px;
- overflow: hidden;
-}
-
-.volatility-bar {
- height: 100%;
- border-radius: 4px;
- transition: width 0.5s ease;
-}
-
-.volatility-value {
- position: absolute;
- right: -45px;
- top: 50%;
- transform: translateY(-50%);
- font-size: 0.8rem;
- color: var(--text-secondary);
-}
-
-.volatility-stats {
- display: flex;
- gap: 1.5rem;
- font-size: 0.8rem;
- color: var(--text-secondary);
-}
-
-/* Reallocation Suggestions */
-.reallocation-list {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.reallocation-item {
- background: rgba(255, 255, 255, 0.03);
- border-radius: 12px;
- padding: 1.25rem;
- border: 1px solid rgba(255, 255, 255, 0.05);
-}
-
-.reallocation-flow {
- display: flex;
- align-items: center;
- gap: 1rem;
- margin-bottom: 1rem;
- flex-wrap: wrap;
-}
-
-.reallocation-from,
-.reallocation-to {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
-}
-
-.category-badge {
- padding: 0.5rem 1rem;
- border-radius: 8px;
- font-weight: 500;
-}
-
-.category-badge.surplus {
- background: rgba(29, 209, 161, 0.2);
- color: #1dd1a1;
-}
-
-.category-badge.deficit {
- background: rgba(255, 107, 107, 0.2);
- color: #ff6b6b;
-}
-
-.surplus-amount {
- font-size: 0.85rem;
- color: #1dd1a1;
-}
-
-.reallocation-arrow {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 0.25rem;
- color: var(--accent-primary);
-}
-
-.transfer-amount {
- font-size: 0.9rem;
- font-weight: 600;
-}
-
-.reallocation-reason {
- font-size: 0.85rem;
- color: var(--text-secondary);
- margin-bottom: 1rem;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.reallocation-actions {
- display: flex;
- gap: 0.75rem;
-}
-
-.reallocation-actions .btn {
- padding: 0.5rem 1rem;
- border-radius: 8px;
- font-size: 0.85rem;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.reallocation-actions .btn-primary {
- background: var(--accent-primary);
- color: var(--bg-primary);
- border: none;
-}
-
-.reallocation-actions .btn-primary:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 15px rgba(100, 255, 218, 0.3);
-}
-
-.reallocation-actions .btn-secondary {
- background: transparent;
- color: var(--text-secondary);
- border: 1px solid rgba(255, 255, 255, 0.2);
-}
-
-.reallocation-actions .btn-secondary:hover {
- background: rgba(255, 255, 255, 0.1);
-}
-
-/* Alerts List */
-.alerts-list {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
-}
-
-.alert-item {
- display: flex;
- gap: 1rem;
- padding: 1rem;
- background: rgba(255, 255, 255, 0.03);
- border-radius: 10px;
- align-items: flex-start;
-}
-
-.alert-icon {
- width: 36px;
- height: 36px;
- border-radius: 8px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 1rem;
-}
-
-.alert-item.anomaly .alert-icon {
- background: rgba(255, 107, 107, 0.2);
- color: #ff6b6b;
-}
-
-.alert-item.prediction .alert-icon {
- background: rgba(84, 160, 255, 0.2);
- color: #54a0ff;
-}
-
-.alert-item.reallocation .alert-icon {
- background: rgba(95, 39, 205, 0.2);
- color: #5f27cd;
-}
-
-.alert-item.standard .alert-icon {
- background: rgba(254, 202, 87, 0.2);
- color: #feca57;
-}
-
-.alert-content {
- flex: 1;
-}
-
-.alert-message {
- color: var(--text-primary);
- margin-bottom: 0.5rem;
-}
-
-.alert-meta {
- display: flex;
- gap: 0.75rem;
- font-size: 0.8rem;
-}
-
-.alert-type {
- color: var(--text-secondary);
- text-transform: capitalize;
-}
-
-.alert-priority {
- padding: 0.15rem 0.5rem;
- border-radius: 5px;
- font-size: 0.7rem;
- text-transform: uppercase;
-}
-
-.alert-priority.critical {
- background: rgba(255, 107, 107, 0.2);
- color: #ff6b6b;
-}
-
-.alert-priority.high {
- background: rgba(255, 159, 67, 0.2);
- color: #ff9f43;
-}
-
-.alert-priority.medium {
- background: rgba(254, 202, 87, 0.2);
- color: #feca57;
-}
-
-.alert-priority.low {
- background: rgba(29, 209, 161, 0.2);
- color: #1dd1a1;
-}
-
-/* Budget Intelligence Grid */
-.budget-intel-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
- gap: 1rem;
-}
-
-.budget-intel-card {
- background: rgba(255, 255, 255, 0.03);
- border-radius: 12px;
- padding: 1.25rem;
- border: 1px solid rgba(255, 255, 255, 0.05);
- transition: all 0.3s ease;
-}
-
-.budget-intel-card:hover {
- border-color: rgba(255, 255, 255, 0.1);
- transform: translateY(-2px);
-}
-
-.budget-intel-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 1rem;
-}
-
-.budget-name {
- font-weight: 600;
- color: var(--text-primary);
-}
-
-.trend-indicator {
- width: 24px;
- height: 24px;
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 0.75rem;
-}
-
-.trend-indicator.increasing {
- background: rgba(255, 107, 107, 0.2);
- color: #ff6b6b;
-}
-
-.trend-indicator.decreasing {
- background: rgba(29, 209, 161, 0.2);
- color: #1dd1a1;
-}
-
-.trend-indicator.stable {
- background: rgba(84, 160, 255, 0.2);
- color: #54a0ff;
-}
-
-.budget-intel-progress {
- margin-bottom: 1rem;
-}
-
-.progress-bar {
- height: 8px;
- background: rgba(255, 255, 255, 0.1);
- border-radius: 4px;
- overflow: hidden;
- margin-bottom: 0.5rem;
-}
-
-.progress-fill {
- height: 100%;
- border-radius: 4px;
- transition: width 0.5s ease;
-}
-
-.progress-fill.normal {
- background: var(--accent-primary);
-}
-
-.progress-fill.caution {
- background: #feca57;
-}
-
-.progress-fill.warning {
- background: #ff9f43;
-}
-
-.progress-fill.over {
- background: #ff6b6b;
-}
-
-.progress-labels {
- display: flex;
- justify-content: space-between;
- font-size: 0.8rem;
- color: var(--text-secondary);
-}
-
-.budget-intel-stats {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 0.5rem;
-}
-
-.stat-item {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
-}
-
-.stat-label {
- font-size: 0.7rem;
- color: var(--text-secondary);
- text-transform: uppercase;
-}
-
-.stat-value {
- font-size: 0.9rem;
- font-weight: 600;
- color: var(--text-primary);
-}
-
-.stat-value.high {
- color: #ff6b6b;
-}
-
-.stat-value.medium {
- color: #feca57;
-}
-
-.stat-value.low {
- color: #1dd1a1;
-}
-
-.budget-anomaly-badge {
- margin-top: 1rem;
- padding: 0.5rem;
- background: rgba(255, 107, 107, 0.1);
- border-radius: 8px;
- color: #ff6b6b;
- font-size: 0.8rem;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-/* Transaction Analyzer */
-.transaction-analyzer {
- background: rgba(255, 255, 255, 0.03);
- border-radius: 15px;
- padding: 1.5rem;
- border: 1px solid rgba(255, 255, 255, 0.05);
-}
-
-.transaction-analyzer h4 {
- color: var(--text-primary);
- margin-bottom: 1rem;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.analyze-form .analyze-row {
- display: flex;
- gap: 0.75rem;
- flex-wrap: wrap;
-}
-
-.analyze-form input,
-.analyze-form select {
- flex: 1;
- min-width: 120px;
- padding: 0.75rem 1rem;
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 8px;
- color: var(--text-primary);
- font-size: 0.9rem;
-}
-
-.analyze-form input:focus,
-.analyze-form select:focus {
- outline: none;
- border-color: var(--accent-primary);
-}
-
-.analyze-form select option {
- background: var(--bg-secondary);
-}
-
-.analyze-btn {
- background: var(--accent-primary);
- color: var(--bg-primary);
- border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 8px;
- cursor: pointer;
- font-weight: 500;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- transition: all 0.3s ease;
-}
-
-.analyze-btn:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 15px rgba(100, 255, 218, 0.3);
-}
-
-/* Analysis Result */
-.analysis-result {
- margin-top: 1rem;
- padding: 1rem;
- border-radius: 10px;
-}
-
-.analysis-result.normal {
- background: rgba(29, 209, 161, 0.1);
- border: 1px solid rgba(29, 209, 161, 0.3);
-}
-
-.analysis-result.anomaly {
- background: rgba(255, 107, 107, 0.1);
- border: 1px solid rgba(255, 107, 107, 0.3);
-}
-
-.analysis-header {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- margin-bottom: 1rem;
- font-weight: 600;
-}
-
-.analysis-result.normal .analysis-header {
- color: #1dd1a1;
-}
-
-.analysis-result.anomaly .analysis-header {
- color: #ff6b6b;
-}
-
-.analysis-details {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 0.75rem;
-}
-
-.detail-row {
- display: flex;
- justify-content: space-between;
- font-size: 0.9rem;
-}
-
-.detail-row span:first-child {
- color: var(--text-secondary);
-}
-
-.detail-row span:last-child {
- color: var(--text-primary);
- font-weight: 500;
-}
-
-.detail-row .critical {
- color: #ff6b6b;
-}
-
-.detail-row .high {
- color: #ff9f43;
-}
-
-.detail-row .medium {
- color: #feca57;
-}
-
-.analysis-suggestion {
- margin-top: 1rem;
- padding: 0.75rem;
- background: rgba(84, 160, 255, 0.1);
- border-radius: 8px;
- color: #54a0ff;
- font-size: 0.9rem;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-/* Notifications */
-.notification {
- position: fixed;
- bottom: 20px;
- right: 20px;
- padding: 1rem 1.5rem;
- border-radius: 10px;
- display: flex;
- align-items: center;
- gap: 0.75rem;
- z-index: 10000;
- animation: slideInRight 0.3s ease-out;
-}
-
-.notification.info {
- background: var(--bg-secondary);
- border: 1px solid rgba(84, 160, 255, 0.3);
- color: #54a0ff;
-}
-
-.notification.success {
- background: var(--bg-secondary);
- border: 1px solid rgba(29, 209, 161, 0.3);
- color: #1dd1a1;
-}
-
-.notification.error {
- background: var(--bg-secondary);
- border: 1px solid rgba(255, 107, 107, 0.3);
- color: #ff6b6b;
-}
-
-.notification.fade-out {
- animation: fadeOut 0.3s ease-out forwards;
-}
-
-@keyframes fadeOut {
- to {
- opacity: 0;
- transform: translateX(20px);
- }
-}
-
-/* Responsive adjustments for Intelligence Dashboard */
-@media (max-width: 768px) {
- .intelligence-cards {
- grid-template-columns: repeat(2, 1fr);
- }
-
- .intelligence-tabs {
- overflow-x: auto;
- padding-bottom: 0.5rem;
- }
-
- .intelligence-tab {
- white-space: nowrap;
- flex-shrink: 0;
- }
-
- .budget-intel-grid {
- grid-template-columns: 1fr;
- }
-
- .analyze-form .analyze-row {
- flex-direction: column;
- }
-
- .analyze-form input,
- .analyze-form select {
- width: 100%;
- }
-
- .analysis-details {
- grid-template-columns: 1fr;
- }
-
- .reallocation-flow {
- flex-direction: column;
- align-items: flex-start;
- }
-
- .reallocation-arrow {
- transform: rotate(90deg);
- align-self: center;
- }
-}
-
-@media (max-width: 480px) {
- .intelligence-cards {
- grid-template-columns: 1fr;
- }
-
- .intel-card {
- padding: 1rem;
- }
-
- .intel-card-value {
- font-size: 1.25rem;
- }
-
- .volatility-bar-container {
- margin-right: 50px;
- }
-}
-/* Theme Toggle Button */
-.theme-toggle-btn {
- background: none;
- border: none;
- font-size: 1.5rem;
- cursor: pointer;
- color: var(--text-color);
- margin-left: auto;
-}
-
-/* Default Light Theme Variables */
-:root {
- --bg-color: #f3f6f9;
- --text-color: #0f0f23;
- --card-bg: #42779b;
- --nav-bg: #0f0f23;
-}
-
-/* Dark Theme Variables */
-.dark-theme {
- --bg-color: #121212;
- --text-color: #e0e0e0;
- --card-bg: #1e1e1e;
- --nav-bg: #0a0a0a;
-}
-
-/* Apply Variables */
-body {
- background-color: var(--bg-color);
- color: var(--text-color);
- transition: background-color 0.3s, color 0.3s;
-}
-
-/* Example component styling */
-.header,
-.navbar {
- background-color: var(--nav-bg);
-}
-
-.balance-card,
-.income-card,
-.expense-card,
-.footer {
- background-color: var(--card-bg);
-}
-
-/* Subscription Manager Styles */
-.subscriptions-section {
- padding: 2rem;
- max-width: 1400px;
- margin: 0 auto;
-}
-
-.subscription-statistics {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
- gap: 1.5rem;
- margin-bottom: 2rem;
-}
-
-.stat-card {
- background: var(--bg-secondary);
- padding: 1.5rem;
- border-radius: 12px;
- display: flex;
- align-items: center;
- gap: 1rem;
- box-shadow: var(--shadow-card);
- transition: all 0.3s ease;
-}
-
-.stat-card:hover {
- transform: translateY(-4px);
- box-shadow: var(--shadow-hover);
-}
-
-.stat-card.stat-warning {
- border-left: 4px solid var(--warning);
-}
-
-.stat-card.stat-info {
- border-left: 4px solid var(--accent-primary);
-}
-
-.stat-icon {
- font-size: 2.5rem;
- opacity: 0.8;
-}
-
-.stat-content {
- flex: 1;
-}
-
-.stat-value {
- font-size: 1.8rem;
- font-weight: bold;
- color: var(--text-primary);
-}
-
-.stat-label {
- font-size: 0.9rem;
- color: var(--text-secondary);
- margin-top: 0.25rem;
-}
-
-.stat-sublabel {
- font-size: 0.8rem;
- color: var(--warning);
- margin-top: 0.25rem;
-}
-
-.subscription-filters {
- display: flex;
- gap: 1rem;
- margin-bottom: 2rem;
- flex-wrap: wrap;
-}
-
-.subscription-filter {
- padding: 0.75rem 1.5rem;
- border: 2px solid var(--bg-tertiary);
- background: var(--bg-secondary);
- color: var(--text-secondary);
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.subscription-filter:hover {
- background: var(--bg-tertiary);
-}
-
-.subscription-filter.active {
- background: var(--accent-primary);
- color: var(--bg-primary);
- border-color: var(--accent-primary);
-}
-
-.subscription-list {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
- gap: 1.5rem;
-}
-
-.subscription-card {
- background: var(--bg-secondary);
- border-radius: 12px;
- padding: 1.5rem;
- box-shadow: var(--shadow-card);
- transition: all 0.3s ease;
- border-left: 4px solid transparent;
-}
-
-.subscription-card:hover {
- transform: translateY(-4px);
- box-shadow: var(--shadow-hover);
-}
-
-.subscription-card.subscription-active {
- border-left-color: var(--success);
-}
-
-.subscription-card.subscription-cancelled {
- border-left-color: var(--error);
- opacity: 0.7;
-}
-
-.subscription-card.subscription-paused {
- border-left-color: var(--warning);
-}
-
-.subscription-card.subscription-trial {
- border-left-color: var(--accent-primary);
-}
-
-.subscription-card.subscription-unused {
- background: linear-gradient(135deg, var(--bg-secondary) 0%, rgba(255, 107, 157, 0.1) 100%);
-}
-
-.subscription-card.subscription-upcoming {
- background: linear-gradient(135deg, var(--bg-secondary) 0%, rgba(255, 193, 7, 0.1) 100%);
-}
-
-.subscription-header {
- display: flex;
- align-items: center;
- gap: 1rem;
- margin-bottom: 1rem;
-}
-
-.subscription-logo {
- font-size: 2.5rem;
- width: 60px;
- height: 60px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--bg-tertiary);
- border-radius: 12px;
-}
-
-.subscription-info {
- flex: 1;
-}
-
-.subscription-info h3 {
- margin: 0;
- font-size: 1.2rem;
- color: var(--text-primary);
-}
-
-.subscription-category {
- font-size: 0.85rem;
- color: var(--text-secondary);
- text-transform: capitalize;
-}
-
-.subscription-actions {
- display: flex;
- gap: 0.5rem;
-}
-
-.btn-icon {
- width: 32px;
- height: 32px;
- border-radius: 6px;
- border: none;
- background: var(--bg-tertiary);
- color: var(--text-secondary);
- cursor: pointer;
- transition: all 0.2s ease;
-}
-
-.btn-icon:hover {
- background: var(--bg-glass);
- transform: scale(1.1);
-}
-
-.subscription-details {
- margin: 1rem 0;
-}
-
-.detail-row {
- display: flex;
- justify-content: space-between;
- padding: 0.5rem 0;
- border-bottom: 1px solid var(--bg-tertiary);
-}
-
-.detail-label {
- color: var(--text-secondary);
- font-size: 0.9rem;
-}
-
-.detail-value {
- color: var(--text-primary);
- font-weight: 500;
-}
-
-.detail-value.text-warning {
- color: var(--warning);
- font-weight: bold;
-}
-
-.subscription-status {
- display: flex;
- gap: 0.5rem;
- flex-wrap: wrap;
- margin: 1rem 0;
-}
-
-.status-badge {
- padding: 0.4rem 0.8rem;
- border-radius: 6px;
- font-size: 0.85rem;
- font-weight: 500;
-}
-
-.subscription-active .status-badge {
- background: rgba(0, 183, 94, 0.2);
- color: var(--success);
-}
-
-.subscription-cancelled .status-badge {
- background: rgba(255, 133, 94, 0.2);
- color: var(--error);
-}
-
-.subscription-paused .status-badge {
- background: rgba(255, 193, 7, 0.2);
- color: var(--warning);
-}
-
-.subscription-trial .status-badge {
- background: rgba(64, 252, 208, 0.2);
- color: var(--accent-primary);
-}
-
-.badge-auto {
- background: rgba(103, 126, 234, 0.2);
- color: #667eea;
-}
-
-.badge-ghost {
- background: rgba(255, 107, 157, 0.2);
- color: var(--accent-secondary);
-}
-
-.badge-upcoming {
- background: rgba(255, 193, 7, 0.2);
- color: var(--warning);
-}
-
-.subscription-alert {
- padding: 1rem;
- border-radius: 8px;
- margin: 1rem 0;
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 1rem;
-}
-
-.alert-warning {
- background: rgba(255, 193, 7, 0.1);
- border: 1px solid var(--warning);
- color: var(--text-primary);
-}
-
-.subscription-footer {
- display: flex;
- gap: 0.5rem;
- flex-wrap: wrap;
-}
-
-.btn-sm {
- padding: 0.5rem 1rem;
- border: none;
- border-radius: 6px;
- background: var(--bg-tertiary);
- color: var(--text-primary);
- cursor: pointer;
- font-size: 0.85rem;
- transition: all 0.2s ease;
-}
-
-.btn-sm:hover {
- background: var(--accent-primary);
- color: var(--bg-primary);
-}
-
-.btn-sm.btn-warning {
- background: var(--warning);
- color: var(--bg-primary);
-}
-
-/* Modals */
-.modal {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.8);
- display: none;
- align-items: center;
- justify-content: center;
- z-index: 1000;
-}
-
-.modal-content {
- background: var(--bg-secondary);
- border-radius: 12px;
- max-width: 600px;
- width: 90%;
- max-height: 90vh;
- overflow-y: auto;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
-}
-
-.modal-content.large {
- max-width: 900px;
-}
-
-.modal-header {
- padding: 1.5rem;
- border-bottom: 1px solid var(--bg-tertiary);
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.modal-header h2 {
- margin: 0;
- color: var(--text-primary);
-}
-
-.modal-close {
- background: none;
- border: none;
- font-size: 1.5rem;
- color: var(--text-secondary);
- cursor: pointer;
- transition: all 0.2s ease;
-}
-
-.modal-close:hover {
- color: var(--error);
- transform: rotate(90deg);
-}
-
-.modal-body {
- padding: 1.5rem;
-}
-
-.detected-list {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.detected-item {
- background: var(--bg-tertiary);
- padding: 1.5rem;
- border-radius: 8px;
-}
-
-.detected-header {
- display: flex;
- align-items: center;
- gap: 1rem;
- margin-bottom: 1rem;
-}
-
-.detected-logo {
- font-size: 2rem;
-}
-
-.detected-info h3 {
- margin: 0;
- color: var(--text-primary);
-}
-
-.detected-confidence {
- font-size: 0.85rem;
- color: var(--success);
-}
-
-.detected-details {
- margin: 1rem 0;
- color: var(--text-secondary);
-}
-
-.detected-details p {
- margin: 0.5rem 0;
-}
-
-/* Empty State */
-.empty-state {
- text-align: center;
- padding: 3rem;
-}
-
-.empty-icon {
- font-size: 4rem;
- margin-bottom: 1rem;
- opacity: 0.5;
-}
-
-.empty-state h3 {
- color: var(--text-primary);
- margin-bottom: 0.5rem;
-}
-
-.empty-state p {
- color: var(--text-secondary);
- margin-bottom: 2rem;
-}
-
-/* Toast Notifications */
-.toast {
- position: fixed;
- bottom: 20px;
- right: 20px;
- padding: 1rem 1.5rem;
- border-radius: 8px;
- background: var(--bg-secondary);
- color: var(--text-primary);
- box-shadow: var(--shadow-hover);
- transform: translateX(400px);
- transition: transform 0.3s ease;
- z-index: 2000;
-}
-
-.toast.show {
- transform: translateX(0);
-}
-
-.toast-success {
- border-left: 4px solid var(--success);
-}
-
-.toast-error {
- border-left: 4px solid var(--error);
-}
-
-.toast-info {
- border-left: 4px solid var(--accent-primary);
-}
-
-/* Loading Placeholder */
-.loading-placeholder {
- text-align: center;
- padding: 3rem;
- color: var(--text-secondary);
-}
-
-.loading-placeholder i {
- font-size: 2rem;
- margin-bottom: 1rem;
- color: var(--accent-primary);
-}
-
-/* ========================
- Financial Wellness & Health Score Styles (Issue #481)
- ======================== */
-
-.wellness-row {
- display: grid;
- grid-template-columns: 400px 1fr;
- gap: 1.5rem;
- margin-bottom: 2rem;
-}
-
-.health-gauge-card, .insights-card {
- background: var(--bg-secondary);
- border-radius: 12px;
- padding: 2rem;
- box-shadow: var(--shadow-card);
-}
-
-.health-gauge-widget {
- display: flex;
- flex-direction: column;
- gap: 1.5rem;
-}
-
-.health-gauge-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.health-gauge-header h3 {
- margin: 0;
- font-size: 1.3rem;
- color: var(--text-primary);
-}
-
-.health-trend {
- padding: 0.4rem 0.8rem;
- border-radius: 6px;
- font-size: 0.9rem;
- font-weight: 500;
-}
-
-.health-trend-improving {
- background: rgba(0, 183, 94, 0.2);
- color: var(--success);
-}
-
-.health-trend-declining {
- background: rgba(255, 133, 94, 0.2);
- color: var(--error);
-}
-
-.health-trend-stable {
- background: rgba(79, 172, 254, 0.2);
- color: #4facfe;
-}
-
-.health-gauge {
- display: flex;
- justify-content: center;
- margin: 1rem 0;
-}
-
-.gauge-svg {
- width: 100%;
- max-width: 250px;
- height: auto;
-}
-
-.gauge-score {
- font-size: 2.5rem;
- font-weight: bold;
-}
-
-.gauge-grade {
- font-size: 0.9rem;
- text-transform: uppercase;
- letter-spacing: 1px;
-}
-
-.health-components {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.component-section h4 {
- margin: 0 0 0.5rem 0;
- font-size: 1rem;
- color: var(--text-secondary);
-}
-
-.component-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0.75rem;
- background: var(--bg-tertiary);
- border-radius: 6px;
- border-left: 3px solid transparent;
-}
-
-.component-item.strength {
- border-left-color: var(--success);
-}
-
-.component-item.weakness {
- border-left-color: var(--warning);
-}
-
-.component-label {
- font-size: 0.9rem;
- color: var(--text-primary);
-}
-
-.component-score {
- font-weight: bold;
- font-size: 1rem;
- color: var(--accent-primary);
-}
-
-.priority-badge {
- padding: 0.2rem 0.6rem;
- border-radius: 4px;
- font-size: 0.75rem;
- font-weight: 500;
- margin-left: 0.5rem;
-}
-
-.priority-critical {
- background: rgba(255, 133, 94, 0.2);
- color: var(--error);
-}
-
-.priority-high {
- background: rgba(255, 193, 7, 0.2);
- color: var(--warning);
-}
-
-.priority-medium {
- background: rgba(79, 172, 254, 0.2);
- color: #4facfe;
-}
-
-.health-actions {
- display: flex;
- gap: 0.75rem;
-}
-
-.health-actions .btn {
- flex: 1;
- padding: 0.75rem;
- border-radius: 8px;
- border: none;
- cursor: pointer;
- font-size: 0.9rem;
- transition: all 0.2s ease;
-}
-
-.health-actions .btn-primary {
- background: var(--accent-primary);
- color: var(--bg-primary);
-}
-
-.health-actions .btn-secondary {
- background: var(--bg-tertiary);
- color: var(--text-primary);
-}
-
-.health-actions .btn:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
-}
-
-/* Smart Insights Styles */
-.smart-insights {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.insights-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 0.5rem;
-}
-
-.insights-header h3 {
- margin: 0;
- font-size: 1.3rem;
- color: var(--text-primary);
-}
-
-.insights-list {
- display: flex;
- flex-direction: column;
- gap: 1rem;
- max-height: 600px;
- overflow-y: auto;
- padding-right: 0.5rem;
-}
-
-.insights-empty {
- text-align: center;
- padding: 3rem;
- color: var(--text-secondary);
-}
-
-.insight-card {
- background: var(--bg-tertiary);
- border-radius: 8px;
- padding: 1.25rem;
- border-left: 4px solid transparent;
- transition: all 0.2s ease;
-}
-
-.insight-card:hover {
- background: var(--bg-glass);
- transform: translateX(4px);
-}
-
-.insight-critical {
- border-left-color: var(--error);
-}
-
-.insight-high {
- border-left-color: var(--warning);
-}
-
-.insight-medium {
- border-left-color: #4facfe;
-}
-
-.insight-low, .insight-info {
- border-left-color: var(--accent-primary);
-}
-
-.insight-header {
- display: flex;
- align-items: flex-start;
- gap: 0.75rem;
- margin-bottom: 0.75rem;
-}
-
-.insight-icon {
- font-size: 1.5rem;
-}
-
-.insight-title-section {
- flex: 1;
-}
-
-.insight-title-section h4 {
- margin: 0 0 0.25rem 0;
- font-size: 1rem;
- color: var(--text-primary);
-}
-
-.insight-type {
- font-size: 0.8rem;
- color: var(--text-secondary);
- text-transform: capitalize;
-}
-
-.insight-confidence {
- font-size: 0.8rem;
- color: var(--success);
- padding: 0.2rem 0.6rem;
- background: rgba(0, 183, 94, 0.1);
- border-radius: 4px;
-}
-
-.insight-message {
- color: var(--text-primary);
- line-height: 1.5;
- margin: 0.75rem 0;
-}
-
-.insight-metrics {
- display: flex;
- flex-wrap: wrap;
- gap: 0.5rem;
- margin: 0.75rem 0;
-}
-
-.insight-metrics span {
- padding: 0.4rem 0.8rem;
- background: var(--bg-secondary);
- border-radius: 6px;
- font-size: 0.85rem;
- color: var(--text-secondary);
-}
-
-.insight-actions {
- display: flex;
- gap: 0.5rem;
- margin: 1rem 0 0.5rem 0;
- flex-wrap: wrap;
-}
-
-.insight-actions .btn-action {
- padding: 0.5rem 1rem;
- background: var(--accent-primary);
- color: var(--bg-primary);
- border: none;
- border-radius: 6px;
- cursor: pointer;
- font-size: 0.85rem;
- transition: all 0.2s ease;
-}
-
-.insight-actions .btn-action:hover {
- background: var(--accent-secondary);
- transform: translateY(-1px);
-}
-
-.insight-footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 1rem;
- padding-top: 1rem;
- border-top: 1px solid var(--bg-secondary);
-}
-
-.insight-age {
- font-size: 0.8rem;
- color: var(--text-secondary);
-}
-
-.insight-controls {
- display: flex;
- gap: 1rem;
-}
-
-.btn-text {
- background: none;
- border: none;
- color: var(--accent-primary);
- cursor: pointer;
- font-size: 0.85rem;
- transition: all 0.2s ease;
-}
-
-.btn-text:hover {
- color: var(--accent-secondary);
- text-decoration: underline;
-}
-
-/* Responsive adjustments */
-@media (max-width: 1200px) {
- .wellness-row {
- grid-template-columns: 1fr;
- }
-}
-
-@media (max-width: 768px) {
- .health-gauge-card, .insights-card {
- padding: 1rem;
- }
-
- .health-actions {
- flex-direction: column;
- }
-
- .insight-header {
- flex-direction: column;
- }
-}
-
-/* =========================
- Scroll To Top Button
- ========================= */
-
-.scroll-to-top {
- position: fixed;
- bottom: 30px;
- right: 30px;
- width: 52px;
- height: 52px;
- border-radius: 50%;
- border: none;
- background: linear-gradient(135deg, #64ffda, #48e0c1);
- color: #0f0f23;
- font-size: 1.2rem;
- cursor: pointer;
- display: none;
- align-items: center;
- justify-content: center;
- z-index: 9999;
- box-shadow: 0 8px 25px rgba(100, 255, 218, 0.35);
- transition: transform 0.25s ease, box-shadow 0.25s ease, opacity 0.25s ease;
-}
-
-.scroll-to-top:hover {
- transform: translateY(-4px);
- box-shadow: 0 12px 35px rgba(100, 255, 218, 0.5);
-}
-
-.scroll-to-top.show {
- display: flex;
-}
-
-/* Mobile adjustment */
-@media (max-width: 768px) {
- .scroll-to-top {
- bottom: 20px;
- right: 20px;
- width: 46px;
- height: 46px;
- }
-}
-
-/* =========================
- FOOTER UX & ACCESSIBILITY
- (Issue #549 – Scoped Fix)
-========================= */
-
-.footer-links a {
- color: inherit;
- text-decoration: none;
- transition: color 0.25s ease, text-decoration-color 0.25s ease;
-}
-
-.footer-links a:hover,
-.footer-links a:focus-visible {
- color: #64ffda;
- text-decoration: underline;
- outline: none;
-}
-
-.social-link {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- transition: transform 0.25s ease, color 0.25s ease;
-}
-
-.social-link:hover,
-.social-link:focus-visible {
- transform: scale(1.15);
- color: #64ffda;
- outline: none;
-}
diff --git a/goals.html b/goals.html
deleted file mode 100644
index 3e2fb7e1..00000000
--- a/goals.html
+++ /dev/null
@@ -1,791 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- Financial Goals - ExpenseFlow
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/index.html b/index.html
deleted file mode 100644
index 91f0e856..00000000
--- a/index.html
+++ /dev/null
@@ -1,1638 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ExpenseFlow - Smart Money Management
-
-
-
-
-
-
-
-
-
-
-
-
-
Smart Money Management
-
Take control of your finances with our intuitive expense tracker
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Data Management
-
-
-
Export Data
-
-
- Quick CSV
-
-
- Quick PDF
-
-
- Advanced Export
-
-
-
-
-
Import Data
-
-
-
- Choose File
-
-
- Import Data
-
-
-
-
-
-
- Merge with existing data
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Filter Options
-
-
- Start Date
-
-
-
- End Date
-
-
-
- Category
-
- All Categories
- 🍽️ Food & Dining
- 🚗 Transportation
- 🛒 Shopping
- 🎬 Entertainment
- 💡 Bills & Utilities
- 🏥 Healthcare
- 📋 Other
-
-
-
- Type
-
- All Types
- 💰 Income Only
- 💸 Expenses Only
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Analyzing spending patterns...
-
-
-
-
-
-
-
-
- Anomalies
-
-
- Volatility
-
-
- Reallocations
-
-
- Alerts
-
-
- Budgets
-
-
-
-
-
-
-
Z-Score Anomaly Detection
-
Transactions flagged with Z-Score ≥ 2.0 (statistically unusual spending)
-
-
-
-
-
Spending Volatility Analysis
-
Category volatility index based on coefficient of variation
-
-
-
-
-
Self-Healing Budget Suggestions
-
AI-suggested fund transfers from surplus to deficit categories
-
-
-
-
-
Budget Alerts
-
AI-driven alerts including predictions and anomaly warnings
-
-
-
-
-
Budget Intelligence Overview
-
Budgets with AI-enhanced statistics and predictions
-
-
-
-
-
-
-
Analyze Transaction
-
-
-
-
- Select Category
- Food & Dining
- Transportation
- Shopping
- Entertainment
- Bills & Utilities
- Healthcare
- Other
-
-
-
- Analyze
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Notes (optional)
-
-
-
-
- Cancel
-
- Save
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Preferred Currency
-
- Loading currencies...
-
- All expenses will be converted to this currency for display
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Install ExpenseFlow for quick access and offline functionality!
-
-
- Install
-
-
- Not now
-
-
-
-
-
-
-
-
- You're offline. Changes will be saved locally.
-
-
-
-
-
-
- A new version is available!
- Update Now
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Unlock ExpenseFlow
-
Your journey to financial freedom starts here. Sign in to access smart analytics, personalized goals, and secure tracking.
-
-
-
-
- Login Now
-
-
-
- Get Started
-
-
-
-
-
Premium Features:
-
- Smart Tracking
- Budget Planning
- Cloud Sync
- Data Export
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Login to ExpenseFlow
-
-
- Email:
-
-
-
- Password:
-
-
- Login
-
- Don't have an account?
- Sign up here
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Sign Up for ExpenseFlow
-
-
- Username:
-
-
-
- Email:
-
-
-
- Password:
-
-
- Sign Up
-
- Already have an account?
- Login here
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
deleted file mode 100644
index 7d549274..00000000
--- a/manifest.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "ExpenseFlow - Smart Money Management",
- "short_name": "ExpenseFlow",
- "description": "Take control of your finances with our intuitive expense tracker",
- "start_url": "/",
- "display": "standalone",
- "theme_color": "#64ffda",
- "background_color": "#0f0f23",
- "icons": [
- {
- "src": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkyIiBoZWlnaHQ9IjE5MiIgdmlld0JveD0iMCAwIDE5MiAxOTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxOTIiIGhlaWdodD0iMTkyIiByeD0iNDIiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhclswXSkiLz4KPHBhdGggZD0iTTQ4IDY0SDQ4LjA0OEgxNDRWMTI4SDk2VjE0NEgxNDRDMTUyLjgzNyAxNDQgMTYwIDEzNi44MzcgMTYwIDEyOFY2NEM1OCA1Ni4xNjMgNTIuMDQzIDQ4IDQ4IEg0OEM0MC4wNzEgNDggMzIgNTYuMDcxIDMyIDY0VjEyOEMzMiAxMzUuOTI5IDQwLjA3MSAxNDQgNDggMTQ0SDgwVjEyOEg0OFY2NFoiIGZpbGw9IiM2NGZmZGEiLz4KPHBhdGggZD0iTTY0IDgwSDgwVjk2SDY0VjgwWiIgZmlsbD0iIzY0ZmZkYSIvPgo8cGF0aCBkPSJNOTYgODBIMTI4Vjk2SDk2VjgwWiIgZmlsbD0iIzY0ZmZkYSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudDBfbGluZWFyWzBdIiB4MT0iMCIgeTE9IjAiIHgyPSIxOTIiIHkyPSIxOTIiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzY2N2VlYSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM3NjRiYTIiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K",
- "sizes": "192x192",
- "type": "image/svg+xml"
- }
- ]
-}
\ No newline at end of file
diff --git a/.env.example b/server/.env.example
similarity index 100%
rename from .env.example
rename to server/.env.example
diff --git a/server/Dockerfile b/server/Dockerfile
new file mode 100644
index 00000000..80bfec62
--- /dev/null
+++ b/server/Dockerfile
@@ -0,0 +1,20 @@
+# Use Node.js 18 as the base image
+FROM node:18-alpine
+
+# Set working directory
+WORKDIR /app
+
+# Copy package files
+COPY package.json package-lock.json ./
+
+# Install dependencies
+RUN npm ci --only=production
+
+# Copy application code
+COPY . .
+
+# Expose port
+EXPOSE 5000
+
+# Start the server
+CMD ["npm", "start"]
diff --git a/chatbot/README.md b/server/chatbot/README.md
similarity index 100%
rename from chatbot/README.md
rename to server/chatbot/README.md
diff --git a/chatbot/chatbot.config.js b/server/chatbot/chatbot.config.js
similarity index 100%
rename from chatbot/chatbot.config.js
rename to server/chatbot/chatbot.config.js
diff --git a/chatbot/chatbot.css b/server/chatbot/chatbot.css
similarity index 100%
rename from chatbot/chatbot.css
rename to server/chatbot/chatbot.css
diff --git a/chatbot/chatbot.data.js b/server/chatbot/chatbot.data.js
similarity index 100%
rename from chatbot/chatbot.data.js
rename to server/chatbot/chatbot.data.js
diff --git a/chatbot/chatbot.html b/server/chatbot/chatbot.html
similarity index 100%
rename from chatbot/chatbot.html
rename to server/chatbot/chatbot.html
diff --git a/chatbot/chatbot.js b/server/chatbot/chatbot.js
similarity index 100%
rename from chatbot/chatbot.js
rename to server/chatbot/chatbot.js
diff --git a/middleware/analyticsValidator.js b/server/middleware/analyticsValidator.js
similarity index 100%
rename from middleware/analyticsValidator.js
rename to server/middleware/analyticsValidator.js
diff --git a/middleware/auditMiddleware.js b/server/middleware/auditMiddleware.js
similarity index 100%
rename from middleware/auditMiddleware.js
rename to server/middleware/auditMiddleware.js
diff --git a/middleware/auth.js b/server/middleware/auth.js
similarity index 100%
rename from middleware/auth.js
rename to server/middleware/auth.js
diff --git a/middleware/authMiddleware.js b/server/middleware/authMiddleware.js
similarity index 100%
rename from middleware/authMiddleware.js
rename to server/middleware/authMiddleware.js
diff --git a/middleware/bankingValidator.js b/server/middleware/bankingValidator.js
similarity index 100%
rename from middleware/bankingValidator.js
rename to server/middleware/bankingValidator.js
diff --git a/middleware/categorizationValidator.js b/server/middleware/categorizationValidator.js
similarity index 100%
rename from middleware/categorizationValidator.js
rename to server/middleware/categorizationValidator.js
diff --git a/middleware/clerkAuth.js b/server/middleware/clerkAuth.js
similarity index 100%
rename from middleware/clerkAuth.js
rename to server/middleware/clerkAuth.js
diff --git a/middleware/deviceFingerprint.js b/server/middleware/deviceFingerprint.js
similarity index 100%
rename from middleware/deviceFingerprint.js
rename to server/middleware/deviceFingerprint.js
diff --git a/middleware/errorMiddleware.js b/server/middleware/errorMiddleware.js
similarity index 100%
rename from middleware/errorMiddleware.js
rename to server/middleware/errorMiddleware.js
diff --git a/middleware/forecastValidator.js b/server/middleware/forecastValidator.js
similarity index 100%
rename from middleware/forecastValidator.js
rename to server/middleware/forecastValidator.js
diff --git a/middleware/gamificationValidator.js b/server/middleware/gamificationValidator.js
similarity index 100%
rename from middleware/gamificationValidator.js
rename to server/middleware/gamificationValidator.js
diff --git a/middleware/inputValidator.js b/server/middleware/inputValidator.js
similarity index 100%
rename from middleware/inputValidator.js
rename to server/middleware/inputValidator.js
diff --git a/middleware/insightValidator.js b/server/middleware/insightValidator.js
similarity index 100%
rename from middleware/insightValidator.js
rename to server/middleware/insightValidator.js
diff --git a/middleware/insightsValidator.js b/server/middleware/insightsValidator.js
similarity index 100%
rename from middleware/insightsValidator.js
rename to server/middleware/insightsValidator.js
diff --git a/middleware/investmentValidator.js b/server/middleware/investmentValidator.js
similarity index 100%
rename from middleware/investmentValidator.js
rename to server/middleware/investmentValidator.js
diff --git a/middleware/policyGuard.js b/server/middleware/policyGuard.js
similarity index 100%
rename from middleware/policyGuard.js
rename to server/middleware/policyGuard.js
diff --git a/middleware/rateLimit.js b/server/middleware/rateLimit.js
similarity index 100%
rename from middleware/rateLimit.js
rename to server/middleware/rateLimit.js
diff --git a/middleware/rateLimiter.js b/server/middleware/rateLimiter.js
similarity index 100%
rename from middleware/rateLimiter.js
rename to server/middleware/rateLimiter.js
diff --git a/middleware/rbac.js b/server/middleware/rbac.js
similarity index 100%
rename from middleware/rbac.js
rename to server/middleware/rbac.js
diff --git a/middleware/recurringValidator.js b/server/middleware/recurringValidator.js
similarity index 100%
rename from middleware/recurringValidator.js
rename to server/middleware/recurringValidator.js
diff --git a/middleware/roleCheck.js b/server/middleware/roleCheck.js
similarity index 100%
rename from middleware/roleCheck.js
rename to server/middleware/roleCheck.js
diff --git a/middleware/sanitization.js b/server/middleware/sanitization.js
similarity index 100%
rename from middleware/sanitization.js
rename to server/middleware/sanitization.js
diff --git a/middleware/sanitizer.js b/server/middleware/sanitizer.js
similarity index 100%
rename from middleware/sanitizer.js
rename to server/middleware/sanitizer.js
diff --git a/middleware/sharedSpaceValidator.js b/server/middleware/sharedSpaceValidator.js
similarity index 100%
rename from middleware/sharedSpaceValidator.js
rename to server/middleware/sharedSpaceValidator.js
diff --git a/middleware/socketAuth.js b/server/middleware/socketAuth.js
similarity index 100%
rename from middleware/socketAuth.js
rename to server/middleware/socketAuth.js
diff --git a/middleware/spaceAuth.js b/server/middleware/spaceAuth.js
similarity index 100%
rename from middleware/spaceAuth.js
rename to server/middleware/spaceAuth.js
diff --git a/middleware/splitValidator.js b/server/middleware/splitValidator.js
similarity index 100%
rename from middleware/splitValidator.js
rename to server/middleware/splitValidator.js
diff --git a/middleware/subscriptionValidator.js b/server/middleware/subscriptionValidator.js
similarity index 100%
rename from middleware/subscriptionValidator.js
rename to server/middleware/subscriptionValidator.js
diff --git a/middleware/taxValidator.js b/server/middleware/taxValidator.js
similarity index 100%
rename from middleware/taxValidator.js
rename to server/middleware/taxValidator.js
diff --git a/middleware/twoFactorAuthMiddleware.js b/server/middleware/twoFactorAuthMiddleware.js
similarity index 100%
rename from middleware/twoFactorAuthMiddleware.js
rename to server/middleware/twoFactorAuthMiddleware.js
diff --git a/middleware/uploadMiddleware.js b/server/middleware/uploadMiddleware.js
similarity index 100%
rename from middleware/uploadMiddleware.js
rename to server/middleware/uploadMiddleware.js
diff --git a/models/AIPrediction.js b/server/models/AIPrediction.js
similarity index 100%
rename from models/AIPrediction.js
rename to server/models/AIPrediction.js
diff --git a/models/AITrainingData.js b/server/models/AITrainingData.js
similarity index 100%
rename from models/AITrainingData.js
rename to server/models/AITrainingData.js
diff --git a/models/Account.js b/server/models/Account.js
similarity index 100%
rename from models/Account.js
rename to server/models/Account.js
diff --git a/models/AccountingConnection.js b/server/models/AccountingConnection.js
similarity index 100%
rename from models/AccountingConnection.js
rename to server/models/AccountingConnection.js
diff --git a/models/Achievement.js b/server/models/Achievement.js
similarity index 100%
rename from models/Achievement.js
rename to server/models/Achievement.js
diff --git a/models/AmortizationSchedule.js b/server/models/AmortizationSchedule.js
similarity index 100%
rename from models/AmortizationSchedule.js
rename to server/models/AmortizationSchedule.js
diff --git a/models/AnalyticsCache.js b/server/models/AnalyticsCache.js
similarity index 100%
rename from models/AnalyticsCache.js
rename to server/models/AnalyticsCache.js
diff --git a/models/AnomalyEvent.js b/server/models/AnomalyEvent.js
similarity index 100%
rename from models/AnomalyEvent.js
rename to server/models/AnomalyEvent.js
diff --git a/models/AnomalyRule.js b/server/models/AnomalyRule.js
similarity index 100%
rename from models/AnomalyRule.js
rename to server/models/AnomalyRule.js
diff --git a/models/ApprovalRequest.js b/server/models/ApprovalRequest.js
similarity index 100%
rename from models/ApprovalRequest.js
rename to server/models/ApprovalRequest.js
diff --git a/models/ApprovalWorkflow.js b/server/models/ApprovalWorkflow.js
similarity index 100%
rename from models/ApprovalWorkflow.js
rename to server/models/ApprovalWorkflow.js
diff --git a/models/Asset.js b/server/models/Asset.js
similarity index 100%
rename from models/Asset.js
rename to server/models/Asset.js
diff --git a/models/AssetDepreciation.js b/server/models/AssetDepreciation.js
similarity index 100%
rename from models/AssetDepreciation.js
rename to server/models/AssetDepreciation.js
diff --git a/models/AssetTransaction.js b/server/models/AssetTransaction.js
similarity index 100%
rename from models/AssetTransaction.js
rename to server/models/AssetTransaction.js
diff --git a/models/AuditLog.js b/server/models/AuditLog.js
similarity index 100%
rename from models/AuditLog.js
rename to server/models/AuditLog.js
diff --git a/models/BackOrder.js b/server/models/BackOrder.js
similarity index 100%
rename from models/BackOrder.js
rename to server/models/BackOrder.js
diff --git a/models/BalanceHistory.js b/server/models/BalanceHistory.js
similarity index 100%
rename from models/BalanceHistory.js
rename to server/models/BalanceHistory.js
diff --git a/models/BankConnection.js b/server/models/BankConnection.js
similarity index 100%
rename from models/BankConnection.js
rename to server/models/BankConnection.js
diff --git a/models/BankInstitution.js b/server/models/BankInstitution.js
similarity index 100%
rename from models/BankInstitution.js
rename to server/models/BankInstitution.js
diff --git a/models/BankLink.js b/server/models/BankLink.js
similarity index 100%
rename from models/BankLink.js
rename to server/models/BankLink.js
diff --git a/models/Bill.js b/server/models/Bill.js
similarity index 100%
rename from models/Bill.js
rename to server/models/Bill.js
diff --git a/models/BillPayment.js b/server/models/BillPayment.js
similarity index 100%
rename from models/BillPayment.js
rename to server/models/BillPayment.js
diff --git a/models/BlockedEntity.js b/server/models/BlockedEntity.js
similarity index 100%
rename from models/BlockedEntity.js
rename to server/models/BlockedEntity.js
diff --git a/models/Budget.js b/server/models/Budget.js
similarity index 100%
rename from models/Budget.js
rename to server/models/Budget.js
diff --git a/models/BudgetForecast.js b/server/models/BudgetForecast.js
similarity index 100%
rename from models/BudgetForecast.js
rename to server/models/BudgetForecast.js
diff --git a/models/BudgetSnapshot.js b/server/models/BudgetSnapshot.js
similarity index 100%
rename from models/BudgetSnapshot.js
rename to server/models/BudgetSnapshot.js
diff --git a/models/CalendarEvent.js b/server/models/CalendarEvent.js
similarity index 100%
rename from models/CalendarEvent.js
rename to server/models/CalendarEvent.js
diff --git a/models/CashFlowForecast.js b/server/models/CashFlowForecast.js
similarity index 100%
rename from models/CashFlowForecast.js
rename to server/models/CashFlowForecast.js
diff --git a/models/CategoryPattern.js b/server/models/CategoryPattern.js
similarity index 100%
rename from models/CategoryPattern.js
rename to server/models/CategoryPattern.js
diff --git a/models/CategoryRule.js b/server/models/CategoryRule.js
similarity index 100%
rename from models/CategoryRule.js
rename to server/models/CategoryRule.js
diff --git a/models/CategoryTraining.js b/server/models/CategoryTraining.js
similarity index 100%
rename from models/CategoryTraining.js
rename to server/models/CategoryTraining.js
diff --git a/models/Challenge.js b/server/models/Challenge.js
similarity index 100%
rename from models/Challenge.js
rename to server/models/Challenge.js
diff --git a/models/ChallengeParticipant.js b/server/models/ChallengeParticipant.js
similarity index 100%
rename from models/ChallengeParticipant.js
rename to server/models/ChallengeParticipant.js
diff --git a/models/Chat.js b/server/models/Chat.js
similarity index 100%
rename from models/Chat.js
rename to server/models/Chat.js
diff --git a/models/Client.js b/server/models/Client.js
similarity index 100%
rename from models/Client.js
rename to server/models/Client.js
diff --git a/models/ComplianceFramework.js b/server/models/ComplianceFramework.js
similarity index 100%
rename from models/ComplianceFramework.js
rename to server/models/ComplianceFramework.js
diff --git a/models/ComplianceRule.js b/server/models/ComplianceRule.js
similarity index 100%
rename from models/ComplianceRule.js
rename to server/models/ComplianceRule.js
diff --git a/models/ComplianceViolation.js b/server/models/ComplianceViolation.js
similarity index 100%
rename from models/ComplianceViolation.js
rename to server/models/ComplianceViolation.js
diff --git a/models/Currency.js b/server/models/Currency.js
similarity index 100%
rename from models/Currency.js
rename to server/models/Currency.js
diff --git a/models/CurrencyRate.js b/server/models/CurrencyRate.js
similarity index 100%
rename from models/CurrencyRate.js
rename to server/models/CurrencyRate.js
diff --git a/models/CustomDashboard.js b/server/models/CustomDashboard.js
similarity index 100%
rename from models/CustomDashboard.js
rename to server/models/CustomDashboard.js
diff --git a/models/DataWarehouse.js b/server/models/DataWarehouse.js
similarity index 100%
rename from models/DataWarehouse.js
rename to server/models/DataWarehouse.js
diff --git a/models/DebtAccount.js b/server/models/DebtAccount.js
similarity index 100%
rename from models/DebtAccount.js
rename to server/models/DebtAccount.js
diff --git a/models/Deduction.js b/server/models/Deduction.js
similarity index 100%
rename from models/Deduction.js
rename to server/models/Deduction.js
diff --git a/models/DeviceFingerprint.js b/server/models/DeviceFingerprint.js
similarity index 100%
rename from models/DeviceFingerprint.js
rename to server/models/DeviceFingerprint.js
diff --git a/models/DocumentFolder.js b/server/models/DocumentFolder.js
similarity index 100%
rename from models/DocumentFolder.js
rename to server/models/DocumentFolder.js
diff --git a/models/EmployeePerk.js b/server/models/EmployeePerk.js
similarity index 100%
rename from models/EmployeePerk.js
rename to server/models/EmployeePerk.js
diff --git a/models/ExchangeHedge.js b/server/models/ExchangeHedge.js
similarity index 100%
rename from models/ExchangeHedge.js
rename to server/models/ExchangeHedge.js
diff --git a/models/Expense.js b/server/models/Expense.js
similarity index 100%
rename from models/Expense.js
rename to server/models/Expense.js
diff --git a/models/ExpenseSplit.js b/server/models/ExpenseSplit.js
similarity index 100%
rename from models/ExpenseSplit.js
rename to server/models/ExpenseSplit.js
diff --git a/models/ExpenseSubmission.js b/server/models/ExpenseSubmission.js
similarity index 100%
rename from models/ExpenseSubmission.js
rename to server/models/ExpenseSubmission.js
diff --git a/models/FinancialAlert.js b/server/models/FinancialAlert.js
similarity index 100%
rename from models/FinancialAlert.js
rename to server/models/FinancialAlert.js
diff --git a/models/FinancialHealthScore.js b/server/models/FinancialHealthScore.js
similarity index 100%
rename from models/FinancialHealthScore.js
rename to server/models/FinancialHealthScore.js
diff --git a/models/FinancialInsight.js b/server/models/FinancialInsight.js
similarity index 100%
rename from models/FinancialInsight.js
rename to server/models/FinancialInsight.js
diff --git a/models/FinancialReport.js b/server/models/FinancialReport.js
similarity index 100%
rename from models/FinancialReport.js
rename to server/models/FinancialReport.js
diff --git a/models/FixedAsset.js b/server/models/FixedAsset.js
similarity index 100%
rename from models/FixedAsset.js
rename to server/models/FixedAsset.js
diff --git a/models/Forecast.js b/server/models/Forecast.js
similarity index 100%
rename from models/Forecast.js
rename to server/models/Forecast.js
diff --git a/models/ForecastScenario.js b/server/models/ForecastScenario.js
similarity index 100%
rename from models/ForecastScenario.js
rename to server/models/ForecastScenario.js
diff --git a/models/ForecastSnapshot.js b/server/models/ForecastSnapshot.js
similarity index 100%
rename from models/ForecastSnapshot.js
rename to server/models/ForecastSnapshot.js
diff --git a/models/FraudDetection.js b/server/models/FraudDetection.js
similarity index 100%
rename from models/FraudDetection.js
rename to server/models/FraudDetection.js
diff --git a/models/Goal.js b/server/models/Goal.js
similarity index 100%
rename from models/Goal.js
rename to server/models/Goal.js
diff --git a/models/Group.js b/server/models/Group.js
similarity index 100%
rename from models/Group.js
rename to server/models/Group.js
diff --git a/models/GroupInvite.js b/server/models/GroupInvite.js
similarity index 100%
rename from models/GroupInvite.js
rename to server/models/GroupInvite.js
diff --git a/models/ImmutableAuditLog.js b/server/models/ImmutableAuditLog.js
similarity index 100%
rename from models/ImmutableAuditLog.js
rename to server/models/ImmutableAuditLog.js
diff --git a/models/ImportedTransaction.js b/server/models/ImportedTransaction.js
similarity index 100%
rename from models/ImportedTransaction.js
rename to server/models/ImportedTransaction.js
diff --git a/models/IncomeSource.js b/server/models/IncomeSource.js
similarity index 100%
rename from models/IncomeSource.js
rename to server/models/IncomeSource.js
diff --git a/models/Insight.js b/server/models/Insight.js
similarity index 100%
rename from models/Insight.js
rename to server/models/Insight.js
diff --git a/models/Integration.js b/server/models/Integration.js
similarity index 100%
rename from models/Integration.js
rename to server/models/Integration.js
diff --git a/models/Investment.js b/server/models/Investment.js
similarity index 100%
rename from models/Investment.js
rename to server/models/Investment.js
diff --git a/models/Invoice.js b/server/models/Invoice.js
similarity index 100%
rename from models/Invoice.js
rename to server/models/Invoice.js
diff --git a/models/LinkedAccount.js b/server/models/LinkedAccount.js
similarity index 100%
rename from models/LinkedAccount.js
rename to server/models/LinkedAccount.js
diff --git a/models/LiquidityThreshold.js b/server/models/LiquidityThreshold.js
similarity index 100%
rename from models/LiquidityThreshold.js
rename to server/models/LiquidityThreshold.js
diff --git a/models/MerchantDatabase.js b/server/models/MerchantDatabase.js
similarity index 100%
rename from models/MerchantDatabase.js
rename to server/models/MerchantDatabase.js
diff --git a/models/MultiCurrencyWallet.js b/server/models/MultiCurrencyWallet.js
similarity index 100%
rename from models/MultiCurrencyWallet.js
rename to server/models/MultiCurrencyWallet.js
diff --git a/models/NetWorthSnapshot.js b/server/models/NetWorthSnapshot.js
similarity index 100%
rename from models/NetWorthSnapshot.js
rename to server/models/NetWorthSnapshot.js
diff --git a/models/Notification.js b/server/models/Notification.js
similarity index 100%
rename from models/Notification.js
rename to server/models/Notification.js
diff --git a/models/Pattern.js b/server/models/Pattern.js
similarity index 100%
rename from models/Pattern.js
rename to server/models/Pattern.js
diff --git a/models/Payment.js b/server/models/Payment.js
similarity index 100%
rename from models/Payment.js
rename to server/models/Payment.js
diff --git a/models/PayrollRun.js b/server/models/PayrollRun.js
similarity index 100%
rename from models/PayrollRun.js
rename to server/models/PayrollRun.js
diff --git a/models/Policy.js b/server/models/Policy.js
similarity index 100%
rename from models/Policy.js
rename to server/models/Policy.js
diff --git a/models/Portfolio.js b/server/models/Portfolio.js
similarity index 100%
rename from models/Portfolio.js
rename to server/models/Portfolio.js
diff --git a/models/PriceHistory.js b/server/models/PriceHistory.js
similarity index 100%
rename from models/PriceHistory.js
rename to server/models/PriceHistory.js
diff --git a/models/ProcurementOrder.js b/server/models/ProcurementOrder.js
similarity index 100%
rename from models/ProcurementOrder.js
rename to server/models/ProcurementOrder.js
diff --git a/models/Project.js b/server/models/Project.js
similarity index 100%
rename from models/Project.js
rename to server/models/Project.js
diff --git a/models/ProjectInvoice.js b/server/models/ProjectInvoice.js
similarity index 100%
rename from models/ProjectInvoice.js
rename to server/models/ProjectInvoice.js
diff --git a/models/Receipt.js b/server/models/Receipt.js
similarity index 100%
rename from models/Receipt.js
rename to server/models/Receipt.js
diff --git a/models/ReceiptDocument.js b/server/models/ReceiptDocument.js
similarity index 100%
rename from models/ReceiptDocument.js
rename to server/models/ReceiptDocument.js
diff --git a/models/ReconciliationRule.js b/server/models/ReconciliationRule.js
similarity index 100%
rename from models/ReconciliationRule.js
rename to server/models/ReconciliationRule.js
diff --git a/models/RecurringExpense.js b/server/models/RecurringExpense.js
similarity index 100%
rename from models/RecurringExpense.js
rename to server/models/RecurringExpense.js
diff --git a/models/ReminderSchedule.js b/server/models/ReminderSchedule.js
similarity index 100%
rename from models/ReminderSchedule.js
rename to server/models/ReminderSchedule.js
diff --git a/models/RiskScore.js b/server/models/RiskScore.js
similarity index 100%
rename from models/RiskScore.js
rename to server/models/RiskScore.js
diff --git a/models/Rule.js b/server/models/Rule.js
similarity index 100%
rename from models/Rule.js
rename to server/models/Rule.js
diff --git a/models/SalaryStructure.js b/server/models/SalaryStructure.js
similarity index 100%
rename from models/SalaryStructure.js
rename to server/models/SalaryStructure.js
diff --git a/models/SeasonalPattern.js b/server/models/SeasonalPattern.js
similarity index 100%
rename from models/SeasonalPattern.js
rename to server/models/SeasonalPattern.js
diff --git a/models/SecurityEvent.js b/server/models/SecurityEvent.js
similarity index 100%
rename from models/SecurityEvent.js
rename to server/models/SecurityEvent.js
diff --git a/models/Session.js b/server/models/Session.js
similarity index 100%
rename from models/Session.js
rename to server/models/Session.js
diff --git a/models/Settlement.js b/server/models/Settlement.js
similarity index 100%
rename from models/Settlement.js
rename to server/models/Settlement.js
diff --git a/models/SharedGoal.js b/server/models/SharedGoal.js
similarity index 100%
rename from models/SharedGoal.js
rename to server/models/SharedGoal.js
diff --git a/models/SharedSpace.js b/server/models/SharedSpace.js
similarity index 100%
rename from models/SharedSpace.js
rename to server/models/SharedSpace.js
diff --git a/models/SpaceActivity.js b/server/models/SpaceActivity.js
similarity index 100%
rename from models/SpaceActivity.js
rename to server/models/SpaceActivity.js
diff --git a/models/SpendingAnomaly.js b/server/models/SpendingAnomaly.js
similarity index 100%
rename from models/SpendingAnomaly.js
rename to server/models/SpendingAnomaly.js
diff --git a/models/SpendingPattern.js b/server/models/SpendingPattern.js
similarity index 100%
rename from models/SpendingPattern.js
rename to server/models/SpendingPattern.js
diff --git a/models/SplitExpense.js b/server/models/SplitExpense.js
similarity index 100%
rename from models/SplitExpense.js
rename to server/models/SplitExpense.js
diff --git a/models/SplitGroup.js b/server/models/SplitGroup.js
similarity index 100%
rename from models/SplitGroup.js
rename to server/models/SplitGroup.js
diff --git a/models/StockItem.js b/server/models/StockItem.js
similarity index 100%
rename from models/StockItem.js
rename to server/models/StockItem.js
diff --git a/models/Subscription.js b/server/models/Subscription.js
similarity index 100%
rename from models/Subscription.js
rename to server/models/Subscription.js
diff --git a/models/SyncLog.js b/server/models/SyncLog.js
similarity index 100%
rename from models/SyncLog.js
rename to server/models/SyncLog.js
diff --git a/models/SyncQueue.js b/server/models/SyncQueue.js
similarity index 100%
rename from models/SyncQueue.js
rename to server/models/SyncQueue.js
diff --git a/models/Tag.js b/server/models/Tag.js
similarity index 100%
rename from models/Tag.js
rename to server/models/Tag.js
diff --git a/models/TaxAuditPack.js b/server/models/TaxAuditPack.js
similarity index 100%
rename from models/TaxAuditPack.js
rename to server/models/TaxAuditPack.js
diff --git a/models/TaxCategory.js b/server/models/TaxCategory.js
similarity index 100%
rename from models/TaxCategory.js
rename to server/models/TaxCategory.js
diff --git a/models/TaxConfig.js b/server/models/TaxConfig.js
similarity index 100%
rename from models/TaxConfig.js
rename to server/models/TaxConfig.js
diff --git a/models/TaxDocument.js b/server/models/TaxDocument.js
similarity index 100%
rename from models/TaxDocument.js
rename to server/models/TaxDocument.js
diff --git a/models/TaxEstimate.js b/server/models/TaxEstimate.js
similarity index 100%
rename from models/TaxEstimate.js
rename to server/models/TaxEstimate.js
diff --git a/models/TaxProfile.js b/server/models/TaxProfile.js
similarity index 100%
rename from models/TaxProfile.js
rename to server/models/TaxProfile.js
diff --git a/models/TaxRule.js b/server/models/TaxRule.js
similarity index 100%
rename from models/TaxRule.js
rename to server/models/TaxRule.js
diff --git a/models/Team.js b/server/models/Team.js
similarity index 100%
rename from models/Team.js
rename to server/models/Team.js
diff --git a/models/TimeEntry.js b/server/models/TimeEntry.js
similarity index 100%
rename from models/TimeEntry.js
rename to server/models/TimeEntry.js
diff --git a/models/Transaction.js b/server/models/Transaction.js
similarity index 100%
rename from models/Transaction.js
rename to server/models/Transaction.js
diff --git a/models/Transfer.js b/server/models/Transfer.js
similarity index 100%
rename from models/Transfer.js
rename to server/models/Transfer.js
diff --git a/models/TreasuryVault.js b/server/models/TreasuryVault.js
similarity index 100%
rename from models/TreasuryVault.js
rename to server/models/TreasuryVault.js
diff --git a/models/TrustedDevice.js b/server/models/TrustedDevice.js
similarity index 100%
rename from models/TrustedDevice.js
rename to server/models/TrustedDevice.js
diff --git a/models/TwoFactorAuth.js b/server/models/TwoFactorAuth.js
similarity index 100%
rename from models/TwoFactorAuth.js
rename to server/models/TwoFactorAuth.js
diff --git a/models/User.js b/server/models/User.js
similarity index 100%
rename from models/User.js
rename to server/models/User.js
diff --git a/models/UserAchievement.js b/server/models/UserAchievement.js
similarity index 100%
rename from models/UserAchievement.js
rename to server/models/UserAchievement.js
diff --git a/models/UserBehaviorProfile.js b/server/models/UserBehaviorProfile.js
similarity index 100%
rename from models/UserBehaviorProfile.js
rename to server/models/UserBehaviorProfile.js
diff --git a/models/UserGamification.js b/server/models/UserGamification.js
similarity index 100%
rename from models/UserGamification.js
rename to server/models/UserGamification.js
diff --git a/models/Vendor.js b/server/models/Vendor.js
similarity index 100%
rename from models/Vendor.js
rename to server/models/Vendor.js
diff --git a/models/Warehouse.js b/server/models/Warehouse.js
similarity index 100%
rename from models/Warehouse.js
rename to server/models/Warehouse.js
diff --git a/models/Webhook.js b/server/models/Webhook.js
similarity index 100%
rename from models/Webhook.js
rename to server/models/Webhook.js
diff --git a/models/Workspace.js b/server/models/Workspace.js
similarity index 100%
rename from models/Workspace.js
rename to server/models/Workspace.js
diff --git a/models/WorkspaceInvite.js b/server/models/WorkspaceInvite.js
similarity index 100%
rename from models/WorkspaceInvite.js
rename to server/models/WorkspaceInvite.js
diff --git a/netlify/functions/api.js b/server/netlify/functions/api.js
similarity index 100%
rename from netlify/functions/api.js
rename to server/netlify/functions/api.js
diff --git a/package-lock.json b/server/package-lock.json
similarity index 100%
rename from package-lock.json
rename to server/package-lock.json
diff --git a/package.json b/server/package.json
similarity index 100%
rename from package.json
rename to server/package.json
diff --git a/repositories/baseRepository.js b/server/repositories/baseRepository.js
similarity index 100%
rename from repositories/baseRepository.js
rename to server/repositories/baseRepository.js
diff --git a/repositories/budgetRepository.js b/server/repositories/budgetRepository.js
similarity index 100%
rename from repositories/budgetRepository.js
rename to server/repositories/budgetRepository.js
diff --git a/repositories/expenseRepository.js b/server/repositories/expenseRepository.js
similarity index 100%
rename from repositories/expenseRepository.js
rename to server/repositories/expenseRepository.js
diff --git a/repositories/goalRepository.js b/server/repositories/goalRepository.js
similarity index 100%
rename from repositories/goalRepository.js
rename to server/repositories/goalRepository.js
diff --git a/repositories/userRepository.js b/server/repositories/userRepository.js
similarity index 100%
rename from repositories/userRepository.js
rename to server/repositories/userRepository.js
diff --git a/routes/accounting.js b/server/routes/accounting.js
similarity index 100%
rename from routes/accounting.js
rename to server/routes/accounting.js
diff --git a/routes/accounts.js b/server/routes/accounts.js
similarity index 100%
rename from routes/accounts.js
rename to server/routes/accounts.js
diff --git a/routes/ai.js b/server/routes/ai.js
similarity index 100%
rename from routes/ai.js
rename to server/routes/ai.js
diff --git a/routes/analytics.js b/server/routes/analytics.js
similarity index 100%
rename from routes/analytics.js
rename to server/routes/analytics.js
diff --git a/routes/approvals.js b/server/routes/approvals.js
similarity index 100%
rename from routes/approvals.js
rename to server/routes/approvals.js
diff --git a/routes/audit.js b/server/routes/audit.js
similarity index 100%
rename from routes/audit.js
rename to server/routes/audit.js
diff --git a/routes/auditCompliance.js b/server/routes/auditCompliance.js
similarity index 100%
rename from routes/auditCompliance.js
rename to server/routes/auditCompliance.js
diff --git a/routes/auth.js b/server/routes/auth.js
similarity index 100%
rename from routes/auth.js
rename to server/routes/auth.js
diff --git a/routes/backups.js b/server/routes/backups.js
similarity index 100%
rename from routes/backups.js
rename to server/routes/backups.js
diff --git a/routes/bills.js b/server/routes/bills.js
similarity index 100%
rename from routes/bills.js
rename to server/routes/bills.js
diff --git a/routes/budgets.js b/server/routes/budgets.js
similarity index 100%
rename from routes/budgets.js
rename to server/routes/budgets.js
diff --git a/routes/calendar.js b/server/routes/calendar.js
similarity index 100%
rename from routes/calendar.js
rename to server/routes/calendar.js
diff --git a/routes/categorization.js b/server/routes/categorization.js
similarity index 100%
rename from routes/categorization.js
rename to server/routes/categorization.js
diff --git a/routes/chat.js b/server/routes/chat.js
similarity index 100%
rename from routes/chat.js
rename to server/routes/chat.js
diff --git a/routes/clients.js b/server/routes/clients.js
similarity index 100%
rename from routes/clients.js
rename to server/routes/clients.js
diff --git a/routes/collaboration.js b/server/routes/collaboration.js
similarity index 100%
rename from routes/collaboration.js
rename to server/routes/collaboration.js
diff --git a/routes/compliance.js b/server/routes/compliance.js
similarity index 100%
rename from routes/compliance.js
rename to server/routes/compliance.js
diff --git a/routes/contact.js b/server/routes/contact.js
similarity index 100%
rename from routes/contact.js
rename to server/routes/contact.js
diff --git a/routes/currency.js b/server/routes/currency.js
similarity index 100%
rename from routes/currency.js
rename to server/routes/currency.js
diff --git a/routes/debt.js b/server/routes/debt.js
similarity index 100%
rename from routes/debt.js
rename to server/routes/debt.js
diff --git a/routes/expenses.js b/server/routes/expenses.js
similarity index 100%
rename from routes/expenses.js
rename to server/routes/expenses.js
diff --git a/routes/export.js b/server/routes/export.js
similarity index 100%
rename from routes/export.js
rename to server/routes/export.js
diff --git a/routes/folders.js b/server/routes/folders.js
similarity index 100%
rename from routes/folders.js
rename to server/routes/folders.js
diff --git a/routes/forecast.js b/server/routes/forecast.js
similarity index 100%
rename from routes/forecast.js
rename to server/routes/forecast.js
diff --git a/routes/forecasting.js b/server/routes/forecasting.js
similarity index 100%
rename from routes/forecasting.js
rename to server/routes/forecasting.js
diff --git a/routes/fraudDetection.js b/server/routes/fraudDetection.js
similarity index 100%
rename from routes/fraudDetection.js
rename to server/routes/fraudDetection.js
diff --git a/routes/gamification.js b/server/routes/gamification.js
similarity index 100%
rename from routes/gamification.js
rename to server/routes/gamification.js
diff --git a/routes/goals.js b/server/routes/goals.js
similarity index 100%
rename from routes/goals.js
rename to server/routes/goals.js
diff --git a/routes/groups.js b/server/routes/groups.js
similarity index 100%
rename from routes/groups.js
rename to server/routes/groups.js
diff --git a/routes/insights.js b/server/routes/insights.js
similarity index 100%
rename from routes/insights.js
rename to server/routes/insights.js
diff --git a/routes/integrations.js b/server/routes/integrations.js
similarity index 100%
rename from routes/integrations.js
rename to server/routes/integrations.js
diff --git a/routes/inventory.js b/server/routes/inventory.js
similarity index 100%
rename from routes/inventory.js
rename to server/routes/inventory.js
diff --git a/routes/investments.js b/server/routes/investments.js
similarity index 100%
rename from routes/investments.js
rename to server/routes/investments.js
diff --git a/routes/invoices.js b/server/routes/invoices.js
similarity index 100%
rename from routes/invoices.js
rename to server/routes/invoices.js
diff --git a/routes/multicurrency.js b/server/routes/multicurrency.js
similarity index 100%
rename from routes/multicurrency.js
rename to server/routes/multicurrency.js
diff --git a/routes/notifications.js b/server/routes/notifications.js
similarity index 100%
rename from routes/notifications.js
rename to server/routes/notifications.js
diff --git a/routes/openBanking.js b/server/routes/openBanking.js
similarity index 100%
rename from routes/openBanking.js
rename to server/routes/openBanking.js
diff --git a/routes/payments.js b/server/routes/payments.js
similarity index 100%
rename from routes/payments.js
rename to server/routes/payments.js
diff --git a/routes/payroll.js b/server/routes/payroll.js
similarity index 100%
rename from routes/payroll.js
rename to server/routes/payroll.js
diff --git a/routes/portfolios.js b/server/routes/portfolios.js
similarity index 100%
rename from routes/portfolios.js
rename to server/routes/portfolios.js
diff --git a/routes/procurement.js b/server/routes/procurement.js
similarity index 100%
rename from routes/procurement.js
rename to server/routes/procurement.js
diff --git a/routes/project-billing.js b/server/routes/project-billing.js
similarity index 100%
rename from routes/project-billing.js
rename to server/routes/project-billing.js
diff --git a/routes/receipts.js b/server/routes/receipts.js
similarity index 100%
rename from routes/receipts.js
rename to server/routes/receipts.js
diff --git a/routes/recurring.js b/server/routes/recurring.js
similarity index 100%
rename from routes/recurring.js
rename to server/routes/recurring.js
diff --git a/routes/reminders.js b/server/routes/reminders.js
similarity index 100%
rename from routes/reminders.js
rename to server/routes/reminders.js
diff --git a/routes/reports.js b/server/routes/reports.js
similarity index 100%
rename from routes/reports.js
rename to server/routes/reports.js
diff --git a/routes/rules.js b/server/routes/rules.js
similarity index 100%
rename from routes/rules.js
rename to server/routes/rules.js
diff --git a/routes/security.js b/server/routes/security.js
similarity index 100%
rename from routes/security.js
rename to server/routes/security.js
diff --git a/routes/sharedSpaces.js b/server/routes/sharedSpaces.js
similarity index 100%
rename from routes/sharedSpaces.js
rename to server/routes/sharedSpaces.js
diff --git a/routes/splits.js b/server/routes/splits.js
similarity index 100%
rename from routes/splits.js
rename to server/routes/splits.js
diff --git a/routes/subscriptions.js b/server/routes/subscriptions.js
similarity index 100%
rename from routes/subscriptions.js
rename to server/routes/subscriptions.js
diff --git a/routes/sync.js b/server/routes/sync.js
similarity index 100%
rename from routes/sync.js
rename to server/routes/sync.js
diff --git a/routes/tags.js b/server/routes/tags.js
similarity index 100%
rename from routes/tags.js
rename to server/routes/tags.js
diff --git a/routes/tax.js b/server/routes/tax.js
similarity index 100%
rename from routes/tax.js
rename to server/routes/tax.js
diff --git a/routes/teams.js b/server/routes/teams.js
similarity index 100%
rename from routes/teams.js
rename to server/routes/teams.js
diff --git a/routes/transactions.js b/server/routes/transactions.js
similarity index 100%
rename from routes/transactions.js
rename to server/routes/transactions.js
diff --git a/routes/treasury.js b/server/routes/treasury.js
similarity index 100%
rename from routes/treasury.js
rename to server/routes/treasury.js
diff --git a/routes/twoFactorAuth.js b/server/routes/twoFactorAuth.js
similarity index 100%
rename from routes/twoFactorAuth.js
rename to server/routes/twoFactorAuth.js
diff --git a/routes/user.js b/server/routes/user.js
similarity index 100%
rename from routes/user.js
rename to server/routes/user.js
diff --git a/routes/workspaces.js b/server/routes/workspaces.js
similarity index 100%
rename from routes/workspaces.js
rename to server/routes/workspaces.js
diff --git a/server.js b/server/server.js
similarity index 97%
rename from server.js
rename to server/server.js
index 0db54b9e..36e21444 100644
--- a/server.js
+++ b/server/server.js
@@ -160,8 +160,9 @@ app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Static files
-app.use(express.static('public'));
-app.use(express.static('.'));
+// Static files served by Nginx in production/docker
+// app.use(express.static('public'));
+// app.use(express.static('.'));
// Security logging middleware
app.use((req, res, next) => {
@@ -303,9 +304,10 @@ app.use(notFoundHandler);
app.use(errorHandler);
// Root route to serve the UI
-app.get('/', (req, res) => {
- res.sendFile(require('path').join(__dirname, 'public', 'index.html'));
-});
+// Root route served by Nginx
+// app.get('/', (req, res) => {
+// res.sendFile(require('path').join(__dirname, 'public', 'index.html'));
+// });
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
diff --git a/services/accountTakeoverAlertingService.js b/server/services/accountTakeoverAlertingService.js
similarity index 100%
rename from services/accountTakeoverAlertingService.js
rename to server/services/accountTakeoverAlertingService.js
diff --git a/services/accountingService.js b/server/services/accountingService.js
similarity index 100%
rename from services/accountingService.js
rename to server/services/accountingService.js
diff --git a/services/advancedAnalyticsService.js b/server/services/advancedAnalyticsService.js
similarity index 100%
rename from services/advancedAnalyticsService.js
rename to server/services/advancedAnalyticsService.js
diff --git a/services/aiInsightsService.js b/server/services/aiInsightsService.js
similarity index 100%
rename from services/aiInsightsService.js
rename to server/services/aiInsightsService.js
diff --git a/services/aiService.js b/server/services/aiService.js
similarity index 100%
rename from services/aiService.js
rename to server/services/aiService.js
diff --git a/services/alertService.js b/server/services/alertService.js
similarity index 100%
rename from services/alertService.js
rename to server/services/alertService.js
diff --git a/services/analysisEngine.js b/server/services/analysisEngine.js
similarity index 100%
rename from services/analysisEngine.js
rename to server/services/analysisEngine.js
diff --git a/services/analyticsService.js b/server/services/analyticsService.js
similarity index 100%
rename from services/analyticsService.js
rename to server/services/analyticsService.js
diff --git a/services/anomalyDetectionService.js b/server/services/anomalyDetectionService.js
similarity index 100%
rename from services/anomalyDetectionService.js
rename to server/services/anomalyDetectionService.js
diff --git a/services/approvalService.js b/server/services/approvalService.js
similarity index 100%
rename from services/approvalService.js
rename to server/services/approvalService.js
diff --git a/services/assetService.js b/server/services/assetService.js
similarity index 100%
rename from services/assetService.js
rename to server/services/assetService.js
diff --git a/services/auditComplianceService.js b/server/services/auditComplianceService.js
similarity index 100%
rename from services/auditComplianceService.js
rename to server/services/auditComplianceService.js
diff --git a/services/auditService.js b/server/services/auditService.js
similarity index 100%
rename from services/auditService.js
rename to server/services/auditService.js
diff --git a/services/backupRecoveryService.js b/server/services/backupRecoveryService.js
similarity index 100%
rename from services/backupRecoveryService.js
rename to server/services/backupRecoveryService.js
diff --git a/services/backupService.js b/server/services/backupService.js
similarity index 100%
rename from services/backupService.js
rename to server/services/backupService.js
diff --git a/services/billReminderService.js b/server/services/billReminderService.js
similarity index 100%
rename from services/billReminderService.js
rename to server/services/billReminderService.js
diff --git a/services/billService.js b/server/services/billService.js
similarity index 100%
rename from services/billService.js
rename to server/services/billService.js
diff --git a/services/budgetAlertService.js b/server/services/budgetAlertService.js
similarity index 100%
rename from services/budgetAlertService.js
rename to server/services/budgetAlertService.js
diff --git a/services/budgetForecastingService.js b/server/services/budgetForecastingService.js
similarity index 100%
rename from services/budgetForecastingService.js
rename to server/services/budgetForecastingService.js
diff --git a/services/budgetIntelligenceService.js b/server/services/budgetIntelligenceService.js
similarity index 100%
rename from services/budgetIntelligenceService.js
rename to server/services/budgetIntelligenceService.js
diff --git a/services/budgetService.js b/server/services/budgetService.js
similarity index 100%
rename from services/budgetService.js
rename to server/services/budgetService.js
diff --git a/services/calendarService.js b/server/services/calendarService.js
similarity index 100%
rename from services/calendarService.js
rename to server/services/calendarService.js
diff --git a/services/cashFlowForecastService.js b/server/services/cashFlowForecastService.js
similarity index 100%
rename from services/cashFlowForecastService.js
rename to server/services/cashFlowForecastService.js
diff --git a/services/categorizationEngine.js b/server/services/categorizationEngine.js
similarity index 100%
rename from services/categorizationEngine.js
rename to server/services/categorizationEngine.js
diff --git a/services/categorizationService.js b/server/services/categorizationService.js
similarity index 100%
rename from services/categorizationService.js
rename to server/services/categorizationService.js
diff --git a/services/categoryService.js b/server/services/categoryService.js
similarity index 100%
rename from services/categoryService.js
rename to server/services/categoryService.js
diff --git a/services/chatService.js b/server/services/chatService.js
similarity index 100%
rename from services/chatService.js
rename to server/services/chatService.js
diff --git a/services/collaborationService.js b/server/services/collaborationService.js
similarity index 100%
rename from services/collaborationService.js
rename to server/services/collaborationService.js
diff --git a/services/complianceEngine.js b/server/services/complianceEngine.js
similarity index 100%
rename from services/complianceEngine.js
rename to server/services/complianceEngine.js
diff --git a/services/cronJobs.js b/server/services/cronJobs.js
similarity index 100%
rename from services/cronJobs.js
rename to server/services/cronJobs.js
diff --git a/services/currencyService.js b/server/services/currencyService.js
similarity index 100%
rename from services/currencyService.js
rename to server/services/currencyService.js
diff --git a/services/debtService.js b/server/services/debtService.js
similarity index 100%
rename from services/debtService.js
rename to server/services/debtService.js
diff --git a/services/deductionEngine.js b/server/services/deductionEngine.js
similarity index 100%
rename from services/deductionEngine.js
rename to server/services/deductionEngine.js
diff --git a/services/discoveryService.js b/server/services/discoveryService.js
similarity index 100%
rename from services/discoveryService.js
rename to server/services/discoveryService.js
diff --git a/services/emailService.js b/server/services/emailService.js
similarity index 100%
rename from services/emailService.js
rename to server/services/emailService.js
diff --git a/services/expenseService.js b/server/services/expenseService.js
similarity index 100%
rename from services/expenseService.js
rename to server/services/expenseService.js
diff --git a/services/exportService.js b/server/services/exportService.js
similarity index 100%
rename from services/exportService.js
rename to server/services/exportService.js
diff --git a/services/fileUploadService.js b/server/services/fileUploadService.js
similarity index 100%
rename from services/fileUploadService.js
rename to server/services/fileUploadService.js
diff --git a/services/forecastService.js b/server/services/forecastService.js
similarity index 100%
rename from services/forecastService.js
rename to server/services/forecastService.js
diff --git a/services/forecastingEngine.js b/server/services/forecastingEngine.js
similarity index 100%
rename from services/forecastingEngine.js
rename to server/services/forecastingEngine.js
diff --git a/services/forecastingService.js b/server/services/forecastingService.js
similarity index 100%
rename from services/forecastingService.js
rename to server/services/forecastingService.js
diff --git a/services/forensicAuditService.js b/server/services/forensicAuditService.js
similarity index 100%
rename from services/forensicAuditService.js
rename to server/services/forensicAuditService.js
diff --git a/services/forexService.js b/server/services/forexService.js
similarity index 100%
rename from services/forexService.js
rename to server/services/forexService.js
diff --git a/services/fraudDetectionService.js b/server/services/fraudDetectionService.js
similarity index 100%
rename from services/fraudDetectionService.js
rename to server/services/fraudDetectionService.js
diff --git a/services/gamificationService.js b/server/services/gamificationService.js
similarity index 100%
rename from services/gamificationService.js
rename to server/services/gamificationService.js
diff --git a/services/goalService.js b/server/services/goalService.js
similarity index 100%
rename from services/goalService.js
rename to server/services/goalService.js
diff --git a/services/groupService.js b/server/services/groupService.js
similarity index 100%
rename from services/groupService.js
rename to server/services/groupService.js
diff --git a/services/insightService.js b/server/services/insightService.js
similarity index 100%
rename from services/insightService.js
rename to server/services/insightService.js
diff --git a/services/integrationService.js b/server/services/integrationService.js
similarity index 100%
rename from services/integrationService.js
rename to server/services/integrationService.js
diff --git a/services/intelligenceService.js b/server/services/intelligenceService.js
similarity index 100%
rename from services/intelligenceService.js
rename to server/services/intelligenceService.js
diff --git a/services/internationalizationService.js b/server/services/internationalizationService.js
similarity index 100%
rename from services/internationalizationService.js
rename to server/services/internationalizationService.js
diff --git a/services/inventoryService.js b/server/services/inventoryService.js
similarity index 100%
rename from services/inventoryService.js
rename to server/services/inventoryService.js
diff --git a/services/investmentService.js b/server/services/investmentService.js
similarity index 100%
rename from services/investmentService.js
rename to server/services/investmentService.js
diff --git a/services/invitationService.js b/server/services/invitationService.js
similarity index 100%
rename from services/invitationService.js
rename to server/services/invitationService.js
diff --git a/services/inviteService.js b/server/services/inviteService.js
similarity index 100%
rename from services/inviteService.js
rename to server/services/inviteService.js
diff --git a/services/invoiceService.js b/server/services/invoiceService.js
similarity index 100%
rename from services/invoiceService.js
rename to server/services/invoiceService.js
diff --git a/services/invoiceSyncService.js b/server/services/invoiceSyncService.js
similarity index 100%
rename from services/invoiceSyncService.js
rename to server/services/invoiceSyncService.js
diff --git a/services/merchantLearningService.js b/server/services/merchantLearningService.js
similarity index 100%
rename from services/merchantLearningService.js
rename to server/services/merchantLearningService.js
diff --git a/services/notificationService.js b/server/services/notificationService.js
similarity index 100%
rename from services/notificationService.js
rename to server/services/notificationService.js
diff --git a/services/ocrService.js b/server/services/ocrService.js
similarity index 100%
rename from services/ocrService.js
rename to server/services/ocrService.js
diff --git a/services/openBankingService.js b/server/services/openBankingService.js
similarity index 100%
rename from services/openBankingService.js
rename to server/services/openBankingService.js
diff --git a/services/parsingService.js b/server/services/parsingService.js
similarity index 100%
rename from services/parsingService.js
rename to server/services/parsingService.js
diff --git a/services/paymentService.js b/server/services/paymentService.js
similarity index 100%
rename from services/paymentService.js
rename to server/services/paymentService.js
diff --git a/services/payrollService.js b/server/services/payrollService.js
similarity index 100%
rename from services/payrollService.js
rename to server/services/payrollService.js
diff --git a/services/pdfService.js b/server/services/pdfService.js
similarity index 100%
rename from services/pdfService.js
rename to server/services/pdfService.js
diff --git a/services/portfolioService.js b/server/services/portfolioService.js
similarity index 100%
rename from services/portfolioService.js
rename to server/services/portfolioService.js
diff --git a/services/priceUpdateService.js b/server/services/priceUpdateService.js
similarity index 100%
rename from services/priceUpdateService.js
rename to server/services/priceUpdateService.js
diff --git a/services/procurementService.js b/server/services/procurementService.js
similarity index 100%
rename from services/procurementService.js
rename to server/services/procurementService.js
diff --git a/services/projectRevenueService.js b/server/services/projectRevenueService.js
similarity index 100%
rename from services/projectRevenueService.js
rename to server/services/projectRevenueService.js
diff --git a/services/recurringService.js b/server/services/recurringService.js
similarity index 100%
rename from services/recurringService.js
rename to server/services/recurringService.js
diff --git a/services/reminderService.js b/server/services/reminderService.js
similarity index 100%
rename from services/reminderService.js
rename to server/services/reminderService.js
diff --git a/services/replenishmentService.js b/server/services/replenishmentService.js
similarity index 100%
rename from services/replenishmentService.js
rename to server/services/replenishmentService.js
diff --git a/services/reportService.js b/server/services/reportService.js
similarity index 100%
rename from services/reportService.js
rename to server/services/reportService.js
diff --git a/services/revaluationService.js b/server/services/revaluationService.js
similarity index 100%
rename from services/revaluationService.js
rename to server/services/revaluationService.js
diff --git a/services/ruleEngine.js b/server/services/ruleEngine.js
similarity index 100%
rename from services/ruleEngine.js
rename to server/services/ruleEngine.js
diff --git a/services/runwayForecaster.js b/server/services/runwayForecaster.js
similarity index 100%
rename from services/runwayForecaster.js
rename to server/services/runwayForecaster.js
diff --git a/services/scoreService.js b/server/services/scoreService.js
similarity index 100%
rename from services/scoreService.js
rename to server/services/scoreService.js
diff --git a/services/securityMonitor.js b/server/services/securityMonitor.js
similarity index 100%
rename from services/securityMonitor.js
rename to server/services/securityMonitor.js
diff --git a/services/securityService.js b/server/services/securityService.js
similarity index 100%
rename from services/securityService.js
rename to server/services/securityService.js
diff --git a/services/settlementService.js b/server/services/settlementService.js
similarity index 100%
rename from services/settlementService.js
rename to server/services/settlementService.js
diff --git a/services/sharedSpaceService.js b/server/services/sharedSpaceService.js
similarity index 100%
rename from services/sharedSpaceService.js
rename to server/services/sharedSpaceService.js
diff --git a/services/splitService.js b/server/services/splitService.js
similarity index 100%
rename from services/splitService.js
rename to server/services/splitService.js
diff --git a/services/subscriptionDetector.js b/server/services/subscriptionDetector.js
similarity index 100%
rename from services/subscriptionDetector.js
rename to server/services/subscriptionDetector.js
diff --git a/services/subscriptionService.js b/server/services/subscriptionService.js
similarity index 100%
rename from services/subscriptionService.js
rename to server/services/subscriptionService.js
diff --git a/services/suspiciousLoginDetectionService.js b/server/services/suspiciousLoginDetectionService.js
similarity index 100%
rename from services/suspiciousLoginDetectionService.js
rename to server/services/suspiciousLoginDetectionService.js
diff --git a/services/tagManagementService.js b/server/services/tagManagementService.js
similarity index 100%
rename from services/tagManagementService.js
rename to server/services/tagManagementService.js
diff --git a/services/taxOptimizationService.js b/server/services/taxOptimizationService.js
similarity index 100%
rename from services/taxOptimizationService.js
rename to server/services/taxOptimizationService.js
diff --git a/services/taxService.js b/server/services/taxService.js
similarity index 100%
rename from services/taxService.js
rename to server/services/taxService.js
diff --git a/services/teamManagementService.js b/server/services/teamManagementService.js
similarity index 100%
rename from services/teamManagementService.js
rename to server/services/teamManagementService.js
diff --git a/services/transactionImportService.js b/server/services/transactionImportService.js
similarity index 100%
rename from services/transactionImportService.js
rename to server/services/transactionImportService.js
diff --git a/services/transactionService.js b/server/services/transactionService.js
similarity index 100%
rename from services/transactionService.js
rename to server/services/transactionService.js
diff --git a/services/treasuryService.js b/server/services/treasuryService.js
similarity index 100%
rename from services/treasuryService.js
rename to server/services/treasuryService.js
diff --git a/services/twoFactorAuthService.js b/server/services/twoFactorAuthService.js
similarity index 100%
rename from services/twoFactorAuthService.js
rename to server/services/twoFactorAuthService.js
diff --git a/services/webhookService.js b/server/services/webhookService.js
similarity index 100%
rename from services/webhookService.js
rename to server/services/webhookService.js
diff --git a/services/wellnessService.js b/server/services/wellnessService.js
similarity index 100%
rename from services/wellnessService.js
rename to server/services/wellnessService.js
diff --git a/services/workspaceService.js b/server/services/workspaceService.js
similarity index 100%
rename from services/workspaceService.js
rename to server/services/workspaceService.js
diff --git a/socket/collabHandler.js b/server/socket/collabHandler.js
similarity index 100%
rename from socket/collabHandler.js
rename to server/socket/collabHandler.js
diff --git a/tests/backupService.test.js b/server/tests/backupService.test.js
similarity index 100%
rename from tests/backupService.test.js
rename to server/tests/backupService.test.js
diff --git a/tests/output/test-report.csv b/server/tests/output/test-report.csv
similarity index 100%
rename from tests/output/test-report.csv
rename to server/tests/output/test-report.csv
diff --git a/tests/output/test-report.pdf b/server/tests/output/test-report.pdf
similarity index 100%
rename from tests/output/test-report.pdf
rename to server/tests/output/test-report.pdf
diff --git a/tests/output/test-report.xlsx b/server/tests/output/test-report.xlsx
similarity index 100%
rename from tests/output/test-report.xlsx
rename to server/tests/output/test-report.xlsx
diff --git a/utils/AppError.js b/server/utils/AppError.js
similarity index 100%
rename from utils/AppError.js
rename to server/utils/AppError.js
diff --git a/utils/ResponseFactory.js b/server/utils/ResponseFactory.js
similarity index 100%
rename from utils/ResponseFactory.js
rename to server/utils/ResponseFactory.js
diff --git a/utils/financialModels.js b/server/utils/financialModels.js
similarity index 100%
rename from utils/financialModels.js
rename to server/utils/financialModels.js
diff --git a/utils/patternMatcher.js b/server/utils/patternMatcher.js
similarity index 100%
rename from utils/patternMatcher.js
rename to server/utils/patternMatcher.js
diff --git a/utils/stockMath.js b/server/utils/stockMath.js
similarity index 100%
rename from utils/stockMath.js
rename to server/utils/stockMath.js
diff --git a/utils/taxCalculators.js b/server/utils/taxCalculators.js
similarity index 100%
rename from utils/taxCalculators.js
rename to server/utils/taxCalculators.js
diff --git a/verify-backup-system.js b/server/verify-backup-system.js
similarity index 100%
rename from verify-backup-system.js
rename to server/verify-backup-system.js
diff --git a/workspace-feature.js b/workspace-feature.js
deleted file mode 100644
index d9da36c1..00000000
--- a/workspace-feature.js
+++ /dev/null
@@ -1,908 +0,0 @@
-/**
- * Enterprise-Grade RBAC Workspace Management
- * Issue #420: Role-Based Access Control & Workspace Invites
- *
- * Roles:
- * - owner: Full control (transfer ownership, delete workspace)
- * - manager: Can manage members and settings
- * - editor: Can add/edit expenses
- * - viewer: Read-only access
- */
-
-var WORKSPACE_API_URL = '/api/workspaces';
-
-// State management with persistence
-let currentWorkspaces = [];
-let activeWorkspace = null;
-let pendingInvites = [];
-
-// Role definitions
-const ROLES = {
- owner: { name: 'Owner', color: '#ff6b6b', icon: 'fa-crown' },
- manager: { name: 'Manager', color: '#4ecdc4', icon: 'fa-user-shield' },
- editor: { name: 'Editor', color: '#45b7d1', icon: 'fa-edit' },
- viewer: { name: 'Viewer', color: '#96ceb4', icon: 'fa-eye' }
-};
-
-// Permission definitions for UI
-const ROLE_PERMISSIONS = {
- owner: ['Full workspace control', 'Transfer ownership', 'Delete workspace', 'Manage all members', 'All editor permissions'],
- manager: ['Manage workspace settings', 'Invite & remove members', 'Promote/demote members', 'Approve expenses', 'All editor permissions'],
- editor: ['Create expenses', 'Edit expenses', 'Delete expenses', 'View budgets', 'Export reports'],
- viewer: ['View expenses', 'View budgets', 'View reports']
-};
-
-// Enhanced API Functions with better error handling
-async function getAuthHeaders() {
- const token = localStorage.getItem('authToken') || localStorage.getItem('token');
- return {
- 'Content-Type': 'application/json',
- 'Authorization': token ? `Bearer ${token}` : ''
- };
-}
-
-/**
- * Enhanced workspace fetching with caching
- */
-async function fetchWorkspaces() {
- try {
- const token = localStorage.getItem('authToken') || localStorage.getItem('token');
- if (!token) return [];
-
- const response = await fetch(WORKSPACE_API_URL, {
- headers: await getAuthHeaders()
- });
- if (!response.ok) throw new Error('Failed to fetch workspaces');
- const data = await response.json();
- currentWorkspaces = data.data || [];
- renderWorkspaceSelection();
- return currentWorkspaces;
- } catch (error) {
- console.error('Error fetching workspaces:', error);
- showWorkspaceNotification('Failed to load workspaces', 'error');
- return [];
- }
-}
-
-/**
- * Enhanced create workspace with validation
- */
-async function createWorkspace(name, description) {
- try {
- const response = await fetch(WORKSPACE_API_URL, {
- method: 'POST',
- headers: await getAuthHeaders(),
- body: JSON.stringify({ name, description })
- });
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('Workspace created successfully!', 'success');
- await fetchWorkspaces();
- return data.data;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return null;
- }
-}
-
-/**
- * Update workspace settings
- */
-async function updateWorkspace(workspaceId, updates) {
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${workspaceId}`, {
- method: 'PUT',
- headers: await getAuthHeaders(),
- body: JSON.stringify(updates)
- });
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('Workspace updated!', 'success');
- await fetchWorkspaces();
- if (activeWorkspace?._id === workspaceId) {
- await loadWorkspaceMembers();
- }
- return data.data;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return null;
- }
-}
-
-/**
- * Delete workspace
- */
-async function deleteWorkspace(workspaceId) {
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${workspaceId}`, {
- method: 'DELETE',
- headers: await getAuthHeaders()
- });
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('Workspace deleted', 'success');
- if (activeWorkspace?._id === workspaceId) {
- selectWorkspace(null);
- }
- await fetchWorkspaces();
- return true;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return false;
- }
-}
-
-/**
- * Invite user to workspace
- */
-async function inviteToWorkspace(workspaceId, email, role, message = '') {
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${workspaceId}/invite`, {
- method: 'POST',
- headers: await getAuthHeaders(),
- body: JSON.stringify({ email, role, message })
- });
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('Invitation sent successfully!', 'success');
- await loadPendingInvites(workspaceId);
- return data.data;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return null;
- }
-}
-
-/**
- * Resend invite
- */
-async function resendInvite(workspaceId, inviteId) {
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${workspaceId}/invites/${inviteId}/resend`, {
- method: 'POST',
- headers: await getAuthHeaders()
- });
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('Invitation resent!', 'success');
- return true;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return false;
- }
-}
-
-/**
- * Revoke invite
- */
-async function revokeInvite(workspaceId, inviteId) {
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${workspaceId}/invites/${inviteId}`, {
- method: 'DELETE',
- headers: await getAuthHeaders()
- });
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('Invitation revoked', 'success');
- await loadPendingInvites(workspaceId);
- return true;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return false;
- }
-}
-
-/**
- * Get pending invites for workspace
- */
-async function loadPendingInvites(workspaceId) {
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${workspaceId}/invites`, {
- headers: await getAuthHeaders()
- });
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- pendingInvites = data.data || [];
- renderPendingInvites();
- return pendingInvites;
- } catch (error) {
- console.error('Error loading invites:', error);
- return [];
- }
-}
-
-/**
- * Join workspace using token
- */
-async function joinWorkspace(token) {
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/join`, {
- method: 'POST',
- headers: await getAuthHeaders(),
- body: JSON.stringify({ token })
- });
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification(data.message || 'Joined workspace successfully!', 'success');
- await fetchWorkspaces();
-
- // Select the new workspace
- if (data.data?.workspace?._id) {
- selectWorkspace(data.data.workspace._id);
- }
-
- return data.data;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return null;
- }
-}
-
-/**
- * Update member role
- */
-async function changeMemberRole(userId, newRole) {
- if (!activeWorkspace || !newRole) return;
-
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${activeWorkspace._id}/members/${userId}`, {
- method: 'PUT',
- headers: await getAuthHeaders(),
- body: JSON.stringify({ role: newRole })
- });
-
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('Member role updated!', 'success');
- await loadWorkspaceMembers();
- return true;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return false;
- }
-}
-
-/**
- * Remove member from workspace
- */
-async function removeMember(userId) {
- if (!activeWorkspace) return;
-
- if (!confirm('Are you sure you want to remove this member?')) {
- return false;
- }
-
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${activeWorkspace._id}/members/${userId}`, {
- method: 'DELETE',
- headers: await getAuthHeaders()
- });
-
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('Member removed', 'success');
- await loadWorkspaceMembers();
- return true;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return false;
- }
-}
-
-/**
- * Leave workspace
- */
-async function leaveWorkspace(workspaceId) {
- if (!confirm('Are you sure you want to leave this workspace?')) {
- return false;
- }
-
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${workspaceId}/leave`, {
- method: 'POST',
- headers: await getAuthHeaders()
- });
-
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('You have left the workspace', 'success');
- selectWorkspace(null);
- await fetchWorkspaces();
- return true;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return false;
- }
-}
-
-/**
- * Transfer ownership
- */
-async function transferOwnership(newOwnerId) {
- if (!activeWorkspace) return;
-
- const newOwner = activeWorkspace.members.find(m => m.user._id === newOwnerId);
- if (!confirm(`Transfer ownership to ${newOwner?.user?.name || 'this member'}? You will become a manager.`)) {
- return false;
- }
-
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${activeWorkspace._id}/transfer`, {
- method: 'POST',
- headers: await getAuthHeaders(),
- body: JSON.stringify({ newOwnerId })
- });
-
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('Ownership transferred!', 'success');
- await loadWorkspaceMembers();
- return true;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return false;
- }
-}
-
-/**
- * Generate shareable invite link
- */
-async function generateInviteLink(role = 'viewer', expiryDays = 30) {
- if (!activeWorkspace) return;
-
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${activeWorkspace._id}/invite-link`, {
- method: 'POST',
- headers: await getAuthHeaders(),
- body: JSON.stringify({ role, expiryDays })
- });
-
- const data = await response.json();
- if (!response.ok) throw new Error(data.error);
-
- showWorkspaceNotification('Invite link generated!', 'success');
- return data.data;
- } catch (error) {
- showWorkspaceNotification(error.message, 'error');
- return null;
- }
-}
-
-// ========================
-// UI Rendering Functions
-// ========================
-
-/**
- * Workspace Selection UI
- */
-function renderWorkspaceSelection() {
- const container = document.getElementById('workspace-selector');
- if (!container) return;
-
- const userRole = activeWorkspace?.userRole || null;
-
- container.innerHTML = `
-
-
- ${activeWorkspace ? activeWorkspace.name.charAt(0).toUpperCase() : ' '}
-
-
- Current Workspace
- ${activeWorkspace ? activeWorkspace.name : 'Personal Account'}
- ${userRole ? `${ROLES[userRole]?.name || userRole} ` : ''}
-
-
-
-
-
-
- Personal Account
-
-
Shared Workspaces
- ${currentWorkspaces.length === 0 ?
- '
No shared workspaces yet
' :
- currentWorkspaces.map(ws => `
-
-
${ws.name.charAt(0).toUpperCase()}
-
- ${ws.name}
- ${ROLES[ws.userRole]?.name || ws.userRole}
-
- ${ws.isOwner ? '
' : ''}
-
- `).join('')
- }
-
-
- `;
-}
-
-/**
- * Render members list with role management
- */
-function renderMembersList(members) {
- const membersList = document.getElementById('members-list');
- if (!membersList) return;
-
- const currentUserId = localStorage.getItem('userId');
- const userRole = activeWorkspace?.userRole || 'viewer';
- const canManageMembers = ['owner', 'manager'].includes(userRole);
- const isOwner = userRole === 'owner';
-
- membersList.innerHTML = members.map(member => {
- const isCurrentUser = member.user._id === currentUserId;
- const memberRole = member.role;
- const roleInfo = ROLES[memberRole] || { name: memberRole, color: '#888', icon: 'fa-user' };
-
- // Can edit if: manager+ AND target is below your role AND not yourself
- const canEdit = canManageMembers && !isCurrentUser &&
- getRoleLevel(userRole) > getRoleLevel(memberRole);
- const canRemove = canEdit;
- const canTransfer = isOwner && !isCurrentUser;
-
- return `
-
-
-
- ${member.user.avatar ?
- `
` :
- (member.user.name || 'U').charAt(0).toUpperCase()
- }
-
-
-
- ${member.user.name || 'Unknown User'}
- ${isCurrentUser ? '(You) ' : ''}
-
- ${member.user.email}
-
-
-
-
-
- ${roleInfo.name}
-
-
-
- ${canEdit ? `
-
-
-
-
-
-
- ` : ''}
- ${canTransfer ? `
-
-
-
- ` : ''}
- ${canRemove ? `
-
-
-
- ` : ''}
-
-
- `;
- }).join('');
-}
-
-/**
- * Render pending invites
- */
-function renderPendingInvites() {
- const invitesList = document.getElementById('pending-invites-list');
- if (!invitesList) return;
-
- if (pendingInvites.length === 0) {
- invitesList.innerHTML = 'No pending invitations
';
- return;
- }
-
- invitesList.innerHTML = pendingInvites.map(invite => `
-
-
- ${invite.email}
- ${ROLES[invite.role]?.name || invite.role}
- Expires ${invite.expiresIn || 'soon'}
-
-
-
-
-
-
-
-
-
-
- `).join('');
-}
-
-/**
- * Select active workspace
- */
-function selectWorkspace(id) {
- if (!id) {
- activeWorkspace = null;
- } else {
- activeWorkspace = currentWorkspaces.find(ws => ws._id === id);
- }
-
- // Close dropdown
- document.getElementById('workspace-dropdown')?.classList.remove('active');
-
- // Update UI
- renderWorkspaceSelection();
- updateWorkspaceDashboard();
-
- // Save preference
- localStorage.setItem('activeWorkspaceId', id || 'personal');
-
- // Dispatch event for other components
- window.dispatchEvent(new CustomEvent('workspaceChanged', {
- detail: { workspace: activeWorkspace }
- }));
-}
-
-function toggleWorkspaceDropdown() {
- document.getElementById('workspace-dropdown')?.classList.toggle('active');
-}
-
-function toggleRoleDropdown(userId) {
- const dropdown = document.getElementById(`role-dropdown-${userId}`);
-
- // Close all other dropdowns
- document.querySelectorAll('.role-dropdown-menu.active').forEach(d => {
- if (d !== dropdown) d.classList.remove('active');
- });
-
- dropdown?.classList.toggle('active');
-}
-
-/**
- * Update dashboard context based on workspace
- */
-function updateWorkspaceDashboard() {
- if (typeof updateAllData === 'function') {
- updateAllData(activeWorkspace ? activeWorkspace._id : null);
- }
-
- // Show/hide workspace settings
- const workspaceSettings = document.getElementById('workspace-settings');
- if (workspaceSettings) {
- workspaceSettings.style.display = activeWorkspace ? 'block' : 'none';
- }
-
- if (activeWorkspace) {
- loadWorkspaceMembers();
- updateWorkspaceInfo();
- }
-}
-
-/**
- * Load workspace members
- */
-async function loadWorkspaceMembers() {
- if (!activeWorkspace) return;
-
- try {
- const response = await fetch(`${WORKSPACE_API_URL}/${activeWorkspace._id}`, {
- headers: await getAuthHeaders()
- });
-
- if (!response.ok) throw new Error('Failed to load workspace');
-
- const data = await response.json();
- activeWorkspace = { ...activeWorkspace, ...data.data };
-
- renderMembersList(activeWorkspace.members);
- updateWorkspaceInfo();
- updateInviteButtonVisibility();
-
- // Load pending invites if user can manage invites
- if (['owner', 'manager'].includes(activeWorkspace.userRole)) {
- await loadPendingInvites(activeWorkspace._id);
- }
- } catch (error) {
- console.error('Error loading workspace:', error);
- showWorkspaceNotification('Failed to load workspace members', 'error');
- }
-}
-
-/**
- * Update workspace info display
- */
-function updateWorkspaceInfo() {
- if (!activeWorkspace) return;
-
- const nameEl = document.getElementById('current-workspace-name');
- const descEl = document.getElementById('current-workspace-desc');
- const memberCountEl = document.getElementById('member-count');
- const userRoleEl = document.getElementById('your-role');
-
- if (nameEl) nameEl.textContent = activeWorkspace.name;
- if (descEl) descEl.textContent = activeWorkspace.description || 'No description';
- if (memberCountEl) memberCountEl.textContent = `${activeWorkspace.members?.length || 0} members`;
-
- if (userRoleEl) {
- const roleInfo = ROLES[activeWorkspace.userRole] || { name: 'Member' };
- userRoleEl.innerHTML = `
-
- ${roleInfo.name}
-
- `;
- }
-}
-
-/**
- * Update invite button visibility
- */
-function updateInviteButtonVisibility() {
- const inviteBtn = document.getElementById('invite-btn');
- const pendingSection = document.getElementById('pending-invites-section');
-
- const canInvite = activeWorkspace && ['owner', 'manager'].includes(activeWorkspace.userRole);
-
- if (inviteBtn) inviteBtn.style.display = canInvite ? 'flex' : 'none';
- if (pendingSection) pendingSection.style.display = canInvite ? 'block' : 'none';
-}
-
-// ========================
-// Modal Functions
-// ========================
-
-function openCreateWorkspaceModal() {
- const modal = document.getElementById('workspace-modal');
- if (modal) modal.classList.add('active');
-}
-
-function closeWorkspaceModal() {
- const modal = document.getElementById('workspace-modal');
- if (modal) modal.classList.remove('active');
-}
-
-function openInviteModal() {
- const modal = document.getElementById('invite-modal');
- if (modal) {
- modal.classList.add('active');
- updateRolePermissions('viewer');
- }
-}
-
-function closeInviteModal() {
- const modal = document.getElementById('invite-modal');
- if (modal) modal.classList.remove('active');
-}
-
-function openInviteLinkModal() {
- const modal = document.getElementById('invite-link-modal');
- if (modal) modal.classList.add('active');
-}
-
-function closeInviteLinkModal() {
- const modal = document.getElementById('invite-link-modal');
- if (modal) modal.classList.remove('active');
-}
-
-/**
- * Update role permissions display
- */
-function updateRolePermissions(role) {
- const display = document.getElementById('role-permissions-display');
- if (!display) return;
-
- const permissions = ROLE_PERMISSIONS[role] || [];
- display.innerHTML = permissions.length > 0
- ? `${permissions.map(p => ` ${p} `).join('')} `
- : 'Select a role to see permissions
';
-}
-
-// ========================
-// Helper Functions
-// ========================
-
-function getRoleLevel(role) {
- const levels = { owner: 4, manager: 3, editor: 2, viewer: 1 };
- return levels[role] || 0;
-}
-
-function getWorkspaceColor(name) {
- const colors = ['#667eea', '#764ba2', '#f093fb', '#f5576c', '#4facfe', '#00f2fe', '#43e97b', '#fa709a'];
- const index = name.charCodeAt(0) % colors.length;
- return colors[index];
-}
-
-function getAvatarColor(name) {
- const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7', '#dfe6e9', '#ff7675', '#74b9ff'];
- const index = (name || 'U').charCodeAt(0) % colors.length;
- return colors[index];
-}
-
-function showWorkspaceNotification(message, type = 'info') {
- if (typeof showNotification === 'function') {
- showNotification(message, type);
- return;
- }
-
- // Fallback notification
- const notification = document.createElement('div');
- notification.className = `workspace-notification ${type}`;
- notification.innerHTML = `
-
- ${message}
- `;
- notification.style.cssText = `
- position: fixed; top: 20px; right: 20px; padding: 1rem 1.5rem;
- border-radius: 8px; color: white; z-index: 10000; display: flex;
- align-items: center; gap: 0.5rem; animation: slideIn 0.3s ease;
- background: ${type === 'success' ? '#00c853' : type === 'error' ? '#ff5252' : '#2196f3'};
- `;
- document.body.appendChild(notification);
- setTimeout(() => notification.remove(), 3000);
-}
-
-// ========================
-// Initialization
-// ========================
-
-function initWorkspaceFeature() {
- const workspaceIdPref = localStorage.getItem('activeWorkspaceId');
-
- fetchWorkspaces().then(() => {
- if (workspaceIdPref && workspaceIdPref !== 'personal') {
- selectWorkspace(workspaceIdPref);
- }
- });
-
- // Handle create workspace form
- const createForm = document.getElementById('create-workspace-form');
- if (createForm) {
- createForm.addEventListener('submit', async (e) => {
- e.preventDefault();
- const name = document.getElementById('workspace-name-input')?.value;
- const desc = document.getElementById('workspace-desc-input')?.value;
- if (name) {
- await createWorkspace(name, desc);
- closeWorkspaceModal();
- createForm.reset();
- }
- });
- }
-
- // Handle invite form
- const inviteForm = document.getElementById('invite-form');
- if (inviteForm) {
- inviteForm.addEventListener('submit', async (e) => {
- e.preventDefault();
- const email = document.getElementById('invite-email')?.value;
- const role = document.getElementById('invite-role')?.value || 'viewer';
- const message = document.getElementById('invite-message')?.value || '';
-
- if (!activeWorkspace) {
- showWorkspaceNotification('No workspace selected', 'error');
- return;
- }
-
- if (email) {
- await inviteToWorkspace(activeWorkspace._id, email, role, message);
- closeInviteModal();
- inviteForm.reset();
- updateRolePermissions('viewer');
- }
- });
- }
-
- // Role selection change
- const roleSelect = document.getElementById('invite-role');
- if (roleSelect) {
- roleSelect.addEventListener('change', (e) => {
- updateRolePermissions(e.target.value);
- });
- }
-
- // Handle invite link form
- const inviteLinkForm = document.getElementById('invite-link-form');
- if (inviteLinkForm) {
- inviteLinkForm.addEventListener('submit', async (e) => {
- e.preventDefault();
- const role = document.getElementById('link-role')?.value || 'viewer';
- const expiry = parseInt(document.getElementById('link-expiry')?.value) || 30;
-
- const result = await generateInviteLink(role, expiry);
- if (result) {
- const linkDisplay = document.getElementById('generated-link');
- if (linkDisplay) {
- linkDisplay.value = result.link;
- linkDisplay.style.display = 'block';
- document.getElementById('copy-link-btn')?.style.display = 'inline-flex';
- }
- }
- });
- }
-
- // Copy link button
- const copyLinkBtn = document.getElementById('copy-link-btn');
- if (copyLinkBtn) {
- copyLinkBtn.addEventListener('click', () => {
- const linkInput = document.getElementById('generated-link');
- if (linkInput) {
- linkInput.select();
- document.execCommand('copy');
- showWorkspaceNotification('Link copied to clipboard!', 'success');
- }
- });
- }
-
- // Handle invitation join from URL
- const urlParams = new URLSearchParams(window.location.search);
- const inviteToken = urlParams.get('token');
- if (inviteToken && window.location.pathname.includes('join-workspace')) {
- joinWorkspace(inviteToken);
- }
-
- // Close dropdowns when clicking outside
- document.addEventListener('click', (e) => {
- if (!e.target.closest('.workspace-selector')) {
- document.getElementById('workspace-dropdown')?.classList.remove('active');
- }
- if (!e.target.closest('.role-dropdown')) {
- document.querySelectorAll('.role-dropdown-menu.active').forEach(d => d.classList.remove('active'));
- }
- });
-}
-
-// Initialize when DOM is ready
-if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initWorkspaceFeature);
-} else {
- initWorkspaceFeature();
-}
-
-// Export functions for module usage
-if (typeof module !== 'undefined' && module.exports) {
- module.exports = {
- fetchWorkspaces,
- createWorkspace,
- selectWorkspace,
- inviteToWorkspace,
- changeMemberRole,
- removeMember,
- leaveWorkspace,
- transferOwnership,
- ROLES,
- ROLE_PERMISSIONS
- };
-}
-
-// Auto-initialize on DOM ready
-if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initWorkspaceFeature);
-} else {
- initWorkspaceFeature();
-}
\ No newline at end of file