Add DEMO feature to the project
This commit is contained in:
146
frontend/src/components/layout/DemoBanner/DemoBanner.tsx
Normal file
146
frontend/src/components/layout/DemoBanner/DemoBanner.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { extendDemoSession, destroyDemoSession } from '../../../api/services/demo';
|
||||
import { apiClient } from '../../../api/client';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export const DemoBanner: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [isDemo, setIsDemo] = useState(false);
|
||||
const [expiresAt, setExpiresAt] = useState<string | null>(null);
|
||||
const [timeRemaining, setTimeRemaining] = useState<string>('');
|
||||
const [canExtend, setCanExtend] = useState(true);
|
||||
const [extending, setExtending] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const demoMode = localStorage.getItem('demo_mode') === 'true';
|
||||
const expires = localStorage.getItem('demo_expires_at');
|
||||
|
||||
setIsDemo(demoMode);
|
||||
setExpiresAt(expires);
|
||||
|
||||
if (demoMode && expires) {
|
||||
const interval = setInterval(() => {
|
||||
const now = new Date().getTime();
|
||||
const expiryTime = new Date(expires).getTime();
|
||||
const diff = expiryTime - now;
|
||||
|
||||
if (diff <= 0) {
|
||||
setTimeRemaining('Sesión expirada');
|
||||
handleExpiration();
|
||||
} else {
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const seconds = Math.floor((diff % 60000) / 1000);
|
||||
setTimeRemaining(`${minutes}:${seconds.toString().padStart(2, '0')}`);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}, [expiresAt]);
|
||||
|
||||
const handleExpiration = () => {
|
||||
localStorage.removeItem('demo_mode');
|
||||
localStorage.removeItem('demo_session_id');
|
||||
localStorage.removeItem('demo_account_type');
|
||||
localStorage.removeItem('demo_expires_at');
|
||||
apiClient.setDemoSessionId(null);
|
||||
navigate('/demo');
|
||||
};
|
||||
|
||||
const handleExtendSession = async () => {
|
||||
const sessionId = apiClient.getDemoSessionId();
|
||||
if (!sessionId) return;
|
||||
|
||||
setExtending(true);
|
||||
try {
|
||||
const updatedSession = await extendDemoSession({ session_id: sessionId });
|
||||
localStorage.setItem('demo_expires_at', updatedSession.expires_at);
|
||||
setExpiresAt(updatedSession.expires_at);
|
||||
|
||||
if (updatedSession.remaining_extensions === 0) {
|
||||
setCanExtend(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error extending session:', error);
|
||||
alert('No se pudo extender la sesión');
|
||||
} finally {
|
||||
setExtending(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEndSession = async () => {
|
||||
const sessionId = apiClient.getDemoSessionId();
|
||||
if (!sessionId) return;
|
||||
|
||||
if (confirm('¿Estás seguro de que quieres terminar la sesión demo?')) {
|
||||
try {
|
||||
await destroyDemoSession({ session_id: sessionId });
|
||||
} catch (error) {
|
||||
console.error('Error destroying session:', error);
|
||||
} finally {
|
||||
handleExpiration();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!isDemo) return null;
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-r from-amber-500 to-orange-500 text-white px-4 py-2 shadow-md">
|
||||
<div className="max-w-7xl mx-auto flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="w-5 h-5 mr-2"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span className="font-medium">Modo Demo</span>
|
||||
</div>
|
||||
|
||||
<div className="hidden sm:flex items-center text-sm">
|
||||
<svg
|
||||
className="w-4 h-4 mr-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Tiempo restante: <span className="font-mono ml-1">{timeRemaining}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{canExtend && (
|
||||
<button
|
||||
onClick={handleExtendSession}
|
||||
disabled={extending}
|
||||
className="px-3 py-1 bg-white/20 hover:bg-white/30 rounded-md text-sm font-medium transition-colors disabled:opacity-50"
|
||||
>
|
||||
{extending ? 'Extendiendo...' : '+30 min'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={handleEndSession}
|
||||
className="px-3 py-1 bg-white/20 hover:bg-white/30 rounded-md text-sm font-medium transition-colors"
|
||||
>
|
||||
Terminar Demo
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DemoBanner;
|
||||
1
frontend/src/components/layout/DemoBanner/index.ts
Normal file
1
frontend/src/components/layout/DemoBanner/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { DemoBanner, default } from './DemoBanner';
|
||||
Reference in New Issue
Block a user