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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { WorkflowCards } from '@/components/deliveries/WorkflowCards';
import { ValueProps } from '@/components/landing/ValueProps';
import { CallToAction } from '@/components/landing/CallToAction';
import { TrustBar } from '@/components/landing/TrustBar';
import { PricingCards } from '@/components/pricing/PricingCards';
import { KineticExplorer } from '@/components/landing/KineticExplorer';
import { PricingCards } from '@/components/pricing/PricingCards';

const solutionFeatures = [
{
Expand Down
8 changes: 5 additions & 3 deletions components/dashboard/ChartSkeletonLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* - Dark mode support
* - Accessible with appropriate ARIA attributes
*/
const chartBarHeights = [72, 128, 96, 164, 112, 144, 84, 152, 104, 132, 68, 120];

export function ChartSkeletonLoader() {
return (
<div className="space-y-4" role="status" aria-label="Loading chart data">
Expand All @@ -30,14 +32,14 @@ export function ChartSkeletonLoader() {
<div className="space-y-2">
{/* SVG chart area placeholder */}
<div className="flex items-end gap-1">
{Array.from({ length: 12 }).map((_, i) => (
{chartBarHeights.map((height, i) => (
<div
key={i}
className="animate-pulse rounded bg-slate-300 dark:bg-slate-600"
style={{
flex: 1,
height: `${Math.random() * 150 + 50}px`,
opacity: 0.5 + Math.random() * 0.5,
height: `${height}px`,
opacity: 0.6 + (i % 3) * 0.1,
}}
/>
))}
Expand Down
94 changes: 37 additions & 57 deletions components/escrow/__tests__/HighValueCargoMultiSigModal.test.tsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,61 @@
import React, { useState } from 'react';
import { render, screen, within } from '@testing-library/react';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { HighValueCargoMultiSigModal } from '@/components/escrow/HighValueCargoMultiSigModal';
import {
useHighValueCargoApproval,
type UseHighValueCargoApprovalResult,
} from '@/hooks/useHighValueCargoApproval';
import type { HighValueCargoApproval } from '@/types/highValueCargo';
highValueCargoService,
HighValueCargoServiceError,
} from '@/services/highValueCargoService';
import {
FIXED_NOW,
SHIPMENT_ID,
awaitingApprovalResponse,
domesticApprovalResponse,
expiredApprovalResponse,
readyApprovalResponse,
submitApprovalResponse,
} from '@/hooks/__tests__/fixtures/highValueCargoApiResponses';

jest.mock('@/hooks/useHighValueCargoApproval', () => ({
...jest.requireActual('@/hooks/useHighValueCargoApproval'),
useHighValueCargoApproval: jest.fn(),
}));
jest.mock('@/services/highValueCargoService', () => {
const actual = jest.requireActual('@/services/highValueCargoService');
return {
...actual,
highValueCargoService: { getApproval: jest.fn(), submitApproval: jest.fn() },
};
});

const mockUseHighValueCargoApproval = useHighValueCargoApproval as jest.MockedFunction<
typeof useHighValueCargoApproval
const mockGetApproval = highValueCargoService.getApproval as jest.MockedFunction<
typeof highValueCargoService.getApproval
>;
const mockSubmitApproval = highValueCargoService.submitApproval as jest.MockedFunction<
typeof highValueCargoService.submitApproval
>;

const REMAINING_MS = (2 * 3600 + 30 * 60 + 15) * 1000;

function hookStateFor(
approval: HighValueCargoApproval | null,
overrides: Partial<UseHighValueCargoApprovalResult> = {},
): UseHighValueCargoApprovalResult {
const approvedCount = approval?.signers.filter((s) => s.status === 'approved').length ?? 0;
const requiredSignatures = approval?.requiredSignatures ?? 0;
const isExpired = approval?.status === 'expired';
const state: UseHighValueCargoApprovalResult = {
approval,
isLoading: false,
error: null,
refetch: jest.fn(),
approvedCount,
requiredSignatures,
allSignaturesCollected: !!approval && approvedCount >= requiredSignatures,
remainingMs: isExpired ? 0 : REMAINING_MS,
isExpired,
acknowledged: false,
setAcknowledged: jest.fn(),
canSubmit: false,
isSubmitting: false,
submitError: null,
submit: jest.fn(),
...overrides,
};
return state;
}

function renderModal(
state: UseHighValueCargoApprovalResult,
props: Partial<React.ComponentProps<typeof HighValueCargoMultiSigModal>> = {},
) {
mockUseHighValueCargoApproval.mockReturnValue(state);
function renderModal(props: Partial<React.ComponentProps<typeof HighValueCargoMultiSigModal>> = {}) {
const onClose = jest.fn();
const onSubmitted = jest.fn();
const queryClient = new QueryClient({
defaultOptions: { queries: { gcTime: 0, retry: false }, mutations: { retry: false } },
});
const utils = render(
<HighValueCargoMultiSigModal
shipmentId={SHIPMENT_ID}
isOpen
onClose={onClose}
onSubmitted={onSubmitted}
{...props}
/>,
<QueryClientProvider client={queryClient}>
<HighValueCargoMultiSigModal
shipmentId={SHIPMENT_ID}
isOpen
onClose={onClose}
onSubmitted={onSubmitted}
{...props}
/>
</QueryClientProvider>,
);
return { ...utils, onClose, onSubmitted, state };
return { ...utils, onClose, onSubmitted };
}

const submitButton = () => screen.getByRole('button', { name: 'Approve release' });
const acknowledgement = () => screen.getByRole('checkbox', { name: /i acknowledge/i });
async function findDialog() {
const dialog = await screen.findByRole('dialog', { name: 'High-value cargo approval' });
await within(dialog).findByText('Shipment value');
return dialog;
}

describe('HighValueCargoMultiSigModal', () => {
beforeEach(() => {
Expand Down
5 changes: 2 additions & 3 deletions components/mobile/MobileFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';
"use client"

import React from 'react';
import Link from 'next/link';
import Link from 'next/link'

// Navigation links data
const footerLinks = [
Expand Down
4 changes: 2 additions & 2 deletions components/shared/ErrorFallbackUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ export function ErrorFallbackUI({
</button>
</div>

{/* Error ID (if available) */}
{/* Error ID */}
<p className={`text-center text-xs ${subtextClass} mt-6`}>
Error ID: {Date.now()}
Error ID: unavailable
</p>
</div>
</div>
Expand Down
9 changes: 1 addition & 8 deletions features/auth/lib/axios.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
import axios from "axios";

const instance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
withCredentials: true,
});

export default instance;
export { default } from '@/lib/httpClient';
3 changes: 1 addition & 2 deletions features/deliveries/components/CancelShipment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface CancelShipmentProps {
*/
export function CancelShipment({ shipmentId }: CancelShipmentProps) {
const [isModalOpen, setIsModalOpen] = useState(false);
const modalRef = useRef<HTMLDivElement>(null);

const {
canCancel,
Expand Down Expand Up @@ -63,8 +64,6 @@ export function CancelShipment({ shipmentId }: CancelShipmentProps) {
setIsModalOpen(false);
};

const modalRef = useRef<HTMLDivElement>(null);

return (
<div className="mt-4">
<button
Expand Down
8 changes: 5 additions & 3 deletions hooks/__tests__/useEscrowPayout.integration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ describe('useEscrowPayout — Integration Tests with React Query and Mocked Web3
],
};

const createWrapper = () => {
return ({ children }: { children: React.ReactNode }) => (
function TestQueryClientProvider({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
}

const createWrapper = () => TestQueryClientProvider;

beforeEach(() => {
jest.clearAllMocks();
Expand Down
20 changes: 11 additions & 9 deletions hooks/useAuditTrail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,18 @@ export function useAuditTrail(
refetchInterval: options?.refetchInterval,
retry: 2,
});
const events = data?.data?.events;
const paginationData = data?.data?.pagination;

/**
* Normalize events for display with icons, colors, and formatted timestamps
*/
const normalizedEvents = useMemo<NormalizedAuditEvent[]>(() => {
if (!data?.data?.events) {
if (!events) {
return [];
}

return data.data.events.map((event) => {
return events.map((event) => {
const { icon, color } = auditService.getEventIcon(event.eventType);
const displayTimestamp = auditService.formatTimestamp(event.timestamp);

Expand All @@ -138,13 +140,13 @@ export function useAuditTrail(
displayTimestamp,
};
});
}, [data?.data?.events]);
}, [events]);

/**
* Extract pagination information from response
*/
const pagination = useMemo(() => {
if (!data?.data?.pagination) {
if (!paginationData) {
return {
nextCursor: undefined,
prevCursor: undefined,
Expand All @@ -154,12 +156,12 @@ export function useAuditTrail(
}

return {
nextCursor: data.data.pagination.nextCursor,
prevCursor: data.data.pagination.prevCursor,
hasMore: data.data.pagination.hasMore,
pageSize: data.data.pagination.pageSize,
nextCursor: paginationData.nextCursor,
prevCursor: paginationData.prevCursor,
hasMore: paginationData.hasMore,
pageSize: paginationData.pageSize,
};
}, [data?.data?.pagination, pageSize]);
}, [paginationData, pageSize]);

/**
* Fetch next page of results
Expand Down
4 changes: 2 additions & 2 deletions hooks/useCallToAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useState, useCallback, useEffect, useRef } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import axios from 'axios';
import { isCancel } from '@/lib/httpClient';
import { z } from 'zod';
import { ctaService } from '@/services/ctaService';
import type { CtaRegistrationResponse } from '@/types/cta';
Expand Down Expand Up @@ -65,7 +65,7 @@ export function useCallToAction(): UseCallToActionReturn {
setResponseMessage(response.message);
form.reset();
} catch (err: unknown) {
if (axios.isCancel(err)) return;
if (isCancel(err)) return;

const message =
err instanceof Error && err.message
Expand Down
4 changes: 2 additions & 2 deletions hooks/useDriverLocationSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ export function useDriverLocationSocket(
const locationStateRef = useRef<LocationState>({
current: { lat: 0, lng: 0 },
target: { lat: 0, lng: 0 },
lastUpdateTime: Date.now(),
lastUpdateTime: 0,
animationFrameId: null,
});

/**
* Interpolates from current to target position linearly over INTERPOLATION_DURATION_MS.
* Called on every animation frame.
*/
const updateInterpolation = useCallback(() => {
const updateInterpolation = useCallback(function updateInterpolation() {
const state = locationStateRef.current;
const now = Date.now();
const elapsed = now - state.lastUpdateTime;
Expand Down
4 changes: 2 additions & 2 deletions hooks/useFleet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import axios from 'axios';
import { isCancel } from '@/lib/httpClient';
import { fleetService } from '@/services/fleetService';
import type { Driver, FleetSummary } from '@/types/fleet';

Expand Down Expand Up @@ -47,7 +47,7 @@ export function useFleet(): UseFleetResult {
})
.catch((err: unknown) => {
if (cancelled) return;
if (axios.isCancel(err)) return;
if (isCancel(err)) return;
const message =
err instanceof Error && err.message
? err.message
Expand Down
5 changes: 3 additions & 2 deletions hooks/useInView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ export function useInView(
): UseInViewResult {
const internalRef = useRef<HTMLDivElement>(null);
const targetRef = ref || internalRef;
const [isInView, setIsInView] = useState(false);
const [isInView, setIsInView] = useState(
() => typeof IntersectionObserver === 'undefined',
);

useEffect(() => {
if (!targetRef.current) return;

// Fallback for browsers without Intersection Observer support
if (typeof IntersectionObserver === 'undefined') {
setIsInView(true);
return;
}

Expand Down
4 changes: 2 additions & 2 deletions hooks/useIndustryHero.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
import axios from 'axios';
import { isCancel } from '@/lib/httpClient';
import { industryService } from '@/services/industryService';
import type {
IndustryHeroContent,
Expand Down Expand Up @@ -41,7 +41,7 @@ export function useIndustryHero(): UseIndustryHeroResult {
setError(null);
})
.catch((err: unknown) => {
if (cancelled || axios.isCancel(err)) return;
if (cancelled || isCancel(err)) return;
setError(
err instanceof Error && err.message
? err.message
Expand Down
4 changes: 2 additions & 2 deletions hooks/useKineticExplorer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
import axios from 'axios';
import { isCancel } from '@/lib/httpClient';
import { kineticExplorerService } from '@/services/kineticExplorerService';
import type { LedgerTransaction, NetworkMetrics } from '@/types/kineticExplorer';

Expand Down Expand Up @@ -40,7 +40,7 @@ export function useKineticExplorer(): UseKineticExplorerResult {
})
.catch((err: unknown) => {
if (cancelled) return;
if (axios.isCancel(err)) return;
if (isCancel(err)) return;
const message =
err instanceof Error && err.message
? err.message
Expand Down
4 changes: 2 additions & 2 deletions hooks/usePricingComparison.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
import axios from 'axios';
import { isCancel } from '@/lib/httpClient';
import { pricingService } from '@/services/pricingService';
import type { PricingComparison } from '@/types/pricing';

Expand Down Expand Up @@ -36,7 +36,7 @@ export function usePricingComparison(): UsePricingComparisonResult {
})
.catch((err: unknown) => {
if (cancelled) return;
if (axios.isCancel(err)) return;
if (isCancel(err)) return;
const message =
err instanceof Error && err.message
? err.message
Expand Down
Loading
Loading