Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 350 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"@cometchat/chat-uikit-react": "^6.4.4",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@iconify/tailwind": "^1.2.0",
"@iconify/tailwind4": "^1.2.3",
"@mui/icons-material": "^9.0.1",
"@mui/material": "^9.0.1",
"@mui/x-charts": "^9.2.0",
Expand Down Expand Up @@ -68,6 +70,7 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@iconify-json/svg-spinners": "^1.2.4",
"@tailwindcss/vite": "^4.3.0",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
Expand Down
26 changes: 3 additions & 23 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -597,29 +597,9 @@ body.chat-page-active .routes-container {
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
}

/* Spinner for loading states */
.spinner-border {
display: inline-block;
width: 1rem;
height: 1rem;
vertical-align: text-bottom;
border: 0.15em solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spinner-border 0.75s linear infinite;
}

.spinner-border-sm {
width: 0.875rem;
height: 0.875rem;
border-width: 0.125em;
}

@keyframes spinner-border {
to {
transform: rotate(360deg);
}
}
/*
* (removed legacy .spinner-border loader — replaced by Iconify eclipse spinner)
*/

/* ==========================================================================
Utility Classes
Expand Down
1 change: 1 addition & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { ConfigProvider, App as AntdApp } from 'antd';
import retailOpsTheme from './theme/antdTheme';
import { AuthProvider, useAuth } from './contexts/AuthContext';
import { SidebarProvider, useSidebar } from './contexts/SidebarContext';

Check failure on line 6 in src/App.jsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'useSidebar' is defined but never used. Allowed unused vars must match /^[A-Z_]/u
import { PageTitleProvider } from './contexts/PageTitleContext';
import { DateRangeProvider } from './contexts/DateRangeContext';
import ProtectedRoute from './components/ProtectedRoute';
Expand Down Expand Up @@ -97,7 +97,7 @@
import { PageLoading } from './components/Spinner';

function AppRoutes() {
const { isAuthenticated, loading, bootstrapping } = useAuth();

Check failure on line 100 in src/App.jsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'bootstrapping' is assigned a value but never used. Allowed unused vars must match /^[A-Z_]/u
const { showWizard, isLoading: onboardingLoading } = useOnboarding();

if (loading || onboardingLoading) return <PageLoading message="Checking authentication..." />;
Expand Down Expand Up @@ -203,6 +203,7 @@
<ToastProvider>
<ConfigProvider
theme={retailOpsTheme}
spin={{ indicator: <span className="icon-[svg-spinners--eclipse]" aria-hidden="true" /> }}
>
<AntdApp>
<AppRoutes />
Expand Down
4 changes: 2 additions & 2 deletions src/components/AsinDetailModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Package, IndianRupee, Star, Award, Store, Activity, BarChart3,
TrendingUp, TrendingDown, Eye, ExternalLink, Calendar, ListChecks,
Image, AlertCircle, Trophy, Sparkles, CheckCircle2, AlertTriangle,
Loader2, RefreshCcw, ChevronDown, X
RefreshCcw, ChevronDown, X
} from 'lucide-react';
import { asinApi } from '../services/api';
import Chart from 'react-apexcharts';
Expand Down Expand Up @@ -62,7 +62,7 @@
: (recommendations || []);
const score = rawScore > 10 ? parseFloat((rawScore / 10).toFixed(1)) : parseFloat((rawScore || 0).toFixed(1));
const color = score >= 8.5 ? C.success : score >= 7.0 ? C.warning : C.danger;
const bgColor = score >= 8.5 ? C.successBg : score >= 7.0 ? C.warningBg : C.dangerBg;

Check failure on line 65 in src/components/AsinDetailModal.jsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'bgColor' is assigned a value but never used. Allowed unused vars must match /^[A-Z_]/u

return (
<Card
Expand Down Expand Up @@ -225,7 +225,7 @@
return d.toLocaleDateString('en-IN', { month: 'short', day: '2-digit' });
});

const commonOptions = {

Check warning on line 228 in src/components/AsinDetailModal.jsx

View workflow job for this annotation

GitHub Actions / Frontend CI

The 'commonOptions' object makes the dependencies of useMemo Hook (at line 324) change on every render. To fix this, wrap the initialization of 'commonOptions' in its own useMemo() Hook
chart: { toolbar: { show: false }, zoom: { enabled: false }, background: 'transparent', fontFamily: 'Inter, system-ui, sans-serif' },
xaxis: {
categories: chartCategories,
Expand Down Expand Up @@ -801,7 +801,7 @@
<div style={{ minHeight: 350, position: 'relative' }}>
{isLoadingSubTrend ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: 350 }}>
<Loader2 size={32} className="animate-spin" style={{ color: C.primary, marginBottom: 12 }} />
<span className="icon-[svg-spinners--eclipse]" style={{ color: C.primary, fontSize: 32, marginBottom: 12 }} aria-hidden="true" />
<Text type="secondary" style={{ fontWeight: 500 }}>Running niche data indexing...</Text>
</div>
) : subBsrSeries.length > 0 ? (
Expand Down
12 changes: 4 additions & 8 deletions src/components/BSRViewModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as XLSX from 'xlsx';
import {
X, BarChart3, Search, Download, Maximize2, Minimize2,
ArrowUpDown, ArrowUp, ArrowDown, FileText, Minus,
TrendingUp, TrendingDown, SlidersHorizontal, Loader2, Store, Calendar
TrendingUp, TrendingDown, SlidersHorizontal, Store, Calendar
} from 'lucide-react';
import { asinApi, sellerApi } from '../services/api';
import InfiniteScrollSelect from './common/InfiniteScrollSelect';
Expand Down Expand Up @@ -301,10 +301,6 @@ const BSRViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', sellerI
.dd { font-size:10px; padding:2px 6px; border-radius:4px; text-align:center; min-width:48px; }
.dd-has { background:${C.successBg}; color:#2E7D32; font-weight:600; }
.dd-no { color:#d1d5db; }
.loader-pulse { height:30px; display:flex; align-items:center; justify-content:center; gap:6px; margin:16px 0; }
.pulse-dot { width:6px; height:6px; background:${C.primary}; border-radius:50%; animation:pulse 1.5s infinite; }
.pulse-dot:nth-child(2) { animation-delay:0.2s; }
.pulse-dot:nth-child(3) { animation-delay:0.4s; }
@keyframes pulse { 0%,100% { transform:scale(1); opacity:0.3; } 50% { transform:scale(1.5); opacity:1; } }
.bt input[type="checkbox"] { width:13px; height:13px; cursor:pointer; accent-color:${C.primary}; }
`;
Expand Down Expand Up @@ -332,7 +328,7 @@ const BSRViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', sellerI
</Space>
<Space size={8}>
<button className="chp" onClick={() => setShowExportMenu(!showExportMenu)} style={{ borderColor: C.success, color: C.success }} disabled={exporting}>
{exporting ? <Loader2 size={13} className="animate-spin" /> : <Download size={13} />} Export
{exporting ? <span className="icon-[svg-spinners--eclipse]" style={{ fontSize: 13 }} aria-hidden="true" /> : <Download size={13} />} Export
</button>
{showExportMenu && !exporting && (
<div className="position-absolute bg-white border rounded-lg shadow-xl p-1" style={{ top: '100%', right: 0, zIndex: 100, marginTop: 4, minWidth: 160 }}>
Expand Down Expand Up @@ -399,7 +395,7 @@ const BSRViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', sellerI
)}
{loading && (
<span className="d-flex align-items-center gap-1 ms-auto" style={{ fontSize: 'var(--font-size-xs)', color: C.textSecondary }}>
<Loader2 size={13} className="animate-spin" /> Fetching...
<span className="icon-[svg-spinners--eclipse]" style={{ fontSize: 13 }} aria-hidden="true" /> Fetching...
</span>
)}
</div>
Expand Down Expand Up @@ -520,7 +516,7 @@ const BSRViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', sellerI
</tbody>
</table>
{(loading || hasMore) && (
<div ref={loaderRef} className="loader-pulse"><div className="pulse-dot" /><div className="pulse-dot" /><div className="pulse-dot" /></div>
<span ref={loaderRef} className="icon-[svg-spinners--eclipse]" style={{ fontSize: 18, color: C.primary, display: 'block', margin: '16px 0' }} aria-hidden="true" />
)}
</div>

Expand Down
2 changes: 1 addition & 1 deletion src/components/DataTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ const DataTable = ({
if (isLoading) {
return (
<div className="d-flex justify-content-center align-items-center p-5" style={{ minHeight: '200px' }}>
<div className="pulse-loader"></div>
<span className="icon-[svg-spinners--eclipse]" style={{ fontSize: 32 }} aria-hidden="true" />
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ExportButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const ExportButton = ({ data, fileName = 'export', format = 'excel', className =
>
{isLoading ? (
<>
<span className="spinner-border spinner-border-sm me-2"></span>
<span className="icon-[svg-spinners--eclipse] me-2" aria-hidden="true"></span>
Exporting...
</>
) : (
Expand Down
14 changes: 4 additions & 10 deletions src/components/PriceViewModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'
import * as XLSX from 'xlsx';
import {
X, TrendingUp, TrendingDown, IndianRupee, Search, Download,
ArrowUp, ArrowDown, Minus, ArrowUpDown, FileText, SlidersHorizontal, Loader2, Store, Calendar, ChevronDown
ArrowUp, ArrowDown, Minus, ArrowUpDown, FileText, SlidersHorizontal, Store, Calendar, ChevronDown
} from 'lucide-react';
import { asinApi, sellerApi } from '../services/api';
import InfiniteScrollSelect from './common/InfiniteScrollSelect';
Expand Down Expand Up @@ -406,10 +406,6 @@ const PriceViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', selle
.dd-no { color:#d1d5db; }
.inp-sm { font-size:11px; height:28px; border:1.5px solid #e5e7eb; border-radius:8px; padding:2px 10px; width:80px; outline:none; }
.inp-sm:focus { border-color:${C.primary}; }
.loader-pulse { height:30px; display:flex; align-items:center; justify-content:center; gap:6px; margin:16px 0; }
.pulse-dot { width:6px; height:6px; background:${C.primary}; border-radius:50%; animation:pulse 1.5s infinite; }
.pulse-dot:nth-child(2) { animation-delay:0.2s; }
.pulse-dot:nth-child(3) { animation-delay:0.4s; }
@keyframes pulse { 0%,100% { transform:scale(1); opacity:0.3; } 50% { transform:scale(1.5); opacity:1; } }
.table input[type="checkbox"] { width:13px; height:13px; cursor:pointer; accent-color:${C.primary}; }
.price-cell { font-variant-numeric:tabular-nums; }
Expand Down Expand Up @@ -444,7 +440,7 @@ const PriceViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', selle
<Space size={8}>
<div className="position-relative">
<button className="chp" onClick={() => setShowExportMenu(!showExportMenu)} style={{ borderColor: C.primary, color: C.primary }} disabled={exporting}>
{exporting ? <Loader2 size={13} className="animate-spin" /> : <Download size={13} />} Export
{exporting ? <span className="icon-[svg-spinners--eclipse]" style={{ fontSize: 13 }} aria-hidden="true" /> : <Download size={13} />} Export
</button>
{showExportMenu && !exporting && (
<div className="position-absolute bg-white border rounded-lg shadow-xl p-1" style={{ top: '100%', right: 0, zIndex: 100, marginTop: 4, minWidth: 160 }}>
Expand Down Expand Up @@ -525,7 +521,7 @@ const PriceViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', selle

{loading && (
<span className="d-flex align-items-center gap-1 ms-auto" style={{ fontSize: 'var(--font-size-xs)', color: C.textSecondary }}>
<Loader2 size={13} className="animate-spin" /> Updating...
<span className="icon-[svg-spinners--eclipse]" style={{ fontSize: 13 }} aria-hidden="true" /> Updating...
</span>
)}
</div>
Expand Down Expand Up @@ -662,9 +658,7 @@ const PriceViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', selle
</tbody>
</table>
{(loading || hasMore) && (
<div ref={loaderRef} className="loader-pulse">
<div className="pulse-dot" /><div className="pulse-dot" /><div className="pulse-dot" />
</div>
<span ref={loaderRef} className="icon-[svg-spinners--eclipse]" style={{ fontSize: 18, color: C.primary, display: 'block', margin: '16px 0' }} aria-hidden="true" />
)}
</div>

Expand Down
12 changes: 4 additions & 8 deletions src/components/RatingViewModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as XLSX from 'xlsx';
import {
X, Star, Search, Download,
ArrowUpDown, ArrowUp, ArrowDown, FileText, MessageSquare,
Minus, TrendingUp, TrendingDown, SlidersHorizontal, Loader2, Store, Calendar
Minus, TrendingUp, TrendingDown, SlidersHorizontal, Store, Calendar
} from 'lucide-react';
import { asinApi, sellerApi } from '../services/api';
import InfiniteScrollSelect from './common/InfiniteScrollSelect';
Expand Down Expand Up @@ -288,10 +288,6 @@ const RatingViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', sell
.dd { font-size:10px; padding:2px 6px; border-radius:4px; text-align:center; min-width:48px; }
.dd-has { background:${C.warningBg}; color:#E65100; font-weight:600; }
.dd-no { color:#d1d5db; }
.loader-pulse { height:30px; display:flex; align-items:center; justify-content:center; gap:6px; margin:16px 0; }
.pulse-dot { width:6px; height:6px; background:${C.primary}; border-radius:50%; animation:pulse 1.5s infinite; }
.pulse-dot:nth-child(2) { animation-delay:0.2s; }
.pulse-dot:nth-child(3) { animation-delay:0.4s; }
@keyframes pulse { 0%,100% { transform:scale(1); opacity:0.3; } 50% { transform:scale(1.5); opacity:1; } }
.rt input[type="checkbox"] { width:13px; height:13px; cursor:pointer; accent-color:${C.primary}; }
.trend-badge { display:inline-flex; align-items:center; gap:3px; padding:2px 6px; border-radius:4px; font-weight:700; font-size:10px; }
Expand Down Expand Up @@ -323,7 +319,7 @@ const RatingViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', sell
</Space>
<Space size={8}>
<button className="chp" onClick={() => setShowExportMenu(!showExportMenu)} style={{ borderColor: C.warning, color: C.warning }} disabled={exporting}>
{exporting ? <Loader2 size={13} className="animate-spin" /> : <Download size={13} />} Export
{exporting ? <span className="icon-[svg-spinners--eclipse]" style={{ fontSize: 13 }} aria-hidden="true" /> : <Download size={13} />} Export
</button>
{showExportMenu && !exporting && (
<div className="position-absolute bg-white border rounded-lg shadow-xl p-1" style={{ top: '100%', right: 0, zIndex: 100, marginTop: 4, minWidth: 160 }}>
Expand Down Expand Up @@ -390,7 +386,7 @@ const RatingViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', sell
)}
{loading && (
<span className="d-flex align-items-center gap-1 ms-auto" style={{ fontSize: 'var(--font-size-xs)', color: C.textSecondary }}>
<Loader2 size={13} className="animate-spin" /> Synchronizing...
<span className="icon-[svg-spinners--eclipse]" style={{ fontSize: 13 }} aria-hidden="true" /> Synchronizing...
</span>
)}
</div>
Expand Down Expand Up @@ -525,7 +521,7 @@ const RatingViewModal = ({ isOpen, onClose, filters = {}, searchQuery = '', sell
</tbody>
</table>
{(loading || hasMore) && (
<div ref={loaderRef} className="loader-pulse"><div className="pulse-dot" /><div className="pulse-dot" /><div className="pulse-dot" /></div>
<span ref={loaderRef} className="icon-[svg-spinners--eclipse]" style={{ fontSize: 18, color: C.primary }} aria-hidden="true" />
)}
</div>

Expand Down
2 changes: 1 addition & 1 deletion src/components/ResultsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ const ResultsPage = ({ asins, onNavigate }) => {
>
{isCalculating ? (
<>
<span className="spinner-border spinner-border-sm me-1"></span>
<span className="icon-[svg-spinners--eclipse] me-1" aria-hidden="true"></span>
Calculating...
</>
) : (
Expand Down
11 changes: 6 additions & 5 deletions src/components/Spinner.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import React from 'react';
import { Spin } from 'antd';
import { LoadingOutlined } from '@ant-design/icons';

// Primary brand color of RetailOps is #1976D2
const antIcon = (
<LoadingOutlined
<span
className="icon-[svg-spinners--eclipse]"
style={{
fontSize: 28,
color: '#1976D2',
}}
spin
aria-hidden="true"
/>
);

const antIconSmall = (
<LoadingOutlined
<span
className="icon-[svg-spinners--eclipse]"
style={{
fontSize: 18,
color: '#1976D2',
}}
spin
aria-hidden="true"
/>
);

Expand Down
2 changes: 1 addition & 1 deletion src/components/Topbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ const Topbar = ({ toggleSidebar }) => {
title="Refresh data"
disabled={isRefreshing}
>
<RefreshCw size={20} className={isRefreshing ? 'spin' : ''} />
{isRefreshing ? <span className="icon-[svg-spinners--eclipse]" style={{ fontSize: 20 }} aria-hidden="true" /> : <RefreshCw size={20} />}
</button>

{/* Notifications */}
Expand Down
2 changes: 1 addition & 1 deletion src/components/UploadForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ const UploadForm = () => {
>
{isLoading ? (
<>
<span className="spinner-border spinner-border-sm me-2"></span>
<span className="icon-[svg-spinners--eclipse] me-2" aria-hidden="true"></span>
{uploadType === 'monthly' ? 'Uploading...' : uploadType === 'ads' ? 'Importing Ads...' : 'Syncing...'}
</>
) : (
Expand Down
2 changes: 1 addition & 1 deletion src/components/actions/ActionChat.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ const ActionChat = ({ actionId, messages: initialMessages = [], onSendMessage, u
onClick={handleSubmit}
>
{isSending ? (
<div className="spinner-border spinner-border-sm" role="status"></div>
<span className="icon-[svg-spinners--eclipse]" role="status" aria-label="Sending"></span>
) : (
<Send size={18} fill="currentColor" />
)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/actions/ActionDetailModal.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Modal, ModalBody, Spinner, Badge, Tooltip } from 'reactstrap';
import { Modal, ModalBody, Badge, Tooltip } from 'reactstrap';
import {
FileText, Target, TrendingUp, AlertTriangle,
Activity, ExternalLink, History, Sparkles,
Expand Down Expand Up @@ -299,7 +299,7 @@ const ActionDetailModal = ({ isOpen, onClose, action: item }) => {
<div className="ai-guide-box mt-2">
{loading ? (
<div className="text-center py-4">
<Spinner size="sm" color="primary" />
<span className="icon-[svg-spinners--eclipse]" style={{ fontSize: 16, color: '#0d6efd' }} aria-hidden="true" />
<div className="mt-2 smallest text-muted">Analyzing task data...</div>
</div>
) : error ? (
Expand Down
6 changes: 3 additions & 3 deletions src/components/actions/ActionListEnhanced.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useMemo } from 'react';
import { Edit2, Trash2, CheckCircle, Clock, AlertTriangle, Filter, Search, Play, Square, Plus, Sparkles, Loader2, Calendar, AlertCircle, ArrowDown, Minus, ThumbsUp, ThumbsDown, RotateCcw, FileText, RefreshCw, Eye } from 'lucide-react';
import { Edit2, Trash2, CheckCircle, Clock, AlertTriangle, Filter, Search, Play, Square, Plus, Sparkles, Calendar, AlertCircle, ArrowDown, Minus, ThumbsUp, ThumbsDown, RotateCcw, FileText, RefreshCw, Eye } from 'lucide-react';
import CompletionModal from './CompletionModal';
import ActionDetailModal from './ActionDetailModal';
import Popover from '../common/Popover';
Expand Down Expand Up @@ -496,11 +496,11 @@ const ActionList = ({
{type === 'KR' && data.metric === 'GMS' && (
<button
onClick={(e) => handleSync(e, data)}
className={`btn btn-sm btn-link p-0 ms-1 ${syncingKRs[data._id || data.id] ? 'fa-spin' : ''}`}
className="btn btn-sm btn-link p-0 ms-1"
title="Sync GMS Progress"
disabled={syncingKRs[data._id || data.id]}
>
<RefreshCw size={10} className={syncingKRs[data._id || data.id] ? 'animate-spin' : ''} />
{syncingKRs[data._id || data.id] ? <span className="icon-[svg-spinners--eclipse]" style={{ fontSize: 10 }} aria-hidden="true" /> : <RefreshCw size={10} />}
</button>
)}
</div>
Expand Down
Loading
Loading