forked from cardosofelipe/fast-next-template
Add demo mode support with MSW integration and documentation
- Integrated Mock Service Worker (MSW) for frontend-only demo mode, allowing API call interception without requiring a backend. - Added `DemoModeBanner` component to indicate active demo mode and display demo credentials. - Enhanced configuration with `DEMO_MODE` flag and demo credentials for user and admin access. - Updated ESLint configuration to exclude MSW-related files from linting and coverage. - Created comprehensive `DEMO_MODE.md` documentation for setup and usage guidelines, including deployment instructions and troubleshooting. - Updated package dependencies to include MSW and related libraries.
This commit is contained in:
66
frontend/src/components/demo/DemoModeBanner.tsx
Normal file
66
frontend/src/components/demo/DemoModeBanner.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Demo Mode Indicator
|
||||
*
|
||||
* Subtle floating badge to indicate demo mode is active
|
||||
* Non-intrusive, doesn't cause layout shift
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import config from '@/config/app.config';
|
||||
import { Sparkles } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
|
||||
export function DemoModeBanner() {
|
||||
// Only show in demo mode
|
||||
if (!config.demo.enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
className="fixed bottom-4 right-4 z-50 inline-flex items-center gap-1.5 rounded-full bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground shadow-lg transition-all hover:scale-105 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||
aria-label="Demo mode active"
|
||||
>
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
<span>Demo Mode</span>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80" side="top" align="end">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-medium leading-none">Demo Mode Active</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
All API calls are mocked. No backend required.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs font-medium text-muted-foreground">Demo Credentials:</p>
|
||||
<div className="space-y-1.5">
|
||||
<code className="block rounded bg-muted px-2 py-1.5 text-xs font-mono">
|
||||
<span className="text-muted-foreground">user:</span>{' '}
|
||||
<span className="font-semibold">{config.demo.credentials.user.email}</span>
|
||||
<span className="text-muted-foreground mx-1">/</span>
|
||||
<span className="font-semibold">{config.demo.credentials.user.password}</span>
|
||||
</code>
|
||||
<code className="block rounded bg-muted px-2 py-1.5 text-xs font-mono">
|
||||
<span className="text-muted-foreground">admin:</span>{' '}
|
||||
<span className="font-semibold">{config.demo.credentials.admin.email}</span>
|
||||
<span className="text-muted-foreground mx-1">/</span>
|
||||
<span className="font-semibold">{config.demo.credentials.admin.password}</span>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
5
frontend/src/components/demo/index.ts
Normal file
5
frontend/src/components/demo/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Demo components exports
|
||||
*/
|
||||
|
||||
export { DemoModeBanner } from './DemoModeBanner';
|
||||
83
frontend/src/components/providers/MSWProvider.tsx
Normal file
83
frontend/src/components/providers/MSWProvider.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* MSW Provider Component
|
||||
*
|
||||
* Initializes Mock Service Worker for demo mode
|
||||
* This component handles MSW setup in a Next.js-compatible way
|
||||
*
|
||||
* IMPORTANT: This is a client component that runs in the browser only
|
||||
* SAFE: Will not interfere with tests or development mode
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
/**
|
||||
* MSW initialization promise (cached)
|
||||
* Ensures MSW is only initialized once
|
||||
*/
|
||||
let mswInitPromise: Promise<void> | null = null;
|
||||
|
||||
function initMSW(): Promise<void> {
|
||||
// Return cached promise if already initialized
|
||||
if (mswInitPromise) {
|
||||
return mswInitPromise;
|
||||
}
|
||||
|
||||
// Check if MSW should start
|
||||
const shouldStart =
|
||||
typeof window !== 'undefined' &&
|
||||
process.env.NODE_ENV !== 'test' &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
!(window as any).__PLAYWRIGHT_TEST__ &&
|
||||
process.env.NEXT_PUBLIC_DEMO_MODE === 'true';
|
||||
|
||||
if (!shouldStart) {
|
||||
// Return resolved promise, no-op
|
||||
mswInitPromise = Promise.resolve();
|
||||
return mswInitPromise;
|
||||
}
|
||||
|
||||
// Initialize MSW (lazy import to avoid loading in non-demo mode)
|
||||
mswInitPromise = import('@/mocks')
|
||||
.then(({ initMocks }) => initMocks())
|
||||
.catch((error) => {
|
||||
console.error('[MSW] Failed to initialize:', error);
|
||||
// Reset promise so it can be retried
|
||||
mswInitPromise = null;
|
||||
throw error;
|
||||
});
|
||||
|
||||
return mswInitPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSW Provider Component
|
||||
*
|
||||
* Wraps children and ensures MSW is initialized before rendering
|
||||
* Uses React 19's `use()` hook for suspense-compatible async initialization
|
||||
*/
|
||||
export function MSWProvider({ children }: { children: React.ReactNode }) {
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize MSW on mount
|
||||
initMSW()
|
||||
.then(() => {
|
||||
setIsReady(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[MSW] Initialization failed:', error);
|
||||
// Still render children even if MSW fails (graceful degradation)
|
||||
setIsReady(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Wait for MSW to be ready before rendering children
|
||||
// This prevents race conditions where API calls happen before MSW is ready
|
||||
if (!isReady) {
|
||||
return null; // or a loading spinner if you prefer
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
Reference in New Issue
Block a user