Frontend changes (phase F1)
This commit is contained in:
@@ -14,7 +14,7 @@ Audit-driven plan to (a) reduce 12 PM2 processes to 3 application servers + 1 au
|
|||||||
| 4 — Build `dashboard-server` (the merge) | Not started | klaviyo/meta/google/typeform still run as 4 separate PM2 apps |
|
| 4 — Build `dashboard-server` (the merge) | Not started | klaviyo/meta/google/typeform still run as 4 separate PM2 apps |
|
||||||
| 5 — Convert `acot-server` to ESM | Not started | |
|
| 5 — Convert `acot-server` to ESM | Not started | |
|
||||||
| 6 — Auth hardening | **Complete (code) — gated on Phase F1** | All in-process items wired (rate-limit, JWT precondition, CORS lockdown, request-log, upload allowlist, `requirePermission` on sensitive routes, permissions seed migration). `authenticate()` is live on `/api/*`. Server-side artefacts (Caddyfile, ecosystem.cjs) written to `inventory-server/deploy/` for review. 6.11 (audit logging) deferred. **Frontend cannot use the app until Phase F1 ships** — see below |
|
| 6 — Auth hardening | **Complete (code) — gated on Phase F1** | All in-process items wired (rate-limit, JWT precondition, CORS lockdown, request-log, upload allowlist, `requirePermission` on sensitive routes, permissions seed migration). `authenticate()` is live on `/api/*`. Server-side artefacts (Caddyfile, ecosystem.cjs) written to `inventory-server/deploy/` for review. 6.11 (audit logging) deferred. **Frontend cannot use the app until Phase F1 ships** — see below |
|
||||||
| **F1 — Frontend fetch wrapper (NEW)** | **Not started — CRITICAL** | Frontend uses raw `fetch()` in ~220 sites; only 7 send `Authorization: Bearer`. With Phase 6's `authenticate()` middleware live, every refresh 401s until the frontend uniformly attaches the token. See "Phase F1" below |
|
| **F1 — Frontend fetch wrapper (NEW)** | **Complete (code) — 2026-05-23** | Wrappers at `inventory/src/utils/api.ts` (`apiFetch`) and `inventory/src/utils/apiClient.ts` (axios instance). 170 `fetch()` sites across 76 files migrated to `apiFetch`; 32 `axios.*` sites across 11 files migrated to `apiClient`. AuthContext `/login`+`/me`, App.tsx `/me`, and `services/apiv2.ts` (external PHP backend) intentionally left as raw `fetch`. Type-check + production build pass clean |
|
||||||
| 7 — Caddyfile final form | Partial | Proposed file at `inventory-server/deploy/Caddyfile.proposed`. Apply blocked on F1 (forward_auth would 401 every page load until then) |
|
| 7 — Caddyfile final form | Partial | Proposed file at `inventory-server/deploy/Caddyfile.proposed`. Apply blocked on F1 (forward_auth would 401 every page load until then) |
|
||||||
| 8 — ecosystem.config.cjs final form | Partial | Proposed at `inventory-server/deploy/ecosystem.config.cjs.proposed`. Includes Phase 6.4 JWT_SECRET footgun fix and 6.10 lt-wordlist token move |
|
| 8 — ecosystem.config.cjs final form | Partial | Proposed at `inventory-server/deploy/ecosystem.config.cjs.proposed`. Includes Phase 6.4 JWT_SECRET footgun fix and 6.10 lt-wordlist token move |
|
||||||
|
|
||||||
@@ -519,7 +519,7 @@ Already have `import-audit-log` and `product-editor-audit-log` tables. Extend th
|
|||||||
|
|
||||||
## Phase F1 — Frontend fetch wrapper (NEW — 2026-05-23)
|
## Phase F1 — Frontend fetch wrapper (NEW — 2026-05-23)
|
||||||
|
|
||||||
Status: **Not started. CRITICAL. Blocks the Phase 3+6 deploy from being usable.**
|
Status: **Complete (code) — 2026-05-23.** Two wrappers landed at `inventory/src/utils/api.ts` and `inventory/src/utils/apiClient.ts`. Migration touched 87 files (76 fetch, 11 axios) covering ~200 call sites. Type-check clean; production build clean. Intentional exclusions: AuthContext `/login`+`/me` (own auth flow), App.tsx initial `/me` session check, and `services/apiv2.ts` (calls the separate PHP backend at backend.acherryontop.com which has its own cookie auth, out of scope per the plan). Ready to ship in the same deploy window as Phase 3+6.
|
||||||
|
|
||||||
### The discovery
|
### The discovery
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
@@ -34,7 +35,7 @@ export function AgingSellThrough() {
|
|||||||
const { data, isLoading, isError } = useQuery<AgingCohort[]>({
|
const { data, isLoading, isError } = useQuery<AgingCohort[]>({
|
||||||
queryKey: ['aging-sell-through'],
|
queryKey: ['aging-sell-through'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/aging`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/aging`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch aging data');
|
if (!response.ok) throw new Error('Failed to fetch aging data');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
@@ -45,7 +46,7 @@ export function CapitalEfficiency() {
|
|||||||
const { data, isLoading, isError } = useQuery<EfficiencyData>({
|
const { data, isLoading, isError } = useQuery<EfficiencyData>({
|
||||||
queryKey: ['capital-efficiency'],
|
queryKey: ['capital-efficiency'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/efficiency`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/efficiency`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch capital efficiency');
|
if (!response.ok) throw new Error('Failed to fetch capital efficiency');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
@@ -34,7 +35,7 @@ export function DiscountImpact() {
|
|||||||
const { data, isLoading, isError } = useQuery<DiscountRow[]>({
|
const { data, isLoading, isError } = useQuery<DiscountRow[]>({
|
||||||
queryKey: ['discount-impact'],
|
queryKey: ['discount-impact'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/discounts`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/discounts`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch discount data');
|
if (!response.ok) throw new Error('Failed to fetch discount data');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
@@ -55,7 +56,7 @@ export function GrowthMomentum() {
|
|||||||
const { data, isLoading, isError } = useQuery<GrowthData>({
|
const { data, isLoading, isError } = useQuery<GrowthData>({
|
||||||
queryKey: ['growth-momentum'],
|
queryKey: ['growth-momentum'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/growth`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/growth`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch growth data');
|
if (!response.ok) throw new Error('Failed to fetch growth data');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
@@ -39,7 +40,7 @@ export function InventoryFlow() {
|
|||||||
const { data, isLoading, isError } = useQuery<FlowPoint[]>({
|
const { data, isLoading, isError } = useQuery<FlowPoint[]>({
|
||||||
queryKey: ['inventory-flow', period],
|
queryKey: ['inventory-flow', period],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/flow?period=${period}`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/flow?period=${period}`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch inventory flow');
|
if (!response.ok) throw new Error('Failed to fetch inventory flow');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
@@ -35,7 +36,7 @@ export function InventoryTrends() {
|
|||||||
const { data, isLoading, isError } = useQuery<TrendPoint[]>({
|
const { data, isLoading, isError } = useQuery<TrendPoint[]>({
|
||||||
queryKey: ['inventory-trends', period],
|
queryKey: ['inventory-trends', period],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/inventory-trends?period=${period}`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/inventory-trends?period=${period}`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch inventory trends');
|
if (!response.ok) throw new Error('Failed to fetch inventory trends');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
@@ -36,7 +37,7 @@ export function InventoryValueTrend() {
|
|||||||
const { data, isLoading, isError } = useQuery<ValuePoint[]>({
|
const { data, isLoading, isError } = useQuery<ValuePoint[]>({
|
||||||
queryKey: ['inventory-value', period],
|
queryKey: ['inventory-value', period],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/inventory-value?period=${period}`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/inventory-value?period=${period}`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch inventory value');
|
if (!response.ok) throw new Error('Failed to fetch inventory value');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
@@ -42,7 +43,7 @@ export function PortfolioAnalysis() {
|
|||||||
const { data, isLoading, isError } = useQuery<PortfolioData>({
|
const { data, isLoading, isError } = useQuery<PortfolioData>({
|
||||||
queryKey: ['portfolio-analysis'],
|
queryKey: ['portfolio-analysis'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/portfolio`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/portfolio`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch portfolio analysis');
|
if (!response.ok) throw new Error('Failed to fetch portfolio analysis');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
@@ -54,7 +55,7 @@ export function SeasonalPatterns() {
|
|||||||
const { data, isLoading, isError } = useQuery<SeasonalData>({
|
const { data, isLoading, isError } = useQuery<SeasonalData>({
|
||||||
queryKey: ['seasonal-patterns'],
|
queryKey: ['seasonal-patterns'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/seasonal`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/seasonal`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch seasonal data');
|
if (!response.ok) throw new Error('Failed to fetch seasonal data');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
@@ -72,7 +73,7 @@ export function StockHealth() {
|
|||||||
const { data, isLoading, isError } = useQuery<StockHealthData>({
|
const { data, isLoading, isError } = useQuery<StockHealthData>({
|
||||||
queryKey: ['stock-health'],
|
queryKey: ['stock-health'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/stock-health`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/stock-health`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch stock health');
|
if (!response.ok) throw new Error('Failed to fetch stock health');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
@@ -49,7 +50,7 @@ export function StockoutRisk() {
|
|||||||
const { data, isLoading, isError } = useQuery<StockoutRiskData>({
|
const { data, isLoading, isError } = useQuery<StockoutRiskData>({
|
||||||
queryKey: ['stockout-risk'],
|
queryKey: ['stockout-risk'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/stockout-risk`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/stockout-risk`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch stockout risk');
|
if (!response.ok) throw new Error('Failed to fetch stockout risk');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
@@ -73,7 +74,7 @@ export function ChatRoom({ roomId, selectedUserId }: ChatRoomProps) {
|
|||||||
|
|
||||||
const fetchRoom = async () => {
|
const fetchRoom = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.chatUrl}/rooms/${roomId}?userId=${selectedUserId}`);
|
const response = await apiFetch(`${config.chatUrl}/rooms/${roomId}?userId=${selectedUserId}`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
@@ -101,7 +102,7 @@ export function ChatRoom({ roomId, selectedUserId }: ChatRoomProps) {
|
|||||||
params.set('before', before);
|
params.set('before', before);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${config.chatUrl}/rooms/${roomId}/messages?${params}`);
|
const response = await apiFetch(`${config.chatUrl}/rooms/${roomId}/messages?${params}`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
@@ -136,7 +137,7 @@ export function ChatRoom({ roomId, selectedUserId }: ChatRoomProps) {
|
|||||||
|
|
||||||
const loadAttachments = async (messageIds: number[]) => {
|
const loadAttachments = async (messageIds: number[]) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.chatUrl}/messages/attachments`, {
|
const response = await apiFetch(`${config.chatUrl}/messages/attachments`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -172,7 +173,7 @@ export function ChatRoom({ roomId, selectedUserId }: ChatRoomProps) {
|
|||||||
if (!searchQuery || searchQuery.length < 2) return;
|
if (!searchQuery || searchQuery.length < 2) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await apiFetch(
|
||||||
`${config.chatUrl}/users/${selectedUserId}/search?q=${encodeURIComponent(searchQuery)}&limit=20`
|
`${config.chatUrl}/users/${selectedUserId}/search?q=${encodeURIComponent(searchQuery)}&limit=20`
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Loader2, Hash, Lock, Users, MessageSquare } from 'lucide-react';
|
import { Loader2, Hash, Lock, Users, MessageSquare } from 'lucide-react';
|
||||||
@@ -33,7 +34,7 @@ export function ChatTest({ selectedUserId }: ChatTestProps) {
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.chatUrl}/users/${selectedUserId}/rooms`);
|
const response = await apiFetch(`${config.chatUrl}/users/${selectedUserId}/rooms`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import { Loader2, Hash, Users, MessageSquare, MessageCircle, Users2 } from 'lucide-react';
|
import { Loader2, Hash, Users, MessageSquare, MessageCircle, Users2 } from 'lucide-react';
|
||||||
@@ -50,7 +51,7 @@ export function RoomList({ selectedUserId, selectedRoomId, onRoomSelect }: RoomL
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.chatUrl}/users/${selectedUserId}/rooms`);
|
const response = await apiFetch(`${config.chatUrl}/users/${selectedUserId}/rooms`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ import {
|
|||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import axios from "axios";
|
import { apiClient } from "@/utils/apiClient";
|
||||||
import type { SearchProduct } from "@/components/product-editor/types";
|
import type { SearchProduct } from "@/components/product-editor/types";
|
||||||
import {
|
import {
|
||||||
parsePastedTable,
|
parsePastedTable,
|
||||||
@@ -165,7 +165,7 @@ export function AddProductsDialog({
|
|||||||
setIsSearching(true);
|
setIsSearching(true);
|
||||||
setSearched(true);
|
setSearched(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get<{ results: QuickSearchResult[]; total: number }>(
|
const res = await apiClient.get<{ results: QuickSearchResult[]; total: number }>(
|
||||||
"/api/products/search",
|
"/api/products/search",
|
||||||
{ params: { q: query } }
|
{ params: { q: query } }
|
||||||
);
|
);
|
||||||
@@ -182,7 +182,7 @@ export function AddProductsDialog({
|
|||||||
async (result: QuickSearchResult) => {
|
async (result: QuickSearchResult) => {
|
||||||
// Look up full details to pull MOQ (to default qty sensibly)
|
// Look up full details to pull MOQ (to default qty sensibly)
|
||||||
try {
|
try {
|
||||||
const res = await axios.get<SearchProduct[]>("/api/import/search-products", {
|
const res = await apiClient.get<SearchProduct[]>("/api/import/search-products", {
|
||||||
params: { pid: result.pid },
|
params: { pid: result.pid },
|
||||||
});
|
});
|
||||||
const full = (res.data ?? [])[0];
|
const full = (res.data ?? [])[0];
|
||||||
@@ -204,7 +204,7 @@ export function AddProductsDialog({
|
|||||||
if (pids.length === 0) return;
|
if (pids.length === 0) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.get<SearchProduct[]>("/api/import/search-products", {
|
const res = await apiClient.get<SearchProduct[]>("/api/import/search-products", {
|
||||||
params: { pid: pids.join(",") },
|
params: { pid: pids.join(",") },
|
||||||
});
|
});
|
||||||
const items = (res.data ?? []).map((p) => ({
|
const items = (res.data ?? []).map((p) => ({
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { ComboboxField } from "@/components/product-editor/ComboboxField";
|
import { ComboboxField } from "@/components/product-editor/ComboboxField";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import type { FieldOption } from "@/components/product-editor/types";
|
import type { FieldOption } from "@/components/product-editor/types";
|
||||||
@@ -30,7 +31,7 @@ export function SupplierSelector({
|
|||||||
const { data, isLoading, error } = useQuery({
|
const { data, isLoading, error } = useQuery({
|
||||||
queryKey: ["field-options"],
|
queryKey: ["field-options"],
|
||||||
queryFn: async (): Promise<FieldOptionsResponse> => {
|
queryFn: async (): Promise<FieldOptionsResponse> => {
|
||||||
const res = await fetch("/api/import/field-options");
|
const res = await apiFetch("/api/import/field-options");
|
||||||
if (!res.ok) throw new Error("Failed to load suppliers");
|
if (!res.ok) throw new Error("Failed to load suppliers");
|
||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* full PoLineItem rows via `GET /api/products/batch`.
|
* full PoLineItem rows via `GET /api/products/batch`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import axios from "axios";
|
import { apiClient } from "@/utils/apiClient";
|
||||||
import type {
|
import type {
|
||||||
RawIdentifierRow,
|
RawIdentifierRow,
|
||||||
ResolveResult,
|
ResolveResult,
|
||||||
@@ -66,7 +66,7 @@ export async function resolveIdentifiers(
|
|||||||
|
|
||||||
const identifiers = rows.map((r) => r.identifier);
|
const identifiers = rows.map((r) => r.identifier);
|
||||||
|
|
||||||
const res = await axios.post<ResolveApiResponse>(
|
const res = await apiClient.post<ResolveApiResponse>(
|
||||||
"/api/products/resolve-identifiers",
|
"/api/products/resolve-identifiers",
|
||||||
{ identifiers }
|
{ identifiers }
|
||||||
);
|
);
|
||||||
@@ -120,7 +120,7 @@ export async function fetchBatchProducts(
|
|||||||
|
|
||||||
for (let i = 0; i < uniqPids.length; i += BATCH_LOOKUP_MAX_PIDS) {
|
for (let i = 0; i < uniqPids.length; i += BATCH_LOOKUP_MAX_PIDS) {
|
||||||
const chunk = uniqPids.slice(i, i + BATCH_LOOKUP_MAX_PIDS);
|
const chunk = uniqPids.slice(i, i + BATCH_LOOKUP_MAX_PIDS);
|
||||||
const res = await axios.get<Omit<PoLineItem, "qty">[]>(
|
const res = await apiClient.get<Omit<PoLineItem, "qty">[]>(
|
||||||
"/api/products/batch",
|
"/api/products/batch",
|
||||||
{ params: { pids: chunk.join(",") } }
|
{ params: { pids: chunk.join(",") } }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useMemo } from "react"
|
import { useState, useMemo } from "react"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import {
|
import {
|
||||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger,
|
Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger,
|
||||||
@@ -18,7 +19,7 @@ function useCampaignData(open: boolean) {
|
|||||||
const campaigns = useQuery<CampaignsResponse>({
|
const campaigns = useQuery<CampaignsResponse>({
|
||||||
queryKey: ["newsletter-campaigns"],
|
queryKey: ["newsletter-campaigns"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`${config.apiUrl}/newsletter/campaigns`)
|
const res = await apiFetch(`${config.apiUrl}/newsletter/campaigns`)
|
||||||
if (!res.ok) throw new Error("Failed to fetch campaigns")
|
if (!res.ok) throw new Error("Failed to fetch campaigns")
|
||||||
return res.json()
|
return res.json()
|
||||||
},
|
},
|
||||||
@@ -29,7 +30,7 @@ function useCampaignData(open: boolean) {
|
|||||||
const products = useQuery<{ products: ProductAggregate[] }>({
|
const products = useQuery<{ products: ProductAggregate[] }>({
|
||||||
queryKey: ["newsletter-campaigns-products"],
|
queryKey: ["newsletter-campaigns-products"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`${config.apiUrl}/newsletter/campaigns/products`)
|
const res = await apiFetch(`${config.apiUrl}/newsletter/campaigns/products`)
|
||||||
if (!res.ok) throw new Error("Failed to fetch")
|
if (!res.ok) throw new Error("Failed to fetch")
|
||||||
return res.json()
|
return res.json()
|
||||||
},
|
},
|
||||||
@@ -40,7 +41,7 @@ function useCampaignData(open: boolean) {
|
|||||||
const links = useQuery<{ links: LinkAggregate[] }>({
|
const links = useQuery<{ links: LinkAggregate[] }>({
|
||||||
queryKey: ["newsletter-campaigns-links"],
|
queryKey: ["newsletter-campaigns-links"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`${config.apiUrl}/newsletter/campaigns/links`)
|
const res = await apiFetch(`${config.apiUrl}/newsletter/campaigns/links`)
|
||||||
if (!res.ok) throw new Error("Failed to fetch")
|
if (!res.ok) throw new Error("Failed to fetch")
|
||||||
return res.json()
|
return res.json()
|
||||||
},
|
},
|
||||||
@@ -51,7 +52,7 @@ function useCampaignData(open: boolean) {
|
|||||||
const brands = useQuery<{ brands: BrandAggregate[] }>({
|
const brands = useQuery<{ brands: BrandAggregate[] }>({
|
||||||
queryKey: ["newsletter-campaigns-brands"],
|
queryKey: ["newsletter-campaigns-brands"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`${config.apiUrl}/newsletter/campaigns/brands`)
|
const res = await apiFetch(`${config.apiUrl}/newsletter/campaigns/brands`)
|
||||||
if (!res.ok) throw new Error("Failed to fetch")
|
if (!res.ok) throw new Error("Failed to fetch")
|
||||||
return res.json()
|
return res.json()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
@@ -18,7 +19,7 @@ export function NewsletterStats() {
|
|||||||
const { data } = useQuery<Stats>({
|
const { data } = useQuery<Stats>({
|
||||||
queryKey: ["newsletter-stats"],
|
queryKey: ["newsletter-stats"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`${config.apiUrl}/newsletter/stats`)
|
const res = await apiFetch(`${config.apiUrl}/newsletter/stats`)
|
||||||
if (!res.ok) throw new Error("Failed to fetch stats")
|
if (!res.ok) throw new Error("Failed to fetch stats")
|
||||||
return res.json()
|
return res.json()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useState, useMemo, useContext } from "react"
|
import { useState, useMemo, useContext } from "react"
|
||||||
import {
|
import {
|
||||||
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
|
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
|
||||||
@@ -170,7 +171,7 @@ function ScoreBreakdownTooltip({ pid, score, children }: { pid: number; score: n
|
|||||||
const { data } = useQuery<ScoreBreakdown>({
|
const { data } = useQuery<ScoreBreakdown>({
|
||||||
queryKey: ["score-breakdown", pid],
|
queryKey: ["score-breakdown", pid],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`${config.apiUrl}/newsletter/score-breakdown/${pid}`)
|
const res = await apiFetch(`${config.apiUrl}/newsletter/score-breakdown/${pid}`)
|
||||||
if (!res.ok) throw new Error("Failed")
|
if (!res.ok) throw new Error("Failed")
|
||||||
return res.json()
|
return res.json()
|
||||||
},
|
},
|
||||||
@@ -246,7 +247,7 @@ export function RecommendationTable({ category }: RecommendationTableProps) {
|
|||||||
const { data, isLoading } = useQuery<RecommendationResponse>({
|
const { data, isLoading } = useQuery<RecommendationResponse>({
|
||||||
queryKey: ["newsletter-recommendations", category, page],
|
queryKey: ["newsletter-recommendations", category, page],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(
|
const res = await apiFetch(
|
||||||
`${config.apiUrl}/newsletter/recommendations?category=${category}&page=${page}&limit=${limit}`
|
`${config.apiUrl}/newsletter/recommendations?category=${category}&page=${page}&limit=${limit}`
|
||||||
)
|
)
|
||||||
if (!res.ok) throw new Error("Failed to fetch recommendations")
|
if (!res.ok) throw new Error("Failed to fetch recommendations")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
@@ -52,7 +53,7 @@ export function BestSellers() {
|
|||||||
const { data, isError, isLoading } = useQuery<BestSellersData>({
|
const { data, isError, isLoading } = useQuery<BestSellersData>({
|
||||||
queryKey: ["best-sellers"],
|
queryKey: ["best-sellers"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/dashboard/best-sellers`)
|
const response = await apiFetch(`${config.apiUrl}/dashboard/best-sellers`)
|
||||||
if (!response.ok) throw new Error("Failed to fetch best sellers");
|
if (!response.ok) throw new Error("Failed to fetch best sellers");
|
||||||
return response.json()
|
return response.json()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { BarChart, Bar, ResponsiveContainer, XAxis, YAxis, Tooltip as RechartsTooltip, Cell, LineChart, Line } from "recharts"
|
import { BarChart, Bar, ResponsiveContainer, XAxis, YAxis, Tooltip as RechartsTooltip, Cell, LineChart, Line } from "recharts"
|
||||||
import config from "@/config"
|
import config from "@/config"
|
||||||
import { Target, TrendingDown, ArrowUpDown } from "lucide-react"
|
import { Target, TrendingDown, ArrowUpDown } from "lucide-react"
|
||||||
@@ -83,7 +84,7 @@ export function ForecastAccuracy() {
|
|||||||
const { data, error, isLoading } = useQuery<AccuracyData>({
|
const { data, error, isLoading } = useQuery<AccuracyData>({
|
||||||
queryKey: ["forecast-accuracy"],
|
queryKey: ["forecast-accuracy"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/dashboard/forecast/accuracy`)
|
const response = await apiFetch(`${config.apiUrl}/dashboard/forecast/accuracy`)
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch forecast accuracy")
|
throw new Error("Failed to fetch forecast accuracy")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||||
import { AreaChart, Area, ResponsiveContainer, XAxis, YAxis, Tooltip as RechartsTooltip } from "recharts"
|
import { AreaChart, Area, ResponsiveContainer, XAxis, YAxis, Tooltip as RechartsTooltip } from "recharts"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
@@ -72,7 +73,7 @@ export function ForecastMetrics() {
|
|||||||
startDate: new Date().toISOString(),
|
startDate: new Date().toISOString(),
|
||||||
endDate: getEndDate(period).toISOString(),
|
endDate: getEndDate(period).toISOString(),
|
||||||
});
|
});
|
||||||
const response = await fetch(`${config.apiUrl}/dashboard/forecast/metrics?${params}`)
|
const response = await apiFetch(`${config.apiUrl}/dashboard/forecast/metrics?${params}`)
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
throw new Error(`Failed to fetch forecast metrics: ${text}`);
|
throw new Error(`Failed to fetch forecast metrics: ${text}`);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||||
import config from "@/config"
|
import config from "@/config"
|
||||||
import { formatCurrency } from "@/utils/formatCurrency"
|
import { formatCurrency } from "@/utils/formatCurrency"
|
||||||
@@ -38,7 +39,7 @@ export function OverstockMetrics() {
|
|||||||
const { data, isError, isLoading } = useQuery<OverstockMetricsData>({
|
const { data, isError, isLoading } = useQuery<OverstockMetricsData>({
|
||||||
queryKey: ["overstock-metrics"],
|
queryKey: ["overstock-metrics"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/dashboard/overstock/metrics`)
|
const response = await apiFetch(`${config.apiUrl}/dashboard/overstock/metrics`)
|
||||||
if (!response.ok) throw new Error('Failed to fetch overstock metrics');
|
if (!response.ok) throw new Error('Failed to fetch overstock metrics');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||||
import { PieChart, Pie, ResponsiveContainer, Cell, Sector } from "recharts"
|
import { PieChart, Pie, ResponsiveContainer, Cell, Sector } from "recharts"
|
||||||
import config from "@/config"
|
import config from "@/config"
|
||||||
@@ -75,7 +76,7 @@ export function PurchaseMetrics() {
|
|||||||
const { data, isError, isLoading } = useQuery<PurchaseMetricsData>({
|
const { data, isError, isLoading } = useQuery<PurchaseMetricsData>({
|
||||||
queryKey: ["purchase-metrics"],
|
queryKey: ["purchase-metrics"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/dashboard/purchase/metrics`)
|
const response = await apiFetch(`${config.apiUrl}/dashboard/purchase/metrics`)
|
||||||
if (!response.ok) throw new Error('Failed to fetch purchase metrics');
|
if (!response.ok) throw new Error('Failed to fetch purchase metrics');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||||
import config from "@/config"
|
import config from "@/config"
|
||||||
import { formatCurrency } from "@/utils/formatCurrency"
|
import { formatCurrency } from "@/utils/formatCurrency"
|
||||||
@@ -40,7 +41,7 @@ export function ReplenishmentMetrics() {
|
|||||||
const { data, isError, isLoading } = useQuery<ReplenishmentMetricsData>({
|
const { data, isError, isLoading } = useQuery<ReplenishmentMetricsData>({
|
||||||
queryKey: ["replenishment-metrics"],
|
queryKey: ["replenishment-metrics"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/dashboard/replenishment/metrics`)
|
const response = await apiFetch(`${config.apiUrl}/dashboard/replenishment/metrics`)
|
||||||
if (!response.ok) throw new Error('Failed to fetch replenishment metrics');
|
if (!response.ok) throw new Error('Failed to fetch replenishment metrics');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||||
import { AreaChart, Area, ResponsiveContainer, XAxis, YAxis, Tooltip as RechartsTooltip } from "recharts"
|
import { AreaChart, Area, ResponsiveContainer, XAxis, YAxis, Tooltip as RechartsTooltip } from "recharts"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
@@ -61,7 +62,7 @@ export function SalesMetrics() {
|
|||||||
startDate: addDays(new Date(), -period).toISOString(),
|
startDate: addDays(new Date(), -period).toISOString(),
|
||||||
endDate: new Date().toISOString(),
|
endDate: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
const response = await fetch(`${config.apiUrl}/dashboard/sales/metrics?${params}`)
|
const response = await apiFetch(`${config.apiUrl}/dashboard/sales/metrics?${params}`)
|
||||||
if (!response.ok) throw new Error("Failed to fetch sales metrics");
|
if (!response.ok) throw new Error("Failed to fetch sales metrics");
|
||||||
return response.json()
|
return response.json()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||||
import { PieChart, Pie, ResponsiveContainer, Cell, Sector } from "recharts"
|
import { PieChart, Pie, ResponsiveContainer, Cell, Sector } from "recharts"
|
||||||
import config from "@/config"
|
import config from "@/config"
|
||||||
@@ -107,7 +108,7 @@ export function StockMetrics() {
|
|||||||
const { data, isError, isLoading } = useQuery<StockMetricsData>({
|
const { data, isError, isLoading } = useQuery<StockMetricsData>({
|
||||||
queryKey: ["stock-metrics"],
|
queryKey: ["stock-metrics"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/dashboard/stock/metrics`);
|
const response = await apiFetch(`${config.apiUrl}/dashboard/stock/metrics`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch stock metrics');
|
if (!response.ok) throw new Error('Failed to fetch stock metrics');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
@@ -29,7 +30,7 @@ export function TopOverstockedProducts() {
|
|||||||
const { data, isError, isLoading } = useQuery<Product[]>({
|
const { data, isError, isLoading } = useQuery<Product[]>({
|
||||||
queryKey: ["top-overstocked-products"],
|
queryKey: ["top-overstocked-products"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/dashboard/overstock/products?limit=50`)
|
const response = await apiFetch(`${config.apiUrl}/dashboard/overstock/products?limit=50`)
|
||||||
if (!response.ok) throw new Error("Failed to fetch overstocked products");
|
if (!response.ok) throw new Error("Failed to fetch overstocked products");
|
||||||
return response.json()
|
return response.json()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
@@ -29,7 +30,7 @@ export function TopReplenishProducts() {
|
|||||||
const { data, isError, isLoading } = useQuery<Product[]>({
|
const { data, isError, isLoading } = useQuery<Product[]>({
|
||||||
queryKey: ["top-replenish-products"],
|
queryKey: ["top-replenish-products"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/dashboard/replenish/products?limit=50`)
|
const response = await apiFetch(`${config.apiUrl}/dashboard/replenish/products?limit=50`)
|
||||||
if (!response.ok) throw new Error("Failed to fetch products to replenish");
|
if (!response.ok) throw new Error("Failed to fetch products to replenish");
|
||||||
return response.json()
|
return response.json()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useCallback, useRef } from "react";
|
import { useState, useCallback, useRef } from "react";
|
||||||
import axios from "axios";
|
import { apiClient } from "@/utils/apiClient";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -305,7 +305,7 @@ export function ImageManager({
|
|||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("image", file);
|
formData.append("image", file);
|
||||||
const res = await axios.post("/api/import/upload-image", formData);
|
const res = await apiClient.post("/api/import/upload-image", formData);
|
||||||
if (res.data?.imageUrl) {
|
if (res.data?.imageUrl) {
|
||||||
addNewImage(res.data.imageUrl);
|
addNewImage(res.data.imageUrl);
|
||||||
}
|
}
|
||||||
@@ -356,7 +356,7 @@ export function ImageManager({
|
|||||||
setAddOpen(false);
|
setAddOpen(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.post("/api/import/upload-image-url", {
|
const res = await apiClient.post("/api/import/upload-image-url", {
|
||||||
imageUrl: normalizeImageUrlInput(url),
|
imageUrl: normalizeImageUrlInput(url),
|
||||||
});
|
});
|
||||||
if (res.data?.imageUrl) {
|
if (res.data?.imageUrl) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect, useCallback, useRef, useContext } from "react";
|
import { useState, useEffect, useCallback, useRef, useContext } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { apiClient } from "@/utils/apiClient";
|
||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -499,7 +500,7 @@ export function ProductEditForm({
|
|||||||
originalValuesRef.current = { ...data };
|
originalValuesRef.current = { ...data };
|
||||||
if (imageChanges) {
|
if (imageChanges) {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get(`/api/import/product-images/${product.pid}`);
|
const res = await apiClient.get(`/api/import/product-images/${product.pid}`);
|
||||||
originalImagesRef.current = res.data;
|
originalImagesRef.current = res.data;
|
||||||
setProductImages(res.data);
|
setProductImages(res.data);
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useCallback } from "react";
|
import { useState, useCallback } from "react";
|
||||||
import axios from "axios";
|
import { apiClient } from "@/utils/apiClient";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -57,11 +57,11 @@ export function ProductSearch({
|
|||||||
setIsSearching(true);
|
setIsSearching(true);
|
||||||
onNewSearch();
|
onNewSearch();
|
||||||
try {
|
try {
|
||||||
const res = await axios.get("/api/products/search", {
|
const res = await apiClient.get("/api/products/search", {
|
||||||
params: { q: searchTerm },
|
params: { q: searchTerm },
|
||||||
});
|
});
|
||||||
if (res.data.total === 0) {
|
if (res.data.total === 0) {
|
||||||
const fallback = await axios.get("/api/import/search-products", {
|
const fallback = await apiClient.get("/api/import/search-products", {
|
||||||
params: { q: searchTerm },
|
params: { q: searchTerm },
|
||||||
});
|
});
|
||||||
const fullProducts = fallback.data as SearchProduct[];
|
const fullProducts = fallback.data as SearchProduct[];
|
||||||
@@ -102,7 +102,7 @@ export function ProductSearch({
|
|||||||
}
|
}
|
||||||
setIsLoadingProduct(product.pid);
|
setIsLoadingProduct(product.pid);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get("/api/import/search-products", {
|
const res = await apiClient.get("/api/import/search-products", {
|
||||||
params: { pid: product.pid },
|
params: { pid: product.pid },
|
||||||
});
|
});
|
||||||
const full = (res.data as SearchProduct[])[0];
|
const full = (res.data as SearchProduct[])[0];
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import type { TaxonomySuggestion, ProductSuggestions } from '@/components/product-import/steps/ValidationStep/store/types';
|
import type { TaxonomySuggestion, ProductSuggestions } from '@/components/product-import/steps/ValidationStep/store/types';
|
||||||
|
|
||||||
const API_BASE = '/api/ai';
|
const API_BASE = '/api/ai';
|
||||||
@@ -20,7 +21,7 @@ let initPromise: Promise<boolean> | null = null;
|
|||||||
|
|
||||||
async function ensureInitialized(): Promise<boolean> {
|
async function ensureInitialized(): Promise<boolean> {
|
||||||
if (!initPromise) {
|
if (!initPromise) {
|
||||||
initPromise = fetch(`${API_BASE}/initialize`, { method: 'POST' })
|
initPromise = apiFetch(`${API_BASE}/initialize`, { method: 'POST' })
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((d) => Boolean(d.success))
|
.then((d) => Boolean(d.success))
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -81,7 +82,7 @@ export function useProductSuggestions(product: ProductInput): ProductSuggestionR
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE}/suggestions`, {
|
const response = await apiFetch(`${API_BASE}/suggestions`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product: p }),
|
body: JSON.stringify({ product: p }),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Check, ChevronsUpDown, Loader2 } from "lucide-react";
|
import { Check, ChevronsUpDown, Loader2 } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ export function CreateProductCategoryDialog({
|
|||||||
setIsLoadingLines(true);
|
setIsLoadingLines(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.apiUrl}/import/product-lines/${targetCompanyId}`);
|
const response = await apiFetch(`${config.apiUrl}/import/product-lines/${targetCompanyId}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to load product lines");
|
throw new Error("Failed to load product lines");
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Loader2, Link as LinkIcon, Image as ImageIcon } from "lucide-react";
|
import { Loader2, Link as LinkIcon, Image as ImageIcon } from "lucide-react";
|
||||||
@@ -83,7 +84,7 @@ export const ProductCard = ({
|
|||||||
const { data: reusableImages, isLoading: isLoadingReusable } = useQuery<ReusableImage[]>({
|
const { data: reusableImages, isLoading: isLoadingReusable } = useQuery<ReusableImage[]>({
|
||||||
queryKey: ["reusable-images"],
|
queryKey: ["reusable-images"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/reusable-images`);
|
const response = await apiFetch(`${config.apiUrl}/reusable-images`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch reusable images");
|
throw new Error("Failed to fetch reusable images");
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -1,4 +1,5 @@
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import config from "@/config";
|
import config from "@/config";
|
||||||
import { Product, ProductImageSortable, ImageMetadata, ImageNotice } from "../types";
|
import { Product, ProductImageSortable, ImageMetadata, ImageNotice } from "../types";
|
||||||
|
|
||||||
@@ -248,7 +249,7 @@ export const useProductImageOperations = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Upload the image
|
// Upload the image
|
||||||
const response = await fetch(`${config.apiUrl}/import/upload-image`, {
|
const response = await apiFetch(`${config.apiUrl}/import/upload-image`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
@@ -355,7 +356,7 @@ export const useProductImageOperations = ({
|
|||||||
const filename = urlParts[urlParts.length - 1];
|
const filename = urlParts[urlParts.length - 1];
|
||||||
|
|
||||||
// Call API to delete the image
|
// Call API to delete the image
|
||||||
const response = await fetch(`${config.apiUrl}/import/delete-image`, {
|
const response = await apiFetch(`${config.apiUrl}/import/delete-image`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import config from "@/config";
|
import config from "@/config";
|
||||||
import { Product, ProductImageSortable } from "../types";
|
import { Product, ProductImageSortable } from "../types";
|
||||||
@@ -59,7 +60,7 @@ export const useUrlImageUpload = ({
|
|||||||
let fileName = "From URL";
|
let fileName = "From URL";
|
||||||
|
|
||||||
if (isLikelyWebpUrl(validatedUrl)) {
|
if (isLikelyWebpUrl(validatedUrl)) {
|
||||||
const response = await fetch(`${config.apiUrl}/import/upload-image-url`, {
|
const response = await apiFetch(`${config.apiUrl}/import/upload-image-url`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState, memo, useRef, useLayoutEffect } from "react"
|
import React, { useCallback, useEffect, useMemo, useState, memo, useRef, useLayoutEffect } from "react"
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useRsi } from "../../hooks/useRsi"
|
import { useRsi } from "../../hooks/useRsi"
|
||||||
import { setColumn } from "./utils/setColumn"
|
import { setColumn } from "./utils/setColumn"
|
||||||
import { setIgnoreColumn } from "./utils/setIgnoreColumn"
|
import { setIgnoreColumn } from "./utils/setIgnoreColumn"
|
||||||
@@ -672,7 +673,7 @@ const MatchColumnsStepComponent = <T extends string>({
|
|||||||
queryKey: ["product-lines", globalSelections.company],
|
queryKey: ["product-lines", globalSelections.company],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!globalSelections.company) return [];
|
if (!globalSelections.company) return [];
|
||||||
const response = await fetch(`${config.apiUrl}/import/product-lines/${globalSelections.company}`);
|
const response = await apiFetch(`${config.apiUrl}/import/product-lines/${globalSelections.company}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch product lines");
|
throw new Error("Failed to fetch product lines");
|
||||||
}
|
}
|
||||||
@@ -687,7 +688,7 @@ const MatchColumnsStepComponent = <T extends string>({
|
|||||||
queryKey: ["sublines", globalSelections.line],
|
queryKey: ["sublines", globalSelections.line],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!globalSelections.line) return [];
|
if (!globalSelections.line) return [];
|
||||||
const response = await fetch(`${config.apiUrl}/import/sublines/${globalSelections.line}`);
|
const response = await apiFetch(`${config.apiUrl}/import/sublines/${globalSelections.line}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch sublines");
|
throw new Error("Failed to fetch sublines");
|
||||||
}
|
}
|
||||||
@@ -756,7 +757,7 @@ const MatchColumnsStepComponent = <T extends string>({
|
|||||||
queryKey: ["product-lines-mapped", mappedCompanyValue],
|
queryKey: ["product-lines-mapped", mappedCompanyValue],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!mappedCompanyValue) return [];
|
if (!mappedCompanyValue) return [];
|
||||||
const response = await fetch(`${config.apiUrl}/import/product-lines/${mappedCompanyValue}`);
|
const response = await apiFetch(`${config.apiUrl}/import/product-lines/${mappedCompanyValue}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch product lines for mapped company");
|
throw new Error("Failed to fetch product lines for mapped company");
|
||||||
}
|
}
|
||||||
@@ -771,7 +772,7 @@ const MatchColumnsStepComponent = <T extends string>({
|
|||||||
queryKey: ["sublines-mapped", mappedLineValue],
|
queryKey: ["sublines-mapped", mappedLineValue],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!mappedLineValue) return [];
|
if (!mappedLineValue) return [];
|
||||||
const response = await fetch(`${config.apiUrl}/import/sublines/${mappedLineValue}`);
|
const response = await apiFetch(`${config.apiUrl}/import/sublines/${mappedLineValue}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch sublines for mapped line");
|
throw new Error("Failed to fetch sublines for mapped line");
|
||||||
}
|
}
|
||||||
@@ -785,7 +786,7 @@ const MatchColumnsStepComponent = <T extends string>({
|
|||||||
const { data: fieldOptionsData } = useQuery({
|
const { data: fieldOptionsData } = useQuery({
|
||||||
queryKey: ["field-options"],
|
queryKey: ["field-options"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/import/field-options`);
|
const response = await apiFetch(`${config.apiUrl}/import/field-options`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch field options");
|
throw new Error("Failed to fetch field options");
|
||||||
}
|
}
|
||||||
@@ -852,7 +853,7 @@ const MatchColumnsStepComponent = <T extends string>({
|
|||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
try {
|
try {
|
||||||
// Clear backend cache
|
// Clear backend cache
|
||||||
const response = await fetch(`${config.apiUrl}/import/clear-taxonomy-cache`, {
|
const response = await apiFetch(`${config.apiUrl}/import/clear-taxonomy-cache`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
+15
-14
@@ -13,6 +13,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo, useRef, useCallback, memo, useState } from 'react';
|
import { useMemo, useRef, useCallback, memo, useState } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { type ColumnDef } from '@tanstack/react-table';
|
import { type ColumnDef } from '@tanstack/react-table';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
@@ -249,7 +250,7 @@ const CellWrapper = memo(({
|
|||||||
setIsGeneratingUpc(true);
|
setIsGeneratingUpc(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.apiUrl}/import/generate-upc`, {
|
const response = await apiFetch(`${config.apiUrl}/import/generate-upc`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ supplierId: supplierIdString }),
|
body: JSON.stringify({ supplierId: supplierIdString }),
|
||||||
@@ -287,7 +288,7 @@ const CellWrapper = memo(({
|
|||||||
startValidatingCell(rowIndex, 'item_number');
|
startValidatingCell(rowIndex, 'item_number');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const validationResponse = await fetch(
|
const validationResponse = await apiFetch(
|
||||||
`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upc)}&supplierId=${encodeURIComponent(supplierIdString)}`
|
`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upc)}&supplierId=${encodeURIComponent(supplierIdString)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -491,7 +492,7 @@ const CellWrapper = memo(({
|
|||||||
if (!cached) {
|
if (!cached) {
|
||||||
// Start loading state and fetch product lines
|
// Start loading state and fetch product lines
|
||||||
state.setLoadingProductLines(companyId, true);
|
state.setLoadingProductLines(companyId, true);
|
||||||
fetch(`/api/import/product-lines/${companyId}`)
|
apiFetch(`/api/import/product-lines/${companyId}`)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(lines => {
|
.then(lines => {
|
||||||
const opts = lines.map((line: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
const opts = lines.map((line: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
||||||
@@ -523,7 +524,7 @@ const CellWrapper = memo(({
|
|||||||
if (!cached) {
|
if (!cached) {
|
||||||
// Start loading state and fetch sublines
|
// Start loading state and fetch sublines
|
||||||
state.setLoadingSublines(lineId, true);
|
state.setLoadingSublines(lineId, true);
|
||||||
fetch(`/api/import/sublines/${lineId}`)
|
apiFetch(`/api/import/sublines/${lineId}`)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(sublines => {
|
.then(sublines => {
|
||||||
const opts = sublines.map((subline: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
const opts = sublines.map((subline: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
||||||
@@ -565,7 +566,7 @@ const CellWrapper = memo(({
|
|||||||
line: valueToSave as string | number,
|
line: valueToSave as string | number,
|
||||||
});
|
});
|
||||||
|
|
||||||
fetch('/api/ai/validate/inline/name', {
|
apiFetch('/api/ai/validate/inline/name', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product: payload }),
|
body: JSON.stringify({ product: payload }),
|
||||||
@@ -596,7 +597,7 @@ const CellWrapper = memo(({
|
|||||||
|
|
||||||
const payload = buildDescriptionValidationPayload(currentRowForContext, fields);
|
const payload = buildDescriptionValidationPayload(currentRowForContext, fields);
|
||||||
|
|
||||||
fetch('/api/ai/validate/inline/description', {
|
apiFetch('/api/ai/validate/inline/description', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product: payload }),
|
body: JSON.stringify({ product: payload }),
|
||||||
@@ -640,7 +641,7 @@ const CellWrapper = memo(({
|
|||||||
startValidatingCell(rowIndex, 'item_number');
|
startValidatingCell(rowIndex, 'item_number');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await apiFetch(
|
||||||
`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upc)}&supplierId=${encodeURIComponent(supplierId)}`
|
`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upc)}&supplierId=${encodeURIComponent(supplierId)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -726,7 +727,7 @@ const CellWrapper = memo(({
|
|||||||
? '/api/ai/validate/inline/name'
|
? '/api/ai/validate/inline/name'
|
||||||
: '/api/ai/validate/inline/description';
|
: '/api/ai/validate/inline/description';
|
||||||
|
|
||||||
fetch(endpoint, {
|
apiFetch(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product: productPayload }),
|
body: JSON.stringify({ product: productPayload }),
|
||||||
@@ -793,7 +794,7 @@ const CellWrapper = memo(({
|
|||||||
? '/api/ai/validate/inline/name'
|
? '/api/ai/validate/inline/name'
|
||||||
: '/api/ai/validate/inline/description';
|
: '/api/ai/validate/inline/description';
|
||||||
|
|
||||||
fetch(endpoint, {
|
apiFetch(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product: payload }),
|
body: JSON.stringify({ product: payload }),
|
||||||
@@ -824,7 +825,7 @@ const CellWrapper = memo(({
|
|||||||
|
|
||||||
state.setLoadingProductLines(companyId, true);
|
state.setLoadingProductLines(companyId, true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/import/product-lines/${companyId}`);
|
const response = await apiFetch(`/api/import/product-lines/${companyId}`);
|
||||||
const lines = await response.json();
|
const lines = await response.json();
|
||||||
const opts = lines.map((ln: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
const opts = lines.map((ln: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
||||||
label: ln.name || ln.label || String(ln.value || ln.id),
|
label: ln.name || ln.label || String(ln.value || ln.id),
|
||||||
@@ -847,7 +848,7 @@ const CellWrapper = memo(({
|
|||||||
|
|
||||||
state.setLoadingSublines(lineId, true);
|
state.setLoadingSublines(lineId, true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/import/sublines/${lineId}`);
|
const response = await apiFetch(`/api/import/sublines/${lineId}`);
|
||||||
const sublines = await response.json();
|
const sublines = await response.json();
|
||||||
const opts = sublines.map((subline: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
const opts = sublines.map((subline: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
||||||
label: subline.name || subline.label || String(subline.value || subline.id),
|
label: subline.name || subline.label || String(subline.value || subline.id),
|
||||||
@@ -1138,7 +1139,7 @@ const TemplateCell = memo(({ rowIndex, currentTemplateId, defaultBrand }: Templa
|
|||||||
|
|
||||||
const payload = buildNameValidationPayload(updatedRow, fields, rows);
|
const payload = buildNameValidationPayload(updatedRow, fields, rows);
|
||||||
|
|
||||||
fetch('/api/ai/validate/inline/name', {
|
apiFetch('/api/ai/validate/inline/name', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product: payload }),
|
body: JSON.stringify({ product: payload }),
|
||||||
@@ -1164,7 +1165,7 @@ const TemplateCell = memo(({ rowIndex, currentTemplateId, defaultBrand }: Templa
|
|||||||
|
|
||||||
const payload = buildDescriptionValidationPayload(updatedRow, fields);
|
const payload = buildDescriptionValidationPayload(updatedRow, fields);
|
||||||
|
|
||||||
fetch('/api/ai/validate/inline/description', {
|
apiFetch('/api/ai/validate/inline/description', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product: payload }),
|
body: JSON.stringify({ product: payload }),
|
||||||
@@ -1206,7 +1207,7 @@ const TemplateCell = memo(({ rowIndex, currentTemplateId, defaultBrand }: Templa
|
|||||||
startValidatingCell(rowIndex, 'item_number');
|
startValidatingCell(rowIndex, 'item_number');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await apiFetch(
|
||||||
`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upc)}&supplierId=${encodeURIComponent(supplierId)}`
|
`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upc)}&supplierId=${encodeURIComponent(supplierId)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -11,6 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createContext, useContext, useRef, useCallback, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useRef, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import type { RowData, ProductSuggestions, TaxonomySuggestion } from '../store/types';
|
import type { RowData, ProductSuggestions, TaxonomySuggestion } from '../store/types';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -104,7 +105,7 @@ export function AiSuggestionsProvider({
|
|||||||
if (isInitialized) return true;
|
if (isInitialized) return true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/initialize`, { method: 'POST' });
|
const response = await apiFetch(`${API_BASE}/initialize`, { method: 'POST' });
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!response.ok || !data.success) {
|
if (!response.ok || !data.success) {
|
||||||
@@ -157,7 +158,7 @@ export function AiSuggestionsProvider({
|
|||||||
notifySubscribers(productIndex);
|
notifySubscribers(productIndex);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/suggestions`, {
|
const response = await apiFetch(`${API_BASE}/suggestions`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product: productData }),
|
body: JSON.stringify({ product: productData }),
|
||||||
@@ -214,7 +215,7 @@ export function AiSuggestionsProvider({
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/suggestions/batch`, {
|
const response = await apiFetch(`${API_BASE}/suggestions/batch`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
+3
-2
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import type { RowData } from '../../store/types';
|
import type { RowData } from '../../store/types';
|
||||||
import type { Field } from '../../../../types';
|
import type { Field } from '../../../../types';
|
||||||
import { prepareDataForAiValidation } from '../../utils/aiValidationUtils';
|
import { prepareDataForAiValidation } from '../../utils/aiValidationUtils';
|
||||||
@@ -164,7 +165,7 @@ export const runAiValidation = async (
|
|||||||
request: AiValidationRequest
|
request: AiValidationRequest
|
||||||
): Promise<AiValidationResponse> => {
|
): Promise<AiValidationResponse> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.apiUrl}/ai-validation/validate`, {
|
const response = await apiFetch(`${config.apiUrl}/ai-validation/validate`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(request),
|
body: JSON.stringify(request),
|
||||||
@@ -201,7 +202,7 @@ export const getAiDebugPrompt = async (
|
|||||||
// For time estimation before validation, send all products
|
// For time estimation before validation, send all products
|
||||||
const productsToSend = options?.previewOnly ? products.slice(0, 5) : products;
|
const productsToSend = options?.previewOnly ? products.slice(0, 5) : products;
|
||||||
|
|
||||||
const response = await fetch(`${config.apiUrl}/ai-validation/debug`, {
|
const response = await apiFetch(`${config.apiUrl}/ai-validation/debug`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
+2
-1
@@ -14,6 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useValidationStore } from '../store/validationStore';
|
import { useValidationStore } from '../store/validationStore';
|
||||||
import { useInitPhase } from '../store/selectors';
|
import { useInitPhase } from '../store/selectors';
|
||||||
import {
|
import {
|
||||||
@@ -48,7 +49,7 @@ async function triggerValidation(
|
|||||||
: '/api/ai/validate/inline/description';
|
: '/api/ai/validate/inline/description';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(endpoint, {
|
const response = await apiFetch(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product: payload }),
|
body: JSON.stringify({ product: payload }),
|
||||||
|
|||||||
+2
-1
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useValidationStore } from '../store/validationStore';
|
import { useValidationStore } from '../store/validationStore';
|
||||||
import { useUpcValidation } from './useUpcValidation';
|
import { useUpcValidation } from './useUpcValidation';
|
||||||
import type { Field } from '../../../types';
|
import type { Field } from '../../../types';
|
||||||
@@ -47,7 +48,7 @@ async function triggerInlineAiValidation(
|
|||||||
: '/api/ai/validate/inline/description';
|
: '/api/ai/validate/inline/description';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(endpoint, {
|
const response = await apiFetch(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product: productPayload }),
|
body: JSON.stringify({ product: productPayload }),
|
||||||
|
|||||||
+2
-1
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { SelectOption } from '../../../types';
|
import type { SelectOption } from '../../../types';
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ export interface FieldOptionsResponse {
|
|||||||
* Fetch field options from the API
|
* Fetch field options from the API
|
||||||
*/
|
*/
|
||||||
const fetchFieldOptions = async (): Promise<FieldOptionsResponse> => {
|
const fetchFieldOptions = async (): Promise<FieldOptionsResponse> => {
|
||||||
const response = await fetch(`${config.apiUrl}/import/field-options`);
|
const response = await apiFetch(`${config.apiUrl}/import/field-options`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch field options');
|
throw new Error('Failed to fetch field options');
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useCallback, useRef } from 'react';
|
import { useState, useCallback, useRef } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
|
|
||||||
// Types for the validation results
|
// Types for the validation results
|
||||||
export interface InlineAiResult {
|
export interface InlineAiResult {
|
||||||
@@ -73,7 +74,7 @@ export function useInlineAiValidation() {
|
|||||||
setState(prev => ({ ...prev, isValidating: true, error: null }));
|
setState(prev => ({ ...prev, isValidating: true, error: null }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/ai/validate/inline/name', {
|
const response = await apiFetch('/api/ai/validate/inline/name', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product }),
|
body: JSON.stringify({ product }),
|
||||||
@@ -138,7 +139,7 @@ export function useInlineAiValidation() {
|
|||||||
setState(prev => ({ ...prev, isValidating: true, error: null }));
|
setState(prev => ({ ...prev, isValidating: true, error: null }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/ai/validate/inline/description', {
|
const response = await apiFetch('/api/ai/validate/inline/description', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ product }),
|
body: JSON.stringify({ product }),
|
||||||
|
|||||||
+3
-3
@@ -12,13 +12,13 @@
|
|||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import { useValidationStore } from '../store/validationStore';
|
import { useValidationStore } from '../store/validationStore';
|
||||||
import type { SelectOption } from '../../../types';
|
import type { SelectOption } from '../../../types';
|
||||||
import axios from 'axios';
|
import { apiClient } from "@/utils/apiClient";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch product lines for a company
|
* Fetch product lines for a company
|
||||||
*/
|
*/
|
||||||
const fetchProductLinesApi = async (companyId: string): Promise<SelectOption[]> => {
|
const fetchProductLinesApi = async (companyId: string): Promise<SelectOption[]> => {
|
||||||
const response = await axios.get(`/api/import/product-lines/${companyId}`);
|
const response = await apiClient.get(`/api/import/product-lines/${companyId}`);
|
||||||
const lines = response.data;
|
const lines = response.data;
|
||||||
|
|
||||||
return lines.map((line: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
return lines.map((line: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
||||||
@@ -31,7 +31,7 @@ const fetchProductLinesApi = async (companyId: string): Promise<SelectOption[]>
|
|||||||
* Fetch sublines for a product line
|
* Fetch sublines for a product line
|
||||||
*/
|
*/
|
||||||
const fetchSublinesApi = async (lineId: string): Promise<SelectOption[]> => {
|
const fetchSublinesApi = async (lineId: string): Promise<SelectOption[]> => {
|
||||||
const response = await axios.get(`/api/import/sublines/${lineId}`);
|
const response = await apiClient.get(`/api/import/sublines/${lineId}`);
|
||||||
const sublines = response.data;
|
const sublines = response.data;
|
||||||
|
|
||||||
return sublines.map((subline: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
return sublines.map((subline: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
||||||
|
|||||||
+2
-1
@@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useCallback, useRef } from 'react';
|
import { useState, useCallback, useRef } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
|
|
||||||
// Types for sanity check results
|
// Types for sanity check results
|
||||||
export interface SanityIssue {
|
export interface SanityIssue {
|
||||||
@@ -103,7 +104,7 @@ export function useSanityCheck() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/ai/validate/sanity-check', {
|
const response = await apiFetch('/api/ai/validate/sanity-check', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ products }),
|
body: JSON.stringify({ products }),
|
||||||
|
|||||||
+4
-3
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useValidationStore } from '../store/validationStore';
|
import { useValidationStore } from '../store/validationStore';
|
||||||
import {
|
import {
|
||||||
useTemplates,
|
useTemplates,
|
||||||
@@ -56,7 +57,7 @@ export const useTemplateManagement = () => {
|
|||||||
setTemplatesLoading(true);
|
setTemplatesLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.apiUrl}/templates`);
|
const response = await apiFetch(`${config.apiUrl}/templates`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch templates');
|
if (!response.ok) throw new Error('Failed to fetch templates');
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -162,7 +163,7 @@ export const useTemplateManagement = () => {
|
|||||||
if (supplierId && upcValue) {
|
if (supplierId && upcValue) {
|
||||||
// Don't await - let it run in background
|
// Don't await - let it run in background
|
||||||
// UPC validation uses its own hooks which will update the store
|
// UPC validation uses its own hooks which will update the store
|
||||||
fetch(`/api/import/validate-upc/${supplierId}/${encodeURIComponent(upcValue)}`)
|
apiFetch(`/api/import/validate-upc/${supplierId}/${encodeURIComponent(upcValue)}`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.itemNumber) {
|
if (result.itemNumber) {
|
||||||
@@ -236,7 +237,7 @@ export const useTemplateManagement = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.apiUrl}/templates`, {
|
const response = await apiFetch(`${config.apiUrl}/templates`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
+2
-1
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useValidationStore } from '../store/validationStore';
|
import { useValidationStore } from '../store/validationStore';
|
||||||
import { useInitialUpcValidationDone } from '../store/selectors';
|
import { useInitialUpcValidationDone } from '../store/selectors';
|
||||||
import { ErrorSource, ErrorType, type UpcValidationResult } from '../store/types';
|
import { ErrorSource, ErrorType, type UpcValidationResult } from '../store/types';
|
||||||
@@ -27,7 +28,7 @@ const fetchProductByUpc = async (
|
|||||||
upcValue: string
|
upcValue: string
|
||||||
): Promise<UpcValidationResult> => {
|
): Promise<UpcValidationResult> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await apiFetch(
|
||||||
`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upcValue)}&supplierId=${encodeURIComponent(supplierId)}`
|
`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upcValue)}&supplierId=${encodeURIComponent(supplierId)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useRef, useDeferredValue, useMemo } from 'react';
|
import { useEffect, useRef, useDeferredValue, useMemo } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useValidationStore } from './store/validationStore';
|
import { useValidationStore } from './store/validationStore';
|
||||||
import { useInitPhase, useIsReady } from './store/selectors';
|
import { useInitPhase, useIsReady } from './store/selectors';
|
||||||
@@ -58,7 +59,7 @@ const createDataFingerprint = (data: Record<string, unknown>[]): string => {
|
|||||||
* Fetch field options from the API
|
* Fetch field options from the API
|
||||||
*/
|
*/
|
||||||
const fetchFieldOptions = async () => {
|
const fetchFieldOptions = async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/import/field-options`);
|
const response = await apiFetch(`${config.apiUrl}/import/field-options`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch field options');
|
throw new Error('Failed to fetch field options');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Drawer as VaulDrawer } from "vaul";
|
import { Drawer as VaulDrawer } from "vaul";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
@@ -77,7 +78,7 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
|||||||
queryKey: ["productMetricDetail", productId],
|
queryKey: ["productMetricDetail", productId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!productId) throw new Error("Product ID is required");
|
if (!productId) throw new Error("Product ID is required");
|
||||||
const response = await fetch(`${config.apiUrl}/metrics/${productId}`, {credentials: 'include'});
|
const response = await apiFetch(`${config.apiUrl}/metrics/${productId}`, {credentials: 'include'});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(`Failed to fetch product details (${response.status}): ${errorData.error || 'Server error'}`);
|
throw new Error(`Failed to fetch product details (${response.status}): ${errorData.error || 'Server error'}`);
|
||||||
@@ -93,7 +94,7 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
|||||||
queryKey: ["productTimeSeries", productId],
|
queryKey: ["productTimeSeries", productId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!productId) throw new Error("Product ID is required");
|
if (!productId) throw new Error("Product ID is required");
|
||||||
const response = await fetch(`${config.apiUrl}/products/${productId}/time-series`, {credentials: 'include'});
|
const response = await apiFetch(`${config.apiUrl}/products/${productId}/time-series`, {credentials: 'include'});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(`Failed to fetch time series data (${response.status}): ${errorData.error || 'Server error'}`);
|
throw new Error(`Failed to fetch time series data (${response.status}): ${errorData.error || 'Server error'}`);
|
||||||
@@ -135,7 +136,7 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
|||||||
queryKey: ["productForecast", productId],
|
queryKey: ["productForecast", productId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!productId) throw new Error("Product ID is required");
|
if (!productId) throw new Error("Product ID is required");
|
||||||
const response = await fetch(`${config.apiUrl}/products/${productId}/forecast`, {credentials: 'include'});
|
const response = await apiFetch(`${config.apiUrl}/products/${productId}/forecast`, {credentials: 'include'});
|
||||||
if (!response.ok) throw new Error("Failed to fetch forecast");
|
if (!response.ok) throw new Error("Failed to fetch forecast");
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import {
|
import {
|
||||||
DollarSign,
|
DollarSign,
|
||||||
Package,
|
Package,
|
||||||
@@ -282,7 +283,7 @@ export function ProductSummaryCards({ activeView, showNonReplenishable, showInvi
|
|||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (showNonReplenishable) params.append('showNonReplenishable', 'true');
|
if (showNonReplenishable) params.append('showNonReplenishable', 'true');
|
||||||
if (showInvisible) params.append('showInvisible', 'true');
|
if (showInvisible) params.append('showInvisible', 'true');
|
||||||
const response = await fetch(`/api/metrics/summary?${params.toString()}`, { credentials: 'include' });
|
const response = await apiFetch(`/api/metrics/summary?${params.toString()}`, { credentials: 'include' });
|
||||||
if (!response.ok) throw new Error('Failed to fetch summary');
|
if (!response.ok) throw new Error('Failed to fetch summary');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
BarChart,
|
BarChart,
|
||||||
@@ -40,7 +41,7 @@ export default function PipelineCard() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('/api/purchase-orders/pipeline')
|
apiFetch('/api/purchase-orders/pipeline')
|
||||||
.then(res => res.ok ? res.json() : Promise.reject('Failed'))
|
.then(res => res.ok ? res.json() : Promise.reject('Failed'))
|
||||||
.then(setData)
|
.then(setData)
|
||||||
.catch(err => console.error('Pipeline fetch error:', err))
|
.catch(err => console.error('Pipeline fetch error:', err))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -85,7 +86,7 @@ export default function PurchaseOrderAccordion({
|
|||||||
? `/api/purchase-orders/receiving/${purchaseOrder.id}/items`
|
? `/api/purchase-orders/receiving/${purchaseOrder.id}/items`
|
||||||
: `/api/purchase-orders/${purchaseOrder.id}/items`;
|
: `/api/purchase-orders/${purchaseOrder.id}/items`;
|
||||||
|
|
||||||
const response = await fetch(endpoint);
|
const response = await apiFetch(endpoint);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch items: ${response.statusText}`);
|
throw new Error(`Failed to fetch items: ${response.statusText}`);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import axios from "axios";
|
import { apiClient } from "@/utils/apiClient";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -92,7 +92,7 @@ export function AuditLog() {
|
|||||||
if (filterPid.trim()) params.pid = filterPid.trim();
|
if (filterPid.trim()) params.pid = filterPid.trim();
|
||||||
if (filterAction !== "all") params.action = filterAction;
|
if (filterAction !== "all") params.action = filterAction;
|
||||||
}
|
}
|
||||||
const res = await axios.get(baseUrl, { params });
|
const res = await apiClient.get(baseUrl, { params });
|
||||||
setEntries(res.data.entries);
|
setEntries(res.data.entries);
|
||||||
setTotal(res.data.total);
|
setTotal(res.data.total);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -125,7 +125,7 @@ export function AuditLog() {
|
|||||||
const viewDetail = async (id: number) => {
|
const viewDetail = async (id: number) => {
|
||||||
setIsLoadingDetail(true);
|
setIsLoadingDetail(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get(`${baseUrl}/${id}`);
|
const res = await apiClient.get(`${baseUrl}/${id}`);
|
||||||
setDetail(res.data);
|
setDetail(res.data);
|
||||||
} catch {
|
} catch {
|
||||||
setDetail(null);
|
setDetail(null);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -189,7 +190,7 @@ export function DataManagement() {
|
|||||||
const checkActiveProcess = async () => {
|
const checkActiveProcess = async () => {
|
||||||
try {
|
try {
|
||||||
console.log("Checking for active processes...");
|
console.log("Checking for active processes...");
|
||||||
const response = await fetch(`${config.apiUrl}/csv/status`, {
|
const response = await apiFetch(`${config.apiUrl}/csv/status`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -509,7 +510,7 @@ export function DataManagement() {
|
|||||||
// Connect to the update SSE endpoint
|
// Connect to the update SSE endpoint
|
||||||
connectToEventSource("update");
|
connectToEventSource("update");
|
||||||
|
|
||||||
const response = await fetch(`${config.apiUrl}/csv/full-update`, {
|
const response = await apiFetch(`${config.apiUrl}/csv/full-update`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
@@ -543,7 +544,7 @@ export function DataManagement() {
|
|||||||
// Connect to the reset SSE endpoint
|
// Connect to the reset SSE endpoint
|
||||||
connectToEventSource("reset");
|
connectToEventSource("reset");
|
||||||
|
|
||||||
const response = await fetch(`${config.apiUrl}/csv/full-reset`, {
|
const response = await apiFetch(`${config.apiUrl}/csv/full-reset`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
@@ -572,7 +573,7 @@ export function DataManagement() {
|
|||||||
// Determine which operation is running
|
// Determine which operation is running
|
||||||
const type = isUpdating ? "update" : "reset";
|
const type = isUpdating ? "update" : "reset";
|
||||||
|
|
||||||
const response = await fetch(`${config.apiUrl}/csv/cancel?type=${type}`, {
|
const response = await apiFetch(`${config.apiUrl}/csv/cancel?type=${type}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
@@ -613,11 +614,11 @@ export function DataManagement() {
|
|||||||
console.log("Fetching history data...");
|
console.log("Fetching history data...");
|
||||||
|
|
||||||
const [importRes, calcRes, moduleRes, tableRes, tableCountsRes] = await Promise.all([
|
const [importRes, calcRes, moduleRes, tableRes, tableCountsRes] = await Promise.all([
|
||||||
fetch(`${config.apiUrl}/csv/history/import`, { credentials: 'include' }),
|
apiFetch(`${config.apiUrl}/csv/history/import`, { credentials: 'include' }),
|
||||||
fetch(`${config.apiUrl}/csv/history/calculate`, { credentials: 'include' }),
|
apiFetch(`${config.apiUrl}/csv/history/calculate`, { credentials: 'include' }),
|
||||||
fetch(`${config.apiUrl}/csv/status/modules`, { credentials: 'include' }),
|
apiFetch(`${config.apiUrl}/csv/status/modules`, { credentials: 'include' }),
|
||||||
fetch(`${config.apiUrl}/csv/status/tables`, { credentials: 'include' }),
|
apiFetch(`${config.apiUrl}/csv/status/tables`, { credentials: 'include' }),
|
||||||
fetch(`${config.apiUrl}/csv/status/table-counts`, { credentials: 'include' }),
|
apiFetch(`${config.apiUrl}/csv/status/table-counts`, { credentials: 'include' }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
console.log("Fetch responses:", {
|
console.log("Fetch responses:", {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -25,7 +26,7 @@ export function GlobalSettings() {
|
|||||||
const loadSettings = async () => {
|
const loadSettings = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(`${config.apiUrl}/config/global`, {
|
const response = await apiFetch(`${config.apiUrl}/config/global`, {
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -49,7 +50,7 @@ export function GlobalSettings() {
|
|||||||
|
|
||||||
const handleSaveSettings = async () => {
|
const handleSaveSettings = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.apiUrl}/config/global`, {
|
const response = await apiFetch(`${config.apiUrl}/config/global`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -43,7 +44,7 @@ export function ProductSettings() {
|
|||||||
const loadSettings = useCallback(async () => {
|
const loadSettings = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(`${config.apiUrl}/config/products?page=${page}&pageSize=${pageSize}&search=${encodeURIComponent(searchQuery)}`, {
|
const response = await apiFetch(`${config.apiUrl}/config/products?page=${page}&pageSize=${pageSize}&search=${encodeURIComponent(searchQuery)}`, {
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -75,7 +76,7 @@ export function ProductSettings() {
|
|||||||
const setting = settings.find(s => s.pid === pid);
|
const setting = settings.find(s => s.pid === pid);
|
||||||
if (!setting) return;
|
if (!setting) return;
|
||||||
|
|
||||||
const response = await fetch(`${config.apiUrl}/config/products/${pid}`, {
|
const response = await apiFetch(`${config.apiUrl}/config/products/${pid}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -104,7 +105,7 @@ export function ProductSettings() {
|
|||||||
|
|
||||||
const handleResetToDefault = useCallback(async (pid: string) => {
|
const handleResetToDefault = useCallback(async (pid: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.apiUrl}/config/products/${pid}/reset`, {
|
const response = await apiFetch(`${config.apiUrl}/config/products/${pid}/reset`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -148,7 +149,7 @@ export function PromptManagement() {
|
|||||||
const { data: prompts, isLoading } = useQuery<AiPrompt[]>({
|
const { data: prompts, isLoading } = useQuery<AiPrompt[]>({
|
||||||
queryKey: ["ai-prompts"],
|
queryKey: ["ai-prompts"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/ai-prompts`);
|
const response = await apiFetch(`${config.apiUrl}/ai-prompts`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch AI prompts");
|
throw new Error("Failed to fetch AI prompts");
|
||||||
}
|
}
|
||||||
@@ -159,7 +160,7 @@ export function PromptManagement() {
|
|||||||
const { data: fieldOptions } = useQuery<FieldOptions>({
|
const { data: fieldOptions } = useQuery<FieldOptions>({
|
||||||
queryKey: ["fieldOptions"],
|
queryKey: ["fieldOptions"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/import/field-options`);
|
const response = await apiFetch(`${config.apiUrl}/import/field-options`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch field options");
|
throw new Error("Failed to fetch field options");
|
||||||
}
|
}
|
||||||
@@ -194,7 +195,7 @@ export function PromptManagement() {
|
|||||||
|
|
||||||
const createMutation = useMutation({
|
const createMutation = useMutation({
|
||||||
mutationFn: async (data: { prompt_text: string; prompt_type: string; company: string | null; is_singleton: boolean }) => {
|
mutationFn: async (data: { prompt_text: string; prompt_type: string; company: string | null; is_singleton: boolean }) => {
|
||||||
const response = await fetch(`${config.apiUrl}/ai-prompts`, {
|
const response = await apiFetch(`${config.apiUrl}/ai-prompts`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -220,7 +221,7 @@ export function PromptManagement() {
|
|||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: async (data: { id: number; prompt_text: string; prompt_type: string; company: string | null; is_singleton: boolean }) => {
|
mutationFn: async (data: { id: number; prompt_text: string; prompt_type: string; company: string | null; is_singleton: boolean }) => {
|
||||||
const response = await fetch(`${config.apiUrl}/ai-prompts/${data.id}`, {
|
const response = await apiFetch(`${config.apiUrl}/ai-prompts/${data.id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -248,7 +249,7 @@ export function PromptManagement() {
|
|||||||
|
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: async (id: number) => {
|
mutationFn: async (id: number) => {
|
||||||
const response = await fetch(`${config.apiUrl}/ai-prompts/${id}`, {
|
const response = await apiFetch(`${config.apiUrl}/ai-prompts/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useMemo, useCallback, useEffect } from "react";
|
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -235,7 +236,7 @@ export function ReusableImageManagement() {
|
|||||||
const { data: images, isLoading } = useQuery<ReusableImage[]>({
|
const { data: images, isLoading } = useQuery<ReusableImage[]>({
|
||||||
queryKey: ["reusable-images"],
|
queryKey: ["reusable-images"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/reusable-images`);
|
const response = await apiFetch(`${config.apiUrl}/reusable-images`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch reusable images");
|
throw new Error("Failed to fetch reusable images");
|
||||||
}
|
}
|
||||||
@@ -246,7 +247,7 @@ export function ReusableImageManagement() {
|
|||||||
const { data: fieldOptions } = useQuery<FieldOptions>({
|
const { data: fieldOptions } = useQuery<FieldOptions>({
|
||||||
queryKey: ["fieldOptions"],
|
queryKey: ["fieldOptions"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/import/field-options`);
|
const response = await apiFetch(`${config.apiUrl}/import/field-options`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch field options");
|
throw new Error("Failed to fetch field options");
|
||||||
}
|
}
|
||||||
@@ -271,7 +272,7 @@ export function ReusableImageManagement() {
|
|||||||
throw new Error("Image file is required");
|
throw new Error("Image file is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${config.apiUrl}/reusable-images/upload`, {
|
const response = await apiFetch(`${config.apiUrl}/reusable-images/upload`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
@@ -297,7 +298,7 @@ export function ReusableImageManagement() {
|
|||||||
mutationFn: async (data: ImageFormData) => {
|
mutationFn: async (data: ImageFormData) => {
|
||||||
if (!data.id) throw new Error("Image ID is required for update");
|
if (!data.id) throw new Error("Image ID is required for update");
|
||||||
|
|
||||||
const response = await fetch(`${config.apiUrl}/reusable-images/${data.id}`, {
|
const response = await apiFetch(`${config.apiUrl}/reusable-images/${data.id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -328,7 +329,7 @@ export function ReusableImageManagement() {
|
|||||||
|
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: async (id: number) => {
|
mutationFn: async (id: number) => {
|
||||||
const response = await fetch(`${config.apiUrl}/reusable-images/${id}`, {
|
const response = await apiFetch(`${config.apiUrl}/reusable-images/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { TemplateForm } from "@/components/templates/TemplateForm";
|
import { TemplateForm } from "@/components/templates/TemplateForm";
|
||||||
@@ -91,7 +92,7 @@ export function TemplateManagement() {
|
|||||||
const { data: templates, isLoading } = useQuery<Template[]>({
|
const { data: templates, isLoading } = useQuery<Template[]>({
|
||||||
queryKey: ["templates"],
|
queryKey: ["templates"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/templates`);
|
const response = await apiFetch(`${config.apiUrl}/templates`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch templates");
|
throw new Error("Failed to fetch templates");
|
||||||
}
|
}
|
||||||
@@ -102,7 +103,7 @@ export function TemplateManagement() {
|
|||||||
const { data: fieldOptions } = useQuery<FieldOptions>({
|
const { data: fieldOptions } = useQuery<FieldOptions>({
|
||||||
queryKey: ["fieldOptions"],
|
queryKey: ["fieldOptions"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/import/field-options`);
|
const response = await apiFetch(`${config.apiUrl}/import/field-options`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch field options");
|
throw new Error("Failed to fetch field options");
|
||||||
}
|
}
|
||||||
@@ -112,7 +113,7 @@ export function TemplateManagement() {
|
|||||||
|
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: async (id: number) => {
|
mutationFn: async (id: number) => {
|
||||||
const response = await fetch(`${config.apiUrl}/templates/${id}`, {
|
const response = await apiFetch(`${config.apiUrl}/templates/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -122,7 +123,7 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps)
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRocketChatUsers = async () => {
|
const fetchRocketChatUsers = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.chatUrl}/users`);
|
const response = await apiFetch(`${config.chatUrl}/users`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect, useContext } from "react";
|
import { useState, useEffect, useContext } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -56,7 +57,7 @@ export function UserManagement() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// Fetch users
|
// Fetch users
|
||||||
const usersResponse = await fetch(`${config.authUrl}/users`, {
|
const usersResponse = await apiFetch(`${config.authUrl}/users`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@@ -82,7 +83,7 @@ export function UserManagement() {
|
|||||||
setUsers(usersData);
|
setUsers(usersData);
|
||||||
|
|
||||||
// Fetch permissions
|
// Fetch permissions
|
||||||
const permissionsResponse = await fetch(`${config.authUrl}/permissions/categories`, {
|
const permissionsResponse = await apiFetch(`${config.authUrl}/permissions/categories`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@@ -128,7 +129,7 @@ export function UserManagement() {
|
|||||||
|
|
||||||
const handleEditUser = async (userId: number) => {
|
const handleEditUser = async (userId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.authUrl}/users/${userId}`, {
|
const response = await apiFetch(`${config.authUrl}/users/${userId}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@@ -216,7 +217,7 @@ export function UserManagement() {
|
|||||||
|
|
||||||
console.log(`${method} request to ${endpoint}`);
|
console.log(`${method} request to ${endpoint}`);
|
||||||
|
|
||||||
const response = await fetch(endpoint, {
|
const response = await apiFetch(endpoint, {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -264,7 +265,7 @@ export function UserManagement() {
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const response = await fetch(`${config.authUrl}/users/${userId}`, {
|
const response = await apiFetch(`${config.authUrl}/users/${userId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -39,7 +40,7 @@ export function VendorSettings() {
|
|||||||
const loadSettings = useCallback(async () => {
|
const loadSettings = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(`${config.apiUrl}/config/vendors?page=${page}&pageSize=${pageSize}&search=${encodeURIComponent(searchQuery)}`, {
|
const response = await apiFetch(`${config.apiUrl}/config/vendors?page=${page}&pageSize=${pageSize}&search=${encodeURIComponent(searchQuery)}`, {
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -75,7 +76,7 @@ export function VendorSettings() {
|
|||||||
const setting = settings.find(s => s.vendor === vendor);
|
const setting = settings.find(s => s.vendor === vendor);
|
||||||
if (!setting) return;
|
if (!setting) return;
|
||||||
|
|
||||||
const response = await fetch(`${config.apiUrl}/config/vendors/${encodeURIComponent(vendor)}`, {
|
const response = await apiFetch(`${config.apiUrl}/config/vendors/${encodeURIComponent(vendor)}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -108,7 +109,7 @@ export function VendorSettings() {
|
|||||||
|
|
||||||
const handleResetToDefault = useCallback(async (vendor: string) => {
|
const handleResetToDefault = useCallback(async (vendor: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.apiUrl}/config/vendors/${encodeURIComponent(vendor)}/reset`, {
|
const response = await apiFetch(`${config.apiUrl}/config/vendors/${encodeURIComponent(vendor)}/reset`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import axios from 'axios';
|
import { apiClient } from "@/utils/apiClient";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -342,7 +342,7 @@ export function SearchProductTemplateDialog({ isOpen, onClose, onTemplateCreated
|
|||||||
apiParams.q = '*';
|
apiParams.q = '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios.get('/api/import/search-products', { params: apiParams });
|
const response = await apiClient.get('/api/import/search-products', { params: apiParams });
|
||||||
|
|
||||||
setSearchResults(response.data.map((product: Product) => ({
|
setSearchResults(response.data.map((product: Product) => ({
|
||||||
...product,
|
...product,
|
||||||
@@ -408,7 +408,7 @@ export function SearchProductTemplateDialog({ isOpen, onClose, onTemplateCreated
|
|||||||
|
|
||||||
const fetchFieldOptions = async () => {
|
const fetchFieldOptions = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/api/import/field-options');
|
const response = await apiClient.get('/api/import/field-options');
|
||||||
setFieldOptions(response.data);
|
setFieldOptions(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching field options:', error);
|
console.error('Error fetching field options:', error);
|
||||||
@@ -442,7 +442,7 @@ export function SearchProductTemplateDialog({ isOpen, onClose, onTemplateCreated
|
|||||||
// Fetch product categories if pid is available
|
// Fetch product categories if pid is available
|
||||||
if (product.pid) {
|
if (product.pid) {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`/api/import/product-categories/${product.pid}`);
|
const response = await apiClient.get(`/api/import/product-categories/${product.pid}`);
|
||||||
const productCategories = response.data;
|
const productCategories = response.data;
|
||||||
|
|
||||||
// Update the selected product with the categories
|
// Update the selected product with the categories
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import axios from 'axios';
|
import { apiClient } from "@/utils/apiClient";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Loader2, ChevronsUpDown, Check } from 'lucide-react';
|
import { Loader2, ChevronsUpDown, Check } from 'lucide-react';
|
||||||
@@ -230,7 +230,7 @@ export function TemplateForm({
|
|||||||
|
|
||||||
console.log('Sending request to:', endpoint, 'with data:', cleanFormData);
|
console.log('Sending request to:', endpoint, 'with data:', cleanFormData);
|
||||||
|
|
||||||
await axios[method](endpoint, cleanFormData);
|
await apiClient[method](endpoint, cleanFormData);
|
||||||
|
|
||||||
toast.success(
|
toast.success(
|
||||||
mode === 'edit' ? 'Template updated successfully' : 'Template created successfully'
|
mode === 'edit' ? 'Template updated successfully' : 'Template created successfully'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card';
|
||||||
import { InventoryValueTrend } from '../components/analytics/InventoryValueTrend';
|
import { InventoryValueTrend } from '../components/analytics/InventoryValueTrend';
|
||||||
import { InventoryFlow } from '../components/analytics/InventoryFlow';
|
import { InventoryFlow } from '../components/analytics/InventoryFlow';
|
||||||
@@ -31,7 +32,7 @@ export function Analytics() {
|
|||||||
const { data: summary, isLoading, isError } = useQuery<InventorySummary>({
|
const { data: summary, isLoading, isError } = useQuery<InventorySummary>({
|
||||||
queryKey: ['inventory-summary'],
|
queryKey: ['inventory-summary'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/analytics/inventory-summary`);
|
const response = await apiFetch(`${config.apiUrl}/analytics/inventory-summary`);
|
||||||
if (!response.ok) throw new Error('Failed to fetch inventory summary');
|
if (!response.ok) throw new Error('Failed to fetch inventory summary');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -655,7 +656,7 @@ export function BlackFridayDashboard() {
|
|||||||
|
|
||||||
const fetchRealtime = async () => {
|
const fetchRealtime = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/dashboard-analytics/realtime/basic", {
|
const response = await apiFetch("/api/dashboard-analytics/realtime/basic", {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useMemo, useCallback, type ReactNode } from "react";
|
import { useState, useMemo, useCallback, type ReactNode } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
@@ -231,7 +232,7 @@ function useAggregateList<TResponse, TSort extends string>(opts: UseAggregateLis
|
|||||||
const query = useQuery<TResponse, Error>({
|
const query = useQuery<TResponse, Error>({
|
||||||
queryKey: [opts.queryKeyPrefix, queryParams.toString()],
|
queryKey: [opts.queryKeyPrefix, queryParams.toString()],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/${opts.endpoint}?${queryParams.toString()}`, {
|
const response = await apiFetch(`${config.apiUrl}/${opts.endpoint}?${queryParams.toString()}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
if (!response.ok) throw new Error(`Network response was not ok (${response.status})`);
|
if (!response.ok) throw new Error(`Network response was not ok (${response.status})`);
|
||||||
@@ -420,7 +421,7 @@ function BrandsPanel() {
|
|||||||
const { data: statsData, isLoading: isLoadingStats } = useQuery<BrandStats, Error>({
|
const { data: statsData, isLoading: isLoadingStats } = useQuery<BrandStats, Error>({
|
||||||
queryKey: ["brandsStats"],
|
queryKey: ["brandsStats"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/brands-aggregate/stats`, { credentials: "include" });
|
const response = await apiFetch(`${config.apiUrl}/brands-aggregate/stats`, { credentials: "include" });
|
||||||
if (!response.ok) throw new Error("Failed to fetch brand stats");
|
if (!response.ok) throw new Error("Failed to fetch brand stats");
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
@@ -429,7 +430,7 @@ function BrandsPanel() {
|
|||||||
const { data: filterOptions } = useQuery<FilterOptions, Error>({
|
const { data: filterOptions } = useQuery<FilterOptions, Error>({
|
||||||
queryKey: ["brandsFilterOptions"],
|
queryKey: ["brandsFilterOptions"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/brands-aggregate/filter-options`, { credentials: "include" });
|
const response = await apiFetch(`${config.apiUrl}/brands-aggregate/filter-options`, { credentials: "include" });
|
||||||
if (!response.ok) throw new Error("Failed to fetch filter options");
|
if (!response.ok) throw new Error("Failed to fetch filter options");
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
@@ -619,7 +620,7 @@ function VendorsPanel() {
|
|||||||
const { data: statsData, isLoading: isLoadingStats } = useQuery<VendorStats, Error>({
|
const { data: statsData, isLoading: isLoadingStats } = useQuery<VendorStats, Error>({
|
||||||
queryKey: ["vendorsStats"],
|
queryKey: ["vendorsStats"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/vendors-aggregate/stats`, { credentials: "include" });
|
const response = await apiFetch(`${config.apiUrl}/vendors-aggregate/stats`, { credentials: "include" });
|
||||||
if (!response.ok) throw new Error("Failed to fetch vendor stats");
|
if (!response.ok) throw new Error("Failed to fetch vendor stats");
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
@@ -628,7 +629,7 @@ function VendorsPanel() {
|
|||||||
const { data: filterOptions } = useQuery<FilterOptions, Error>({
|
const { data: filterOptions } = useQuery<FilterOptions, Error>({
|
||||||
queryKey: ["vendorsFilterOptions"],
|
queryKey: ["vendorsFilterOptions"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/vendors-aggregate/filter-options`, { credentials: "include" });
|
const response = await apiFetch(`${config.apiUrl}/vendors-aggregate/filter-options`, { credentials: "include" });
|
||||||
if (!response.ok) throw new Error("Failed to fetch filter options");
|
if (!response.ok) throw new Error("Failed to fetch filter options");
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { apiClient } from "@/utils/apiClient";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Loader2, Sparkles, Save } from "lucide-react";
|
import { Loader2, Sparkles, Save } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -169,7 +171,7 @@ export default function BulkEdit() {
|
|||||||
const hadExisting = allProducts.length > 0;
|
const hadExisting = allProducts.length > 0;
|
||||||
setIsLoadingProducts(true);
|
setIsLoadingProducts(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get("/api/import/search-products", {
|
const res = await apiClient.get("/api/import/search-products", {
|
||||||
params: { pid: pids.join(",") },
|
params: { pid: pids.join(",") },
|
||||||
});
|
});
|
||||||
const fetched = res.data as SearchProduct[];
|
const fetched = res.data as SearchProduct[];
|
||||||
@@ -200,7 +202,7 @@ export default function BulkEdit() {
|
|||||||
setAllProducts([]);
|
setAllProducts([]);
|
||||||
setIsLoadingProducts(true);
|
setIsLoadingProducts(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get(`/api/import/${endpoint}`, { signal: controller.signal });
|
const res = await apiClient.get(`/api/import/${endpoint}`, { signal: controller.signal });
|
||||||
setAllProducts(res.data);
|
setAllProducts(res.data);
|
||||||
setPage(1);
|
setPage(1);
|
||||||
toast.success(`Loaded ${res.data.length} ${label} products`);
|
toast.success(`Loaded ${res.data.length} ${label} products`);
|
||||||
@@ -215,7 +217,7 @@ export default function BulkEdit() {
|
|||||||
if (landingExtras[tabKey]) return;
|
if (landingExtras[tabKey]) return;
|
||||||
setIsLoadingExtras(true);
|
setIsLoadingExtras(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get("/api/import/landing-extras", {
|
const res = await apiClient.get("/api/import/landing-extras", {
|
||||||
params: { catId, sid: 0 },
|
params: { catId, sid: 0 },
|
||||||
});
|
});
|
||||||
setLandingExtras((prev) => ({ ...prev, [tabKey]: res.data }));
|
setLandingExtras((prev) => ({ ...prev, [tabKey]: res.data }));
|
||||||
@@ -234,7 +236,7 @@ export default function BulkEdit() {
|
|||||||
setAllProducts([]);
|
setAllProducts([]);
|
||||||
setIsLoadingProducts(true);
|
setIsLoadingProducts(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get("/api/import/path-products", {
|
const res = await apiClient.get("/api/import/path-products", {
|
||||||
params: { path: extra.path },
|
params: { path: extra.path },
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
@@ -279,7 +281,7 @@ export default function BulkEdit() {
|
|||||||
try {
|
try {
|
||||||
const params: Record<string, string> = { company: lineCompany, line: lineLine };
|
const params: Record<string, string> = { company: lineCompany, line: lineLine };
|
||||||
if (lineSubline) params.subline = lineSubline;
|
if (lineSubline) params.subline = lineSubline;
|
||||||
const res = await axios.get("/api/import/line-products", { params, signal: controller.signal });
|
const res = await apiClient.get("/api/import/line-products", { params, signal: controller.signal });
|
||||||
setAllProducts(res.data);
|
setAllProducts(res.data);
|
||||||
setPage(1);
|
setPage(1);
|
||||||
toast.success(`Loaded ${res.data.length} products`);
|
toast.success(`Loaded ${res.data.length} products`);
|
||||||
@@ -378,7 +380,7 @@ export default function BulkEdit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(endpoint, {
|
const response = await apiFetch(endpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ product: payload }),
|
body: JSON.stringify({ product: payload }),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useMemo, useCallback, useEffect } from "react";
|
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
@@ -392,7 +393,7 @@ export function Categories() {
|
|||||||
const url = `${config.apiUrl}/categories-aggregate?${queryParams.toString()}`;
|
const url = `${config.apiUrl}/categories-aggregate?${queryParams.toString()}`;
|
||||||
console.log("Fetching categories from URL:", url);
|
console.log("Fetching categories from URL:", url);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await apiFetch(url, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
@@ -427,7 +428,7 @@ export function Categories() {
|
|||||||
>({
|
>({
|
||||||
queryKey: ["categoriesStats"],
|
queryKey: ["categoriesStats"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(
|
const response = await apiFetch(
|
||||||
`${config.apiUrl}/categories-aggregate/stats`,
|
`${config.apiUrl}/categories-aggregate/stats`,
|
||||||
{
|
{
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
@@ -444,7 +445,7 @@ export function Categories() {
|
|||||||
>({
|
>({
|
||||||
queryKey: ["categoriesFilterOptions"],
|
queryKey: ["categoriesFilterOptions"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(
|
const response = await apiFetch(
|
||||||
`${config.apiUrl}/categories-aggregate/filter-options`,
|
`${config.apiUrl}/categories-aggregate/filter-options`,
|
||||||
{
|
{
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect, useContext } from 'react';
|
import React, { useState, useEffect, useContext } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -63,7 +64,7 @@ export function Chat() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${config.chatUrl}/users`);
|
const response = await apiFetch(`${config.chatUrl}/users`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
@@ -123,7 +124,7 @@ export function Chat() {
|
|||||||
|
|
||||||
setSearching(true);
|
setSearching(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await apiFetch(
|
||||||
`${config.chatUrl}/users/${selectedUserId}/search?q=${encodeURIComponent(globalSearchQuery)}&limit=20`
|
`${config.chatUrl}/users/${selectedUserId}/search?q=${encodeURIComponent(globalSearchQuery)}&limit=20`
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState, useMemo, Fragment } from "react";
|
import { useEffect, useState, useMemo, Fragment } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
@@ -110,7 +111,7 @@ export default function Forecasting() {
|
|||||||
const { data: brands = [], isLoading: brandsLoading } = useQuery({
|
const { data: brands = [], isLoading: brandsLoading } = useQuery({
|
||||||
queryKey: ["brands"],
|
queryKey: ["brands"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch("/api/products/brands");
|
const response = await apiFetch("/api/products/brands");
|
||||||
if (!response.ok) throw new Error("Failed to fetch brands");
|
if (!response.ok) throw new Error("Failed to fetch brands");
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return Array.isArray(data) ? data : [];
|
return Array.isArray(data) ? data : [];
|
||||||
@@ -128,7 +129,7 @@ export default function Forecasting() {
|
|||||||
startDate: dateRange.from?.toISOString() || "",
|
startDate: dateRange.from?.toISOString() || "",
|
||||||
endDate: dateRange.to?.toISOString() || "",
|
endDate: dateRange.to?.toISOString() || "",
|
||||||
});
|
});
|
||||||
const response = await fetch(`/api/analytics/forecast-v2?${params}`);
|
const response = await apiFetch(`/api/analytics/forecast-v2?${params}`);
|
||||||
if (!response.ok) throw new Error("Failed to fetch forecast data");
|
if (!response.ok) throw new Error("Failed to fetch forecast data");
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useMemo, useRef, useState, type FormEvent, type MouseEvent } from "react";
|
import { useEffect, useMemo, useRef, useState, type FormEvent, type MouseEvent } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Search, Loader2, PackageOpen, Copy, Check } from "lucide-react";
|
import { Search, Loader2, PackageOpen, Copy, Check } from "lucide-react";
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ export default function HtsLookup() {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const params = new URLSearchParams({ search: submittedTerm });
|
const params = new URLSearchParams({ search: submittedTerm });
|
||||||
const response = await fetch(`/api/hts-lookup?${params.toString()}`);
|
const response = await apiFetch(`/api/hts-lookup?${params.toString()}`);
|
||||||
const payload = await response.json().catch(() => ({}));
|
const payload = await response.json().catch(() => ({}));
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useContext, useMemo } from "react";
|
import { useState, useContext, useMemo } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { ReactSpreadsheetImport, StepType } from "@/components/product-import";
|
import { ReactSpreadsheetImport, StepType } from "@/components/product-import";
|
||||||
import type { StepState } from "@/components/product-import/steps/UploadFlow";
|
import type { StepState } from "@/components/product-import/steps/UploadFlow";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -276,7 +277,7 @@ export function Import() {
|
|||||||
const { data: fieldOptions, isLoading: isLoadingOptions } = useQuery({
|
const { data: fieldOptions, isLoading: isLoadingOptions } = useQuery({
|
||||||
queryKey: ["import-field-options"],
|
queryKey: ["import-field-options"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`${config.apiUrl}/import/field-options`);
|
const response = await apiFetch(`${config.apiUrl}/import/field-options`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch field options");
|
throw new Error("Failed to fetch field options");
|
||||||
}
|
}
|
||||||
@@ -289,7 +290,7 @@ export function Import() {
|
|||||||
queryKey: ["product-lines", selectedCompany],
|
queryKey: ["product-lines", selectedCompany],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!selectedCompany) return [];
|
if (!selectedCompany) return [];
|
||||||
const response = await fetch(`${config.apiUrl}/import/product-lines/${selectedCompany}`);
|
const response = await apiFetch(`${config.apiUrl}/import/product-lines/${selectedCompany}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch product lines");
|
throw new Error("Failed to fetch product lines");
|
||||||
}
|
}
|
||||||
@@ -303,7 +304,7 @@ export function Import() {
|
|||||||
queryKey: ["sublines", selectedLine],
|
queryKey: ["sublines", selectedLine],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!selectedLine) return [];
|
if (!selectedLine) return [];
|
||||||
const response = await fetch(`${config.apiUrl}/import/sublines/${selectedLine}`);
|
const response = await apiFetch(`${config.apiUrl}/import/sublines/${selectedLine}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch sublines");
|
throw new Error("Failed to fetch sublines");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { apiClient } from "@/utils/apiClient";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Loader2, RefreshCw } from "lucide-react";
|
import { Loader2, RefreshCw } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -284,7 +285,7 @@ export default function ProductEditor() {
|
|||||||
const hadExisting = allProducts.length > 0;
|
const hadExisting = allProducts.length > 0;
|
||||||
setIsLoadingProducts(true);
|
setIsLoadingProducts(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get("/api/import/search-products", {
|
const res = await apiClient.get("/api/import/search-products", {
|
||||||
params: { pid: pids.join(",") },
|
params: { pid: pids.join(",") },
|
||||||
});
|
});
|
||||||
const fetched = res.data as SearchProduct[];
|
const fetched = res.data as SearchProduct[];
|
||||||
@@ -319,7 +320,7 @@ export default function ProductEditor() {
|
|||||||
setAllProducts([]);
|
setAllProducts([]);
|
||||||
setIsLoadingProducts(true);
|
setIsLoadingProducts(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get(`/api/import/${endpoint}`, { signal: controller.signal });
|
const res = await apiClient.get(`/api/import/${endpoint}`, { signal: controller.signal });
|
||||||
setAllProducts(res.data);
|
setAllProducts(res.data);
|
||||||
setPage(1);
|
setPage(1);
|
||||||
toast.success(`Loaded ${res.data.length} ${label} products`);
|
toast.success(`Loaded ${res.data.length} ${label} products`);
|
||||||
@@ -334,7 +335,7 @@ export default function ProductEditor() {
|
|||||||
if (landingExtras[tabKey]) return;
|
if (landingExtras[tabKey]) return;
|
||||||
setIsLoadingExtras(true);
|
setIsLoadingExtras(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get("/api/import/landing-extras", {
|
const res = await apiClient.get("/api/import/landing-extras", {
|
||||||
params: { catId, sid: 0 },
|
params: { catId, sid: 0 },
|
||||||
});
|
});
|
||||||
setLandingExtras((prev) => ({ ...prev, [tabKey]: res.data }));
|
setLandingExtras((prev) => ({ ...prev, [tabKey]: res.data }));
|
||||||
@@ -356,7 +357,7 @@ export default function ProductEditor() {
|
|||||||
setAllProducts([]);
|
setAllProducts([]);
|
||||||
setIsLoadingProducts(true);
|
setIsLoadingProducts(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get("/api/import/path-products", {
|
const res = await apiClient.get("/api/import/path-products", {
|
||||||
params: { path: extra.path },
|
params: { path: extra.path },
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
@@ -411,7 +412,7 @@ export default function ProductEditor() {
|
|||||||
try {
|
try {
|
||||||
const params: Record<string, string> = { company: lineCompany, line: lineLine };
|
const params: Record<string, string> = { company: lineCompany, line: lineLine };
|
||||||
if (lineSubline) params.subline = lineSubline;
|
if (lineSubline) params.subline = lineSubline;
|
||||||
const res = await axios.get("/api/import/line-products", { params, signal: controller.signal });
|
const res = await apiClient.get("/api/import/line-products", { params, signal: controller.signal });
|
||||||
setAllProducts(res.data);
|
setAllProducts(res.data);
|
||||||
setPage(1);
|
setPage(1);
|
||||||
toast.success(`Loaded ${res.data.length} products`);
|
toast.success(`Loaded ${res.data.length} products`);
|
||||||
@@ -432,7 +433,7 @@ export default function ProductEditor() {
|
|||||||
setQueryStatus(null);
|
setQueryStatus(null);
|
||||||
setIsLoadingProducts(true);
|
setIsLoadingProducts(true);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get("/api/import/query-products", {
|
const res = await apiClient.get("/api/import/query-products", {
|
||||||
params: { query_id: qid },
|
params: { query_id: qid },
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useMemo, useCallback } from "react";
|
import { useState, useMemo, useCallback } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
@@ -345,7 +346,7 @@ function LineProductDetail({ brand, line }: { brand: string; line: string }) {
|
|||||||
const { data, isLoading } = useQuery<LineProductsResponse>({
|
const { data, isLoading } = useQuery<LineProductsResponse>({
|
||||||
queryKey: ['lineProducts', brand, line],
|
queryKey: ['lineProducts', brand, line],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(
|
const res = await apiFetch(
|
||||||
`${config.apiUrl}/lines-aggregate/${encodeURIComponent(brand)}/${encodeURIComponent(line)}/products`,
|
`${config.apiUrl}/lines-aggregate/${encodeURIComponent(brand)}/${encodeURIComponent(line)}/products`,
|
||||||
{ credentials: 'include' }
|
{ credentials: 'include' }
|
||||||
);
|
);
|
||||||
@@ -484,7 +485,7 @@ export function ProductLines() {
|
|||||||
const { data: listData, isLoading: isLoadingList, error: listError } = useQuery<LineResponse, Error>({
|
const { data: listData, isLoading: isLoadingList, error: listError } = useQuery<LineResponse, Error>({
|
||||||
queryKey: ['productLines', queryParams.toString()],
|
queryKey: ['productLines', queryParams.toString()],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`${config.apiUrl}/lines-aggregate?${queryParams.toString()}`, {
|
const res = await apiFetch(`${config.apiUrl}/lines-aggregate?${queryParams.toString()}`, {
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`Network response was not ok (${res.status})`);
|
if (!res.ok) throw new Error(`Network response was not ok (${res.status})`);
|
||||||
@@ -496,7 +497,7 @@ export function ProductLines() {
|
|||||||
const { data: statsData, isLoading: isLoadingStats } = useQuery<LineStats, Error>({
|
const { data: statsData, isLoading: isLoadingStats } = useQuery<LineStats, Error>({
|
||||||
queryKey: ['productLineStats'],
|
queryKey: ['productLineStats'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`${config.apiUrl}/lines-aggregate/stats`, { credentials: 'include' });
|
const res = await apiFetch(`${config.apiUrl}/lines-aggregate/stats`, { credentials: 'include' });
|
||||||
if (!res.ok) throw new Error("Failed to fetch stats");
|
if (!res.ok) throw new Error("Failed to fetch stats");
|
||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
@@ -505,7 +506,7 @@ export function ProductLines() {
|
|||||||
const { data: filterOptions } = useQuery<LineFilterOptions, Error>({
|
const { data: filterOptions } = useQuery<LineFilterOptions, Error>({
|
||||||
queryKey: ['productLineFilterOptions'],
|
queryKey: ['productLineFilterOptions'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`${config.apiUrl}/lines-aggregate/filter-options`, { credentials: 'include' });
|
const res = await apiFetch(`${config.apiUrl}/lines-aggregate/filter-options`, { credentials: 'include' });
|
||||||
if (!res.ok) throw new Error("Failed to fetch filter options");
|
if (!res.ok) throw new Error("Failed to fetch filter options");
|
||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { useQuery, keepPreviousData } from '@tanstack/react-query';
|
import { useQuery, keepPreviousData } from '@tanstack/react-query';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
@@ -289,7 +290,7 @@ export function Products() {
|
|||||||
params.append('showInvisible', 'true');
|
params.append('showInvisible', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/metrics?${params.toString()}`, {credentials: 'include'});
|
const response = await apiFetch(`/api/metrics?${params.toString()}`, {credentials: 'include'});
|
||||||
if (!response.ok) throw new Error('Failed to fetch products');
|
if (!response.ok) throw new Error('Failed to fetch products');
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -323,7 +324,7 @@ export function Products() {
|
|||||||
queryKey: ['filterOptions'],
|
queryKey: ['filterOptions'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/metrics/filter-options');
|
const response = await apiFetch('/api/metrics/filter-options');
|
||||||
if (!response.ok) throw new Error('Failed to fetch filter options');
|
if (!response.ok) throw new Error('Failed to fetch filter options');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
@@ -348,7 +349,7 @@ export function Products() {
|
|||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (showNonReplenishable) params.append('showNonReplenishable', 'true');
|
if (showNonReplenishable) params.append('showNonReplenishable', 'true');
|
||||||
if (showInvisible) params.append('showInvisible', 'true');
|
if (showInvisible) params.append('showInvisible', 'true');
|
||||||
const response = await fetch(`/api/metrics/summary?${params.toString()}`, { credentials: 'include' });
|
const response = await apiFetch(`/api/metrics/summary?${params.toString()}`, { credentials: 'include' });
|
||||||
if (!response.ok) throw new Error('Failed to fetch summary');
|
if (!response.ok) throw new Error('Failed to fetch summary');
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState, useRef, useMemo } from "react";
|
import { useEffect, useState, useRef, useMemo } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import OrderMetricsCard from "../components/purchase-orders/OrderMetricsCard";
|
import OrderMetricsCard from "../components/purchase-orders/OrderMetricsCard";
|
||||||
import VendorMetricsCard from "../components/purchase-orders/VendorMetricsCard";
|
import VendorMetricsCard from "../components/purchase-orders/VendorMetricsCard";
|
||||||
import CategoryMetricsCard from "../components/purchase-orders/CategoryMetricsCard";
|
import CategoryMetricsCard from "../components/purchase-orders/CategoryMetricsCard";
|
||||||
@@ -151,7 +152,7 @@ export default function PurchaseOrders() {
|
|||||||
console.log("Fetching data with params:", searchParams.toString());
|
console.log("Fetching data with params:", searchParams.toString());
|
||||||
|
|
||||||
// Fetch orders first separately to handle errors better
|
// Fetch orders first separately to handle errors better
|
||||||
const purchaseOrdersRes = await fetch(`/api/purchase-orders?${searchParams.toString()}`);
|
const purchaseOrdersRes = await apiFetch(`/api/purchase-orders?${searchParams.toString()}`);
|
||||||
|
|
||||||
if (!purchaseOrdersRes.ok) {
|
if (!purchaseOrdersRes.ok) {
|
||||||
const errorText = await purchaseOrdersRes.text();
|
const errorText = await purchaseOrdersRes.text();
|
||||||
@@ -179,9 +180,9 @@ export default function PurchaseOrders() {
|
|||||||
|
|
||||||
// Now fetch the additional data in parallel
|
// Now fetch the additional data in parallel
|
||||||
const [vendorMetricsRes, costAnalysisRes, deliveryMetricsRes] = await Promise.all([
|
const [vendorMetricsRes, costAnalysisRes, deliveryMetricsRes] = await Promise.all([
|
||||||
fetch("/api/purchase-orders/vendor-metrics"),
|
apiFetch("/api/purchase-orders/vendor-metrics"),
|
||||||
fetch("/api/purchase-orders/cost-analysis"),
|
apiFetch("/api/purchase-orders/cost-analysis"),
|
||||||
fetch("/api/purchase-orders/delivery-metrics"),
|
apiFetch("/api/purchase-orders/delivery-metrics"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let vendorMetricsData = [];
|
let vendorMetricsData = [];
|
||||||
@@ -369,8 +370,8 @@ export default function PurchaseOrders() {
|
|||||||
const dateParam = oneYearAgo.toISOString().split("T")[0]; // Format as YYYY-MM-DD
|
const dateParam = oneYearAgo.toISOString().split("T")[0]; // Format as YYYY-MM-DD
|
||||||
|
|
||||||
const [vendorResponse, categoryResponse] = await Promise.all([
|
const [vendorResponse, categoryResponse] = await Promise.all([
|
||||||
fetch(`/api/purchase-orders/vendor-analysis?since=${dateParam}`),
|
apiFetch(`/api/purchase-orders/vendor-analysis?since=${dateParam}`),
|
||||||
fetch(`/api/purchase-orders/category-analysis?since=${dateParam}`),
|
apiFetch(`/api/purchase-orders/category-analysis?since=${dateParam}`),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (vendorResponse.ok) {
|
if (vendorResponse.ok) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Fragment, useMemo, useState } from "react";
|
import { Fragment, useMemo, useState } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
Loader2,
|
Loader2,
|
||||||
@@ -205,7 +206,7 @@ function ProductHistory({
|
|||||||
supplierId: String(supplierId),
|
supplierId: String(supplierId),
|
||||||
windowDays: String(windowDays),
|
windowDays: String(windowDays),
|
||||||
});
|
});
|
||||||
const res = await fetch(
|
const res = await apiFetch(
|
||||||
`/api/repeat-orders/${pid}/history?${params.toString()}`
|
`/api/repeat-orders/${pid}/history?${params.toString()}`
|
||||||
);
|
);
|
||||||
if (!res.ok) throw new Error("Failed to load history");
|
if (!res.ok) throw new Error("Failed to load history");
|
||||||
@@ -370,7 +371,7 @@ export default function RepeatOrders() {
|
|||||||
const { data: suppliersData } = useQuery<{ suppliers: Supplier[] }>({
|
const { data: suppliersData } = useQuery<{ suppliers: Supplier[] }>({
|
||||||
queryKey: ["repeat-orders-suppliers"],
|
queryKey: ["repeat-orders-suppliers"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`/api/repeat-orders/suppliers?windowDays=90`);
|
const res = await apiFetch(`/api/repeat-orders/suppliers?windowDays=90`);
|
||||||
if (!res.ok) throw new Error("Failed to load suppliers");
|
if (!res.ok) throw new Error("Failed to load suppliers");
|
||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
@@ -386,7 +387,7 @@ export default function RepeatOrders() {
|
|||||||
minPoCount: String(minPoCount),
|
minPoCount: String(minPoCount),
|
||||||
maxAvgQty: String(maxAvgQty),
|
maxAvgQty: String(maxAvgQty),
|
||||||
});
|
});
|
||||||
const res = await fetch(`/api/repeat-orders?${params.toString()}`);
|
const res = await apiFetch(`/api/repeat-orders?${params.toString()}`);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const payload = await res.json().catch(() => ({}));
|
const payload = await res.json().catch(() => ({}));
|
||||||
throw new Error(payload.error || "Failed to load repeat orders");
|
throw new Error(payload.error || "Failed to load repeat orders");
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useMemo, useRef, useState, type FormEvent, type MouseEvent } from "react";
|
import { useEffect, useMemo, useRef, useState, type FormEvent, type MouseEvent } from "react";
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Search, Loader2, PackageOpen, Copy, Check, ChevronsUpDown, X } from "lucide-react";
|
import { Search, Loader2, PackageOpen, Copy, Check, ChevronsUpDown, X } from "lucide-react";
|
||||||
|
|
||||||
@@ -122,7 +123,7 @@ export default function SpecLookup() {
|
|||||||
const { data: brandsData } = useQuery<{ brands: string[] }>({
|
const { data: brandsData } = useQuery<{ brands: string[] }>({
|
||||||
queryKey: ["spec-lookup-brands"],
|
queryKey: ["spec-lookup-brands"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`/api/brands-aggregate/filter-options`);
|
const response = await apiFetch(`/api/brands-aggregate/filter-options`);
|
||||||
if (!response.ok) throw new Error("Failed to load brands");
|
if (!response.ok) throw new Error("Failed to load brands");
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
@@ -143,7 +144,7 @@ export default function SpecLookup() {
|
|||||||
if (submitted?.company) params.set("company", submitted.company);
|
if (submitted?.company) params.set("company", submitted.company);
|
||||||
if (submitted?.term) params.set("term", submitted.term);
|
if (submitted?.term) params.set("term", submitted.term);
|
||||||
|
|
||||||
const response = await fetch(`/api/spec-lookup?${params.toString()}`);
|
const response = await apiFetch(`/api/spec-lookup?${params.toString()}`);
|
||||||
const payload = await response.json().catch(() => ({}));
|
const payload = await response.json().catch(() => ({}));
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
/**
|
/**
|
||||||
* Import Audit Log API Service
|
* Import Audit Log API Service
|
||||||
*
|
*
|
||||||
@@ -31,7 +32,7 @@ export interface ImportAuditLogEntry {
|
|||||||
*/
|
*/
|
||||||
export async function createImportAuditLog(entry: ImportAuditLogEntry): Promise<void> {
|
export async function createImportAuditLog(entry: ImportAuditLogEntry): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await fetch(BASE_URL, {
|
await apiFetch(BASE_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(entry),
|
body: JSON.stringify(entry),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
* Handles all API calls for import session persistence.
|
* Handles all API calls for import session persistence.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
import type {
|
import type {
|
||||||
ImportSession,
|
ImportSession,
|
||||||
ImportSessionListItem,
|
ImportSessionListItem,
|
||||||
@@ -36,7 +37,7 @@ async function handleResponse<T>(response: Response): Promise<T> {
|
|||||||
* List all sessions for a user (named + unnamed)
|
* List all sessions for a user (named + unnamed)
|
||||||
*/
|
*/
|
||||||
export async function listSessions(userId: number): Promise<ImportSessionListItem[]> {
|
export async function listSessions(userId: number): Promise<ImportSessionListItem[]> {
|
||||||
const response = await fetch(`${BASE_URL}?user_id=${userId}`);
|
const response = await apiFetch(`${BASE_URL}?user_id=${userId}`);
|
||||||
return handleResponse<ImportSessionListItem[]>(response);
|
return handleResponse<ImportSessionListItem[]>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ export async function listSessions(userId: number): Promise<ImportSessionListIte
|
|||||||
* Get a specific session by ID (includes full data)
|
* Get a specific session by ID (includes full data)
|
||||||
*/
|
*/
|
||||||
export async function getSession(id: number): Promise<ImportSession> {
|
export async function getSession(id: number): Promise<ImportSession> {
|
||||||
const response = await fetch(`${BASE_URL}/${id}`);
|
const response = await apiFetch(`${BASE_URL}/${id}`);
|
||||||
return handleResponse<ImportSession>(response);
|
return handleResponse<ImportSession>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ export async function getSession(id: number): Promise<ImportSession> {
|
|||||||
* Create a new named session
|
* Create a new named session
|
||||||
*/
|
*/
|
||||||
export async function createSession(data: ImportSessionCreateRequest): Promise<ImportSession> {
|
export async function createSession(data: ImportSessionCreateRequest): Promise<ImportSession> {
|
||||||
const response = await fetch(BASE_URL, {
|
const response = await apiFetch(BASE_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -64,7 +65,7 @@ export async function createSession(data: ImportSessionCreateRequest): Promise<I
|
|||||||
* Update an existing session by ID
|
* Update an existing session by ID
|
||||||
*/
|
*/
|
||||||
export async function updateSession(id: number, data: ImportSessionUpdateRequest): Promise<ImportSession> {
|
export async function updateSession(id: number, data: ImportSessionUpdateRequest): Promise<ImportSession> {
|
||||||
const response = await fetch(`${BASE_URL}/${id}`, {
|
const response = await apiFetch(`${BASE_URL}/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -76,7 +77,7 @@ export async function updateSession(id: number, data: ImportSessionUpdateRequest
|
|||||||
* Autosave - upsert the unnamed session for a user
|
* Autosave - upsert the unnamed session for a user
|
||||||
*/
|
*/
|
||||||
export async function autosaveSession(data: ImportSessionAutosaveRequest): Promise<ImportSession> {
|
export async function autosaveSession(data: ImportSessionAutosaveRequest): Promise<ImportSession> {
|
||||||
const response = await fetch(`${BASE_URL}/autosave`, {
|
const response = await apiFetch(`${BASE_URL}/autosave`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -88,7 +89,7 @@ export async function autosaveSession(data: ImportSessionAutosaveRequest): Promi
|
|||||||
* Delete a session by ID
|
* Delete a session by ID
|
||||||
*/
|
*/
|
||||||
export async function deleteSession(id: number): Promise<void> {
|
export async function deleteSession(id: number): Promise<void> {
|
||||||
const response = await fetch(`${BASE_URL}/${id}`, {
|
const response = await apiFetch(`${BASE_URL}/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -108,7 +109,7 @@ export async function deleteSession(id: number): Promise<void> {
|
|||||||
* Delete the unnamed/autosave session for a user
|
* Delete the unnamed/autosave session for a user
|
||||||
*/
|
*/
|
||||||
export async function deleteAutosaveSession(userId: number): Promise<void> {
|
export async function deleteAutosaveSession(userId: number): Promise<void> {
|
||||||
const response = await fetch(`${BASE_URL}/autosave/${userId}`, {
|
const response = await apiFetch(`${BASE_URL}/autosave/${userId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
// 404 is ok - means no autosave existed
|
// 404 is ok - means no autosave existed
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
export interface ImageChanges {
|
export interface ImageChanges {
|
||||||
order: (number | string)[];
|
order: (number | string)[];
|
||||||
hidden: number[];
|
hidden: number[];
|
||||||
@@ -64,7 +65,7 @@ export async function submitProductEdit({
|
|||||||
|
|
||||||
let response: Response;
|
let response: Response;
|
||||||
try {
|
try {
|
||||||
response = await fetch(targetUrl, fetchOptions);
|
response = await apiFetch(targetUrl, fetchOptions);
|
||||||
} catch (networkError) {
|
} catch (networkError) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
networkError instanceof Error ? networkError.message : "Network request failed"
|
networkError instanceof Error ? networkError.message : "Network request failed"
|
||||||
@@ -134,7 +135,7 @@ export async function submitTaxonomySet({
|
|||||||
|
|
||||||
let response: Response;
|
let response: Response;
|
||||||
try {
|
try {
|
||||||
response = await fetch(targetUrl, fetchOptions);
|
response = await apiFetch(targetUrl, fetchOptions);
|
||||||
} catch (networkError) {
|
} catch (networkError) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
networkError instanceof Error ? networkError.message : "Network request failed"
|
networkError instanceof Error ? networkError.message : "Network request failed"
|
||||||
@@ -199,7 +200,7 @@ export async function submitImageChanges({
|
|||||||
|
|
||||||
let response: Response;
|
let response: Response;
|
||||||
try {
|
try {
|
||||||
response = await fetch(targetUrl, fetchOptions);
|
response = await apiFetch(targetUrl, fetchOptions);
|
||||||
} catch (networkError) {
|
} catch (networkError) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
networkError instanceof Error ? networkError.message : "Network request failed"
|
networkError instanceof Error ? networkError.message : "Network request failed"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { apiFetch } from '@/utils/api';
|
||||||
/**
|
/**
|
||||||
* Product Editor Audit Log API Service
|
* Product Editor Audit Log API Service
|
||||||
*
|
*
|
||||||
@@ -27,7 +28,7 @@ export interface ProductEditorAuditLogEntry {
|
|||||||
*/
|
*/
|
||||||
export async function createProductEditorAuditLog(entry: ProductEditorAuditLogEntry): Promise<void> {
|
export async function createProductEditorAuditLog(entry: ProductEditorAuditLogEntry): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await fetch(BASE_URL, {
|
await apiFetch(BASE_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(entry),
|
body: JSON.stringify(entry),
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Centralized fetch wrapper that attaches the auth token from localStorage
|
||||||
|
// and bounces the user to /login on a 401.
|
||||||
|
//
|
||||||
|
// Drop-in for `fetch`: same call signature, same return shape. The only
|
||||||
|
// differences from raw fetch:
|
||||||
|
// 1. If a token exists in localStorage and the caller hasn't already set an
|
||||||
|
// Authorization header, we attach `Authorization: Bearer ${token}`.
|
||||||
|
// 2. If the response is 401 *and* we had a token to begin with, we fire an
|
||||||
|
// `auth:logout` window event so AuthContext can clear state and redirect
|
||||||
|
// to /login. (No token + 401 just returns normally — login pages need
|
||||||
|
// that path.)
|
||||||
|
//
|
||||||
|
// We intentionally do NOT set Content-Type. FormData uploads rely on fetch
|
||||||
|
// auto-generating the multipart boundary, and JSON callers set their own
|
||||||
|
// header. This wrapper only touches Authorization.
|
||||||
|
|
||||||
|
export async function apiFetch(
|
||||||
|
input: RequestInfo | URL,
|
||||||
|
init: RequestInit = {},
|
||||||
|
): Promise<Response> {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
const headers = new Headers(init.headers);
|
||||||
|
|
||||||
|
if (token && !headers.has('Authorization')) {
|
||||||
|
headers.set('Authorization', `Bearer ${token}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(input, { ...init, headers });
|
||||||
|
|
||||||
|
if (res.status === 401 && token) {
|
||||||
|
window.dispatchEvent(new Event('auth:logout'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// Centralized axios instance that mirrors apiFetch's behavior: it injects the
|
||||||
|
// auth token from localStorage into every outbound request and dispatches an
|
||||||
|
// `auth:logout` window event on 401 responses (so AuthContext clears state).
|
||||||
|
//
|
||||||
|
// Drop-in for the default `axios` export: use `apiClient.get(...)` etc. in
|
||||||
|
// place of `axios.get(...)`. The interceptors handle the auth plumbing.
|
||||||
|
|
||||||
|
export const apiClient = axios.create();
|
||||||
|
|
||||||
|
apiClient.interceptors.request.use((config) => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token && !config.headers.has('Authorization')) {
|
||||||
|
config.headers.set('Authorization', `Bearer ${token}`);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
apiClient.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
if (error?.response?.status === 401 && localStorage.getItem('token')) {
|
||||||
|
window.dispatchEvent(new Event('auth:logout'));
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default apiClient;
|
||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user