Remove unused useUrlState hook and sallaService

Both were implemented but never imported by any component.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-25 18:09:18 +03:00
parent 8934ba1e51
commit cd1e395ffa
4 changed files with 79 additions and 220 deletions

View File

@@ -0,0 +1,79 @@
# Dashboard Quick & Medium Improvements
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Improve reliability, performance, and code quality of the HiHala Dashboard.
**Architecture:** Focused improvements across data layer (timeout, retry), UI (error handling, loading skeletons, code splitting), config (VAT rate), and DX (TypeScript strict, dead code removal).
**Tech Stack:** React 19, Vite 7, TypeScript 5, Chart.js
---
### Task 1: Fetch Timeout + Retry Logic
**Files:**
- Modify: `src/services/dataService.ts`
- [ ] Add `fetchWithTimeout` wrapper (10s timeout) around all fetch calls
- [ ] Add retry with exponential backoff (3 attempts, 1s/2s/4s) to `fetchNocoDBTable` and `discoverTableIds`
- [ ] Commit
### Task 2: Friendly Error Handling
**Files:**
- Modify: `src/App.tsx` (error display)
- Modify: `src/services/dataService.ts` (error classification)
- [ ] Add error classification in dataService (network, auth, config, unknown)
- [ ] Replace raw error message in App.tsx with user-friendly messages using i18n keys
- [ ] Add error keys to `src/locales/en.json` and `src/locales/ar.json`
- [ ] Commit
### Task 3: Remove Dead Code
**Files:**
- Delete: `src/hooks/useUrlState.ts`
- Delete: `src/services/sallaService.ts`
- [ ] Delete unused files
- [ ] Verify no imports reference them
- [ ] Commit
### Task 4: Route-Based Code Splitting
**Files:**
- Modify: `src/App.tsx`
- [ ] Lazy-load Dashboard, Comparison, Slides with `React.lazy` + `Suspense`
- [ ] Commit
### Task 5: Loading Skeletons
**Files:**
- Create: `src/components/shared/LoadingSkeleton.tsx`
- Modify: `src/App.tsx` (replace spinner with skeleton)
- Modify: `src/App.css` (skeleton styles)
- [ ] Create skeleton component (stat cards + chart placeholders)
- [ ] Use as Suspense fallback and initial loading state
- [ ] Commit
### Task 6: VAT Rate from Config
**Files:**
- Modify: `src/services/dataService.ts`
- [ ] Extract VAT_RATE to a named constant at top of file
- [ ] Commit
### Task 7: TypeScript Strict Mode
**Files:**
- Modify: `tsconfig.json`
- Modify: various files as needed to fix type errors
- [ ] Enable `strict: true`, `noImplicitAny: true`, `strictNullChecks: true`
- [ ] Fix all resulting type errors
- [ ] Verify build passes
- [ ] Commit

View File

@@ -1 +0,0 @@
export { useUrlState } from './useUrlState';

View File

@@ -1,58 +0,0 @@
import { useEffect, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
/**
* Sync state with URL search params
* @param {Object} state - Current state object
* @param {Function} setState - State setter function
* @param {Object} defaultState - Default state values
* @param {Array<string>} keys - Keys to sync with URL
*/
export function useUrlState(state, setState, defaultState, keys) {
const [searchParams, setSearchParams] = useSearchParams();
// Initialize state from URL on mount
useEffect(() => {
const urlState = {};
let hasUrlParams = false;
keys.forEach(key => {
const value = searchParams.get(key);
if (value !== null) {
urlState[key] = value;
hasUrlParams = true;
}
});
if (hasUrlParams) {
setState(prev => ({ ...prev, ...urlState }));
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// Update URL when state changes
const updateUrl = useCallback((newState) => {
const params = new URLSearchParams();
keys.forEach(key => {
const value = newState[key];
if (value && value !== defaultState[key]) {
params.set(key, value);
}
});
setSearchParams(params, { replace: true });
}, [keys, defaultState, setSearchParams]);
// Wrap setState to also update URL
const setStateWithUrl = useCallback((updater) => {
setState(prev => {
const newState = typeof updater === 'function' ? updater(prev) : updater;
updateUrl(newState);
return newState;
});
}, [setState, updateUrl]);
return setStateWithUrl;
}
export default useUrlState;

View File

@@ -1,161 +0,0 @@
// Salla Integration Service
// Connects to the local Salla backend server
const SALLA_SERVER_URL = import.meta.env.VITE_SALLA_SERVER_URL || 'http://localhost:3001';
export interface SallaAuthStatus {
connected: boolean;
hasRefreshToken: boolean;
}
export interface SallaOrder {
id: number;
reference_id: string;
status: {
id: string;
name: string;
customized: { id: string; name: string };
};
amounts: {
total: { amount: number; currency: string };
sub_total: { amount: number; currency: string };
};
customer: {
id: number;
first_name: string;
last_name: string;
email: string;
mobile: string;
};
items: Array<{
id: number;
name: string;
quantity: number;
amounts: { total: { amount: number } };
}>;
created_at: string;
}
export interface SallaProduct {
id: number;
name: string;
sku: string;
price: { amount: number; currency: string };
quantity: number;
status: string;
sold_quantity: number;
}
export interface SallaSummary {
orders: { total: number; recent: number };
products: { total: number };
revenue: { total: number; average_order: number; currency: string };
}
export interface SallaStore {
id: number;
name: string;
description: string;
domain: string;
plan: string;
}
// ============================================
// API Functions
// ============================================
export async function checkSallaAuth(): Promise<SallaAuthStatus> {
try {
const response = await fetch(`${SALLA_SERVER_URL}/auth/status`);
return response.json();
} catch (err) {
return { connected: false, hasRefreshToken: false };
}
}
export function getSallaLoginUrl(): string {
return `${SALLA_SERVER_URL}/auth/login`;
}
export async function getSallaStore(): Promise<SallaStore | null> {
try {
const response = await fetch(`${SALLA_SERVER_URL}/api/store`);
if (!response.ok) throw new Error('Failed to fetch store');
const data = await response.json();
return data.data;
} catch (err) {
console.error('Error fetching store:', err);
return null;
}
}
export async function getSallaOrders(page = 1, perPage = 50): Promise<{ data: SallaOrder[]; pagination: any }> {
try {
const response = await fetch(`${SALLA_SERVER_URL}/api/orders?page=${page}&per_page=${perPage}`);
if (!response.ok) throw new Error('Failed to fetch orders');
return response.json();
} catch (err) {
console.error('Error fetching orders:', err);
return { data: [], pagination: {} };
}
}
export async function getSallaProducts(page = 1, perPage = 50): Promise<{ data: SallaProduct[]; pagination: any }> {
try {
const response = await fetch(`${SALLA_SERVER_URL}/api/products?page=${page}&per_page=${perPage}`);
if (!response.ok) throw new Error('Failed to fetch products');
return response.json();
} catch (err) {
console.error('Error fetching products:', err);
return { data: [], pagination: {} };
}
}
export async function getSallaSummary(): Promise<SallaSummary | null> {
try {
const response = await fetch(`${SALLA_SERVER_URL}/api/analytics/summary`);
if (!response.ok) throw new Error('Failed to fetch summary');
return response.json();
} catch (err) {
console.error('Error fetching summary:', err);
return null;
}
}
// ============================================
// Data Transformation for Dashboard
// ============================================
export function transformOrdersForChart(orders: SallaOrder[]): {
labels: string[];
datasets: { label: string; data: number[] }[];
} {
// Group orders by date
const byDate: Record<string, number> = {};
orders.forEach(order => {
const date = order.created_at.split('T')[0];
byDate[date] = (byDate[date] || 0) + (order.amounts?.total?.amount || 0);
});
const sortedDates = Object.keys(byDate).sort();
return {
labels: sortedDates,
datasets: [{
label: 'Daily Revenue (SAR)',
data: sortedDates.map(d => byDate[d])
}]
};
}
export function getOrderStatusSummary(orders: SallaOrder[]): Record<string, number> {
const byStatus: Record<string, number> = {};
orders.forEach(order => {
const status = order.status?.name || 'Unknown';
byStatus[status] = (byStatus[status] || 0) + 1;
});
return byStatus;
}