2026-01-15 17:01:22 +00:00
|
|
|
/**
|
|
|
|
|
* Offline Queue Manager Component
|
|
|
|
|
* Action 2.5.1.4: Add UI for offline queue management
|
|
|
|
|
*
|
|
|
|
|
* Displays queued requests and allows users to view details, remove requests, or clear the queue
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
|
import { offlineQueue, type QueuedRequest } from '@/services/offlineQueue';
|
|
|
|
|
import { Dialog } from '@/components/ui/dialog';
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
import { Trash2, X, Clock, AlertCircle, CheckCircle2 } from 'lucide-react';
|
|
|
|
|
import { cn } from '@/lib/utils';
|
2026-01-16 11:15:53 +00:00
|
|
|
import { logger } from '@/utils/logger';
|
2026-01-15 17:01:22 +00:00
|
|
|
|
|
|
|
|
interface OfflineQueueManagerProps {
|
|
|
|
|
open: boolean;
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Format timestamp to readable date/time
|
|
|
|
|
*/
|
|
|
|
|
function formatTimestamp(timestamp: number): string {
|
|
|
|
|
const date = new Date(timestamp);
|
|
|
|
|
return date.toLocaleString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Format request method and URL for display
|
|
|
|
|
*/
|
|
|
|
|
function formatRequest(request: QueuedRequest): string {
|
|
|
|
|
const method = request.config.method?.toUpperCase() || 'UNKNOWN';
|
|
|
|
|
const url = request.config.url || 'Unknown URL';
|
|
|
|
|
return `${method} ${url}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get priority badge color
|
|
|
|
|
*/
|
|
|
|
|
function getPriorityColor(priority: QueuedRequest['priority']): string {
|
|
|
|
|
switch (priority) {
|
|
|
|
|
case 'high':
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
return 'bg-destructive/20 text-destructive border-destructive/30';
|
2026-01-15 17:01:22 +00:00
|
|
|
case 'normal':
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
return 'bg-muted/20 text-muted-foreground border-border/30';
|
2026-01-15 17:01:22 +00:00
|
|
|
case 'low':
|
2026-02-08 23:13:27 +00:00
|
|
|
return 'bg-muted/30 text-muted-foreground border-border/50';
|
2026-01-15 17:01:22 +00:00
|
|
|
default:
|
2026-02-08 23:13:27 +00:00
|
|
|
return 'bg-muted/30 text-muted-foreground border-border/50';
|
2026-01-15 17:01:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function OfflineQueueManager({
|
|
|
|
|
open,
|
|
|
|
|
onClose,
|
|
|
|
|
}: OfflineQueueManagerProps) {
|
|
|
|
|
const [queue, setQueue] = useState<QueuedRequest[]>([]);
|
|
|
|
|
const [isRemoving, setIsRemoving] = useState<string | null>(null);
|
|
|
|
|
const [isClearing, setIsClearing] = useState(false);
|
|
|
|
|
|
|
|
|
|
// Update queue when dialog opens or periodically
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!open) return;
|
|
|
|
|
|
|
|
|
|
const updateQueue = () => {
|
|
|
|
|
setQueue(offlineQueue.getQueue());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Update immediately
|
|
|
|
|
updateQueue();
|
|
|
|
|
|
|
|
|
|
// Update every second while dialog is open
|
|
|
|
|
const interval = setInterval(updateQueue, 1000);
|
|
|
|
|
|
|
|
|
|
return () => clearInterval(interval);
|
|
|
|
|
}, [open]);
|
|
|
|
|
|
|
|
|
|
const handleRemoveRequest = async (requestId: string) => {
|
|
|
|
|
setIsRemoving(requestId);
|
|
|
|
|
try {
|
|
|
|
|
await offlineQueue.removeRequest(requestId);
|
|
|
|
|
setQueue(offlineQueue.getQueue());
|
|
|
|
|
} catch (error) {
|
2026-01-16 11:15:20 +00:00
|
|
|
logger.error('Failed to remove request', {
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
|
|
|
requestId,
|
|
|
|
|
});
|
2026-01-15 17:01:22 +00:00
|
|
|
} finally {
|
|
|
|
|
setIsRemoving(null);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleClearQueue = async () => {
|
|
|
|
|
setIsClearing(true);
|
|
|
|
|
try {
|
|
|
|
|
await offlineQueue.clearQueue();
|
|
|
|
|
setQueue([]);
|
|
|
|
|
onClose();
|
|
|
|
|
} catch (error) {
|
2026-01-16 11:15:20 +00:00
|
|
|
logger.error('Failed to clear queue', {
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
|
|
|
});
|
2026-01-15 17:01:22 +00:00
|
|
|
} finally {
|
|
|
|
|
setIsClearing(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Dialog
|
|
|
|
|
open={open}
|
|
|
|
|
onClose={onClose}
|
|
|
|
|
title="Offline Queue Manager"
|
|
|
|
|
size="lg"
|
|
|
|
|
variant="info"
|
|
|
|
|
>
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
{/* Queue Summary */}
|
fix: stabilize builds, tests, and lint across all stacks
Complete stabilization pass bringing all 3 stacks to green:
Frontend (apps/web/):
- Fix TypeScript nullability in useSeason.ts, useTimeOfDay.ts hooks
- Disable no-undef in ESLint config (TypeScript handles it; JSX misidentified)
- Rename 306 story imports from @storybook/react to @storybook/react-vite
- Fix conditional hook call in useMediaQuery.ts useIsTablet
- Move useQuery to top of LoginPage.tsx component
- Remove useless try/catch in GearFormModal.tsx
- Fix stale closure in ResetPasswordPage.tsx handleChange
- Make Storybook decorators (withRouter, withQueryClient, withToast, withAudio)
no-ops since global StorybookDecorator already provides these — prevents
nested Router / duplicate provider crashes in vitest-browser
- Fix nested MemoryRouter in 3 page stories (TrackDetail, PlaylistDetail, UserProfile)
- Update i18n initialization in test setup (await init before changeLanguage)
- Update ~30 test assertions from English to French to match i18n translations
- Update test assertions to match SUMI V3 design changes (shadow vs border)
- Fix remaining story type errors (PlayerError, PlaylistBatchActions,
TrackFilters, VirtualizedChatMessages)
Backend (veza-backend-api/):
- Fix response_test.go RespondWithAppError signature (2 args, not 3)
- Fix TestErrorContractAuthEndpoints expected error codes
(ErrCodeUnauthorized vs ErrCodeInvalidCredentials)
- Fix TestTrackHandler_GetTrackLikes_Success missing auth middleware setup
- Fix TestPlaybackAnalyticsService_GetTrackStats k-anonymity threshold
(needs 5 unique users, not 1)
- Replace NOW() PostgreSQL function with time.Now() parameter in marketplace
service for SQLite test compatibility
- Add missing AutoMigrate entries in marketplace_test.go
(ProductImage, ProductPreview, ProductLicense, ProductReview)
Results:
- Frontend TypeCheck: 617 errors -> 0 errors
- Frontend ESLint: 349 errors -> 0 errors
- Frontend Vitest: 196 failing tests -> 1 skipped (3396/3397 passing)
- Backend go vet: 1 error -> 0 errors
- Backend tests: 5 failing -> all 13 packages passing
- Rust: 150/150 tests passing (unchanged)
- Storybook audit: 0 errors across 1244 stories
Triage report: docs/TRIAGE_REPORT.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:48:07 +00:00
|
|
|
<div className="flex items-center justify-between p-4 bg-card/50 rounded-lg shadow-[0_0_8px_rgba(26,26,30,0.05)]">
|
2026-01-15 17:01:22 +00:00
|
|
|
<div className="flex items-center gap-2">
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
<Clock className="w-5 h-5 text-muted-foreground" />
|
2026-02-08 23:13:27 +00:00
|
|
|
<span className="text-sm text-muted-foreground">
|
2026-01-15 17:01:22 +00:00
|
|
|
{queue.length === 0
|
|
|
|
|
? 'No queued requests'
|
|
|
|
|
: `${queue.length} ${queue.length === 1 ? 'request' : 'requests'} queued`}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
{queue.length > 0 && (
|
|
|
|
|
<Button
|
|
|
|
|
variant="destructive"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={handleClearQueue}
|
|
|
|
|
disabled={isClearing}
|
|
|
|
|
>
|
|
|
|
|
<Trash2 className="w-4 h-4 mr-2" />
|
|
|
|
|
Clear All
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Queue List */}
|
|
|
|
|
{queue.length === 0 ? (
|
2026-02-08 23:13:27 +00:00
|
|
|
<div className="text-center py-8 text-muted-foreground">
|
ui(tokens): migrate kodo-cyan to primary (51 files, 88 instances)
Replace legacy text-kodo-cyan/border-kodo-cyan/bg-kodo-cyan with semantic
text-primary/border-primary/bg-primary across 51 components.
The brand primary color now uses the design system token, enabling proper
theme adaptation. Covers UI primitives, search, dashboard, chat, playlists,
settings, social, marketplace, and auth components.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 23:19:12 +00:00
|
|
|
<CheckCircle2 className="w-12 h-12 mx-auto mb-4 text-primary/50" />
|
2026-01-15 17:01:22 +00:00
|
|
|
<p className="text-sm">All requests have been processed</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
2026-02-08 21:47:41 +00:00
|
|
|
<div className="space-y-2 max-h-layout-list overflow-y-auto custom-scrollbar">
|
2026-01-15 17:01:22 +00:00
|
|
|
{queue.map((request) => (
|
|
|
|
|
<div
|
|
|
|
|
key={request.id}
|
fix: stabilize builds, tests, and lint across all stacks
Complete stabilization pass bringing all 3 stacks to green:
Frontend (apps/web/):
- Fix TypeScript nullability in useSeason.ts, useTimeOfDay.ts hooks
- Disable no-undef in ESLint config (TypeScript handles it; JSX misidentified)
- Rename 306 story imports from @storybook/react to @storybook/react-vite
- Fix conditional hook call in useMediaQuery.ts useIsTablet
- Move useQuery to top of LoginPage.tsx component
- Remove useless try/catch in GearFormModal.tsx
- Fix stale closure in ResetPasswordPage.tsx handleChange
- Make Storybook decorators (withRouter, withQueryClient, withToast, withAudio)
no-ops since global StorybookDecorator already provides these — prevents
nested Router / duplicate provider crashes in vitest-browser
- Fix nested MemoryRouter in 3 page stories (TrackDetail, PlaylistDetail, UserProfile)
- Update i18n initialization in test setup (await init before changeLanguage)
- Update ~30 test assertions from English to French to match i18n translations
- Update test assertions to match SUMI V3 design changes (shadow vs border)
- Fix remaining story type errors (PlayerError, PlaylistBatchActions,
TrackFilters, VirtualizedChatMessages)
Backend (veza-backend-api/):
- Fix response_test.go RespondWithAppError signature (2 args, not 3)
- Fix TestErrorContractAuthEndpoints expected error codes
(ErrCodeUnauthorized vs ErrCodeInvalidCredentials)
- Fix TestTrackHandler_GetTrackLikes_Success missing auth middleware setup
- Fix TestPlaybackAnalyticsService_GetTrackStats k-anonymity threshold
(needs 5 unique users, not 1)
- Replace NOW() PostgreSQL function with time.Now() parameter in marketplace
service for SQLite test compatibility
- Add missing AutoMigrate entries in marketplace_test.go
(ProductImage, ProductPreview, ProductLicense, ProductReview)
Results:
- Frontend TypeCheck: 617 errors -> 0 errors
- Frontend ESLint: 349 errors -> 0 errors
- Frontend Vitest: 196 failing tests -> 1 skipped (3396/3397 passing)
- Backend go vet: 1 error -> 0 errors
- Backend tests: 5 failing -> all 13 packages passing
- Rust: 150/150 tests passing (unchanged)
- Storybook audit: 0 errors across 1244 stories
Triage report: docs/TRIAGE_REPORT.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:48:07 +00:00
|
|
|
className="p-4 bg-card/30 rounded-lg shadow-[0_0_8px_rgba(26,26,30,0.05)] hover:shadow-[0_0_12px_rgba(26,26,30,0.08)] transition-shadow"
|
2026-01-15 17:01:22 +00:00
|
|
|
>
|
|
|
|
|
<div className="flex items-start justify-between gap-4">
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
{/* Request Method and URL */}
|
|
|
|
|
<div className="flex items-center gap-2 mb-2">
|
2026-02-12 01:09:29 +00:00
|
|
|
<span className="font-mono text-sm font-semibold text-foreground truncate">
|
2026-01-15 17:01:22 +00:00
|
|
|
{formatRequest(request)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Metadata */}
|
2026-02-08 23:13:27 +00:00
|
|
|
<div className="flex items-center gap-4 flex-wrap text-xs text-muted-foreground">
|
2026-01-15 17:01:22 +00:00
|
|
|
{/* Priority Badge */}
|
|
|
|
|
<span
|
|
|
|
|
className={cn(
|
|
|
|
|
'px-2 py-0.5 rounded border',
|
|
|
|
|
getPriorityColor(request.priority),
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{request.priority}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{/* Timestamp */}
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
<Clock className="w-3 h-3" />
|
|
|
|
|
{formatTimestamp(request.timestamp)}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{/* Retry Count */}
|
|
|
|
|
{request.retryCount > 0 && (
|
2026-02-08 23:14:40 +00:00
|
|
|
<span className="flex items-center gap-1 text-destructive">
|
2026-01-15 17:01:22 +00:00
|
|
|
<AlertCircle className="w-3 h-3" />
|
|
|
|
|
{request.retryCount} retry
|
|
|
|
|
{request.retryCount > 1 ? 'ies' : ''}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Remove Button */}
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
onClick={() => handleRemoveRequest(request.id)}
|
|
|
|
|
disabled={isRemoving === request.id}
|
|
|
|
|
className="shrink-0"
|
|
|
|
|
>
|
|
|
|
|
{isRemoving === request.id ? (
|
|
|
|
|
<Clock className="w-4 h-4 animate-spin" />
|
|
|
|
|
) : (
|
|
|
|
|
<X className="w-4 h-4" />
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Info Message */}
|
|
|
|
|
{queue.length > 0 && (
|
fix: stabilize builds, tests, and lint across all stacks
Complete stabilization pass bringing all 3 stacks to green:
Frontend (apps/web/):
- Fix TypeScript nullability in useSeason.ts, useTimeOfDay.ts hooks
- Disable no-undef in ESLint config (TypeScript handles it; JSX misidentified)
- Rename 306 story imports from @storybook/react to @storybook/react-vite
- Fix conditional hook call in useMediaQuery.ts useIsTablet
- Move useQuery to top of LoginPage.tsx component
- Remove useless try/catch in GearFormModal.tsx
- Fix stale closure in ResetPasswordPage.tsx handleChange
- Make Storybook decorators (withRouter, withQueryClient, withToast, withAudio)
no-ops since global StorybookDecorator already provides these — prevents
nested Router / duplicate provider crashes in vitest-browser
- Fix nested MemoryRouter in 3 page stories (TrackDetail, PlaylistDetail, UserProfile)
- Update i18n initialization in test setup (await init before changeLanguage)
- Update ~30 test assertions from English to French to match i18n translations
- Update test assertions to match SUMI V3 design changes (shadow vs border)
- Fix remaining story type errors (PlayerError, PlaylistBatchActions,
TrackFilters, VirtualizedChatMessages)
Backend (veza-backend-api/):
- Fix response_test.go RespondWithAppError signature (2 args, not 3)
- Fix TestErrorContractAuthEndpoints expected error codes
(ErrCodeUnauthorized vs ErrCodeInvalidCredentials)
- Fix TestTrackHandler_GetTrackLikes_Success missing auth middleware setup
- Fix TestPlaybackAnalyticsService_GetTrackStats k-anonymity threshold
(needs 5 unique users, not 1)
- Replace NOW() PostgreSQL function with time.Now() parameter in marketplace
service for SQLite test compatibility
- Add missing AutoMigrate entries in marketplace_test.go
(ProductImage, ProductPreview, ProductLicense, ProductReview)
Results:
- Frontend TypeCheck: 617 errors -> 0 errors
- Frontend ESLint: 349 errors -> 0 errors
- Frontend Vitest: 196 failing tests -> 1 skipped (3396/3397 passing)
- Backend go vet: 1 error -> 0 errors
- Backend tests: 5 failing -> all 13 packages passing
- Rust: 150/150 tests passing (unchanged)
- Storybook audit: 0 errors across 1244 stories
Triage report: docs/TRIAGE_REPORT.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:48:07 +00:00
|
|
|
<div className="p-4 bg-muted/10 shadow-[0_0_8px_rgba(26,26,30,0.05)] rounded-lg text-xs text-muted-foreground">
|
2026-01-15 17:01:22 +00:00
|
|
|
<p>
|
|
|
|
|
Queued requests will be automatically processed when you're back
|
|
|
|
|
online. You can remove individual requests or clear the entire
|
|
|
|
|
queue.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
}
|