@liiift-studio/sales-portal 1.3.1 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -184,10 +184,12 @@ export default function LoginForm({
184
184
  />
185
185
  <Button
186
186
  variant="contained"
187
+ disableElevation
187
188
  className="bold"
188
189
  onClick={handleLogin}
189
190
  disabled={loading}
190
191
  aria-label={buttonLabel}
192
+ fullWidth
191
193
  sx={{
192
194
  pl: '12px',
193
195
  m: 0,
@@ -199,13 +201,15 @@ export default function LoginForm({
199
201
  justifyContent: 'flex-start',
200
202
  bgcolor: 'var(--green)',
201
203
  height: '56px',
202
- width: '100%',
203
204
  borderRadius: '8px',
204
205
  opacity: 1,
205
206
  '&:hover': {
206
207
  color: 'var(--green)',
208
+ bgcolor: 'var(--green)',
207
209
  },
208
210
  boxShadow: 'none',
211
+ '&:active': { boxShadow: 'none' },
212
+ '&:focus': { boxShadow: 'none' },
209
213
  }}
210
214
  >
211
215
  {buttonLabel}
@@ -29,7 +29,7 @@ dayjs.extend(utc);
29
29
  * Sales dashboard component
30
30
  */
31
31
  export default function Sales(props) {
32
- const { month, year, designer, updateDate, categories = false, admin = false } = props;
32
+ const { month, year, designer, updateDate, categories = false, admin = false, fetchDelay = 0 } = props;
33
33
 
34
34
  // UI State
35
35
  const [loadingStates, setLoadingStates] = useState({
@@ -44,9 +44,13 @@ export default function Sales(props) {
44
44
  });
45
45
  const [message, setMessage] = useState('');
46
46
  const [previousSalesError, setPreviousSalesError] = useState('');
47
+ const [retryInfo, setRetryInfo] = useState({ retrying: false, attempt: 0, label: '' });
47
48
  const [date, setDate] = useState(null);
48
49
  const [displayLosses, setDisplayLosses] = useState(false);
49
50
 
51
+ // Max retry attempts for failed API calls
52
+ const MAX_RETRIES = 3;
53
+
50
54
  // Helper to update individual loading states
51
55
  const updateLoadingState = useCallback((key, value) => {
52
56
  setLoadingStates(prev => ({
@@ -147,21 +151,40 @@ export default function Sales(props) {
147
151
  }
148
152
  }, [date, designer?.admin, month, year, updateDate]);
149
153
 
154
+ // Fetch with retry logic for rate-limited requests
155
+ async function fetchWithRetry(url, options, label, attempt = 0) {
156
+ const response = await fetch(url, options);
157
+
158
+ if (!response.ok && attempt < MAX_RETRIES) {
159
+ const delay = Math.pow(2, attempt) * 1000 + Math.random() * 500;
160
+ setRetryInfo({ retrying: true, attempt: attempt + 1, label });
161
+ await new Promise(resolve => setTimeout(resolve, delay));
162
+ return fetchWithRetry(url, options, label, attempt + 1);
163
+ }
164
+
165
+ setRetryInfo({ retrying: false, attempt: 0, label: '' });
166
+ return response;
167
+ }
168
+
150
169
  // Fetch current period sales data
151
170
  async function fetchSales(){
152
171
  if (!designer?.user || !designer?.password || !date) return;
153
172
  updateLoadingState('salesData', true);
154
173
  try {
155
- const response = await fetch('/api/sales-portal/getSales', {
156
- method: 'POST',
157
- headers: { 'Content-Type': 'application/json' },
158
- body: JSON.stringify({
159
- user: designer?.user,
160
- password: designer?.password,
161
- date,
162
- admin: designer?.admin
163
- }),
164
- });
174
+ const response = await fetchWithRetry(
175
+ '/api/sales-portal/getSales',
176
+ {
177
+ method: 'POST',
178
+ headers: { 'Content-Type': 'application/json' },
179
+ body: JSON.stringify({
180
+ user: designer?.user,
181
+ password: designer?.password,
182
+ date,
183
+ admin: designer?.admin
184
+ }),
185
+ },
186
+ 'Sales data'
187
+ );
165
188
  const data = await response.json();
166
189
 
167
190
  if (data.success) {
@@ -182,15 +205,15 @@ export default function Sales(props) {
182
205
  if (!designer?.user || !designer?.password || !date) {
183
206
  return;
184
207
  }
185
-
208
+
186
209
  updateLoadingState('previousSalesData', true);
187
210
  setPreviousSalesError(''); // Clear any previous errors
188
-
211
+
189
212
  // Calculate previous month date with more robust handling
190
213
  const previousDate = new Date(date.getTime()); // Create a copy
191
214
  const currentMonth = previousDate.getUTCMonth();
192
215
  const currentYear = previousDate.getUTCFullYear();
193
-
216
+
194
217
  // Handle edge cases for month boundaries
195
218
  if (currentMonth === 0) {
196
219
  // January -> December of previous year
@@ -199,23 +222,23 @@ export default function Sales(props) {
199
222
  } else {
200
223
  previousDate.setUTCMonth(currentMonth - 1);
201
224
  }
202
-
225
+
203
226
  try {
204
- const response = await fetch('/api/sales-portal/getSales', {
205
- method: 'POST',
206
- headers: { 'Content-Type': 'application/json' },
207
- body: JSON.stringify({
208
- user: designer?.user,
209
- password: designer?.password,
210
- date: previousDate,
211
- admin: designer?.admin
212
- }),
213
- });
214
-
215
- if (!response.ok) {
216
- throw new Error(`HTTP error! status: ${response.status}`);
217
- }
218
-
227
+ const response = await fetchWithRetry(
228
+ '/api/sales-portal/getSales',
229
+ {
230
+ method: 'POST',
231
+ headers: { 'Content-Type': 'application/json' },
232
+ body: JSON.stringify({
233
+ user: designer?.user,
234
+ password: designer?.password,
235
+ date: previousDate,
236
+ admin: designer?.admin
237
+ }),
238
+ },
239
+ 'Previous sales data'
240
+ );
241
+
219
242
  const data = await response.json();
220
243
 
221
244
  if (data.success) {
@@ -237,6 +260,13 @@ export default function Sales(props) {
237
260
  }
238
261
 
239
262
  useEffect(() => {
263
+ if (fetchDelay > 0) {
264
+ const timer = setTimeout(() => {
265
+ fetchSales();
266
+ fetchPreviousSales();
267
+ }, fetchDelay);
268
+ return () => clearTimeout(timer);
269
+ }
240
270
  fetchSales();
241
271
  fetchPreviousSales();
242
272
  }, [designer?.user, designer?.password, designer?.admin, date]);
@@ -623,6 +653,42 @@ export default function Sales(props) {
623
653
  </Box>
624
654
  </Box>
625
655
 
656
+ {/* Retry Message */}
657
+ {retryInfo.retrying && (
658
+ <Box sx={{ width: '100%', pb: 4 }}>
659
+ <Typography variant='body2' sx={{
660
+ color: 'var(--grey800, #666)',
661
+ mt: 2,
662
+ display: 'flex',
663
+ alignItems: 'center',
664
+ gap: 1,
665
+ }}>
666
+ <Box component="span" sx={{
667
+ '@keyframes dotPulse': {
668
+ '0%, 20%': { opacity: 0 },
669
+ '50%': { opacity: 1 },
670
+ '100%': { opacity: 0 },
671
+ },
672
+ display: 'inline-flex',
673
+ gap: '2px',
674
+ '& span': {
675
+ width: '4px',
676
+ height: '4px',
677
+ borderRadius: '50%',
678
+ bgcolor: 'var(--grey800, #666)',
679
+ display: 'inline-block',
680
+ animation: 'dotPulse 1.4s infinite',
681
+ },
682
+ '& span:nth-of-type(2)': { animationDelay: '0.2s' },
683
+ '& span:nth-of-type(3)': { animationDelay: '0.4s' },
684
+ }}>
685
+ <span /><span /><span />
686
+ </Box>
687
+ {retryInfo.label} — retrying (attempt {retryInfo.attempt} of {MAX_RETRIES})
688
+ </Typography>
689
+ </Box>
690
+ )}
691
+
626
692
  {/* Error Messages */}
627
693
  {!!(message !== '') &&
628
694
  <Box sx={{ width: '100%', pb: 8 }}>
@@ -637,7 +703,7 @@ export default function Sales(props) {
637
703
  </Box>
638
704
  }
639
705
  </Box>
640
- ), [loading, sales, total, designer, date, message, revenueChangePercent, previousSales]);
706
+ ), [loading, sales, total, designer, date, message, revenueChangePercent, previousSales, retryInfo]);
641
707
 
642
708
  return (
643
709
  <LocalizationProvider dateAdapter={AdapterDayjs}>
@@ -57,7 +57,7 @@ export default function SalesPortalPage({
57
57
  const isLoggedIn = !!designers;
58
58
 
59
59
  const content = (
60
- <>
60
+ <Box sx={{ minHeight: '100vh' }}>
61
61
  {/* Login Modal */}
62
62
  <LoginForm
63
63
  open={!isLoggedIn}
@@ -130,11 +130,12 @@ export default function SalesPortalPage({
130
130
  month={month}
131
131
  year={year}
132
132
  updateDate={updateDate}
133
+ fetchDelay={admin ? i * 2000 : 0}
133
134
  />
134
135
  ))}
135
136
  </Box>
136
137
  )}
137
- </>
138
+ </Box>
138
139
  );
139
140
 
140
141
  // Optionally wrap with theme provider
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liiift-studio/sales-portal",
3
- "version": "1.3.1",
3
+ "version": "1.3.4",
4
4
  "description": "Centralized sales portal package for Liiift Studio projects",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",