Phase 1 (Foundation): - Project restructure (presenton-main → backend/ + frontend/) - Database schema (8 new models, Alembic config, seed script) - Auth (Azure AD SSO + dev bypass, JWT sessions, AuthMiddleware) - RBAC (access_service, rbac_middleware, admin routers) - Audit logging (fire-and-forget, AuditMiddleware, admin router) - i18n (react-i18next with 5 namespace files) Phase 2 (Admin Panel & Client Management): - Admin panel shell (sidebar layout, role guard, 12 pages) - Redux admin slice with 18 async thunks - User management (role changes, deactivation) - Client management (CRUD, brand config, team management) - Brand config editor (colors, fonts, logos, voice rules) - Master deck upload & parser (PPTX → HTML → React pipeline) - Audit log viewer with filters and CSV/JSON export Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
80 lines
No EOL
2.8 KiB
TypeScript
80 lines
No EOL
2.8 KiB
TypeScript
'use client'
|
|
import React, { useEffect, useState, useRef } from 'react';
|
|
|
|
interface ProgressBarProps {
|
|
duration: number;
|
|
onComplete?: () => void;
|
|
}
|
|
|
|
export const ProgressBar = ({ duration, onComplete }: ProgressBarProps) => {
|
|
const [progress, setProgress] = useState(0);
|
|
const progressInterval = useRef<NodeJS.Timeout | null>(null);
|
|
const startTime = useRef<number>(Date.now());
|
|
|
|
useEffect(() => {
|
|
const updateProgress = () => {
|
|
const currentTime = Date.now();
|
|
const elapsedTime = currentTime - startTime.current;
|
|
const calculatedProgress = (elapsedTime / (duration * 1000)) * 100;
|
|
|
|
if (calculatedProgress >= 95) {
|
|
setProgress(95);
|
|
if (progressInterval.current) {
|
|
clearInterval(progressInterval.current);
|
|
}
|
|
onComplete?.();
|
|
return;
|
|
}
|
|
|
|
// Slow down progress after 90%
|
|
if (calculatedProgress > 90) {
|
|
const remainingProgress = Math.min(99 - progress, 0.1);
|
|
setProgress(prev => prev + remainingProgress);
|
|
} else {
|
|
setProgress(Math.min(calculatedProgress, 90));
|
|
}
|
|
};
|
|
|
|
progressInterval.current = setInterval(updateProgress, 50);
|
|
|
|
return () => {
|
|
if (progressInterval.current) {
|
|
clearInterval(progressInterval.current);
|
|
}
|
|
};
|
|
}, [duration, onComplete]);
|
|
|
|
return (
|
|
<div className="w-full space-y-2">
|
|
<div className="flex justify-end items-center text-white/80 text-sm">
|
|
{/* <span>Processing...</span> */}
|
|
<span className='font-inter text-end font-medium text-xs'>{Math.round(progress)}%</span>
|
|
</div>
|
|
<div className="w-full bg-white rounded-full h-2 overflow-hidden">
|
|
<div
|
|
className="h-full bg-gradient-to-r from-[#9034EA] via-[#5146E5] to-[#9034EA] rounded-full animate-gradient transition-all duration-300 ease-out"
|
|
style={{
|
|
width: `${progress}%`,
|
|
backgroundSize: '200% 100%',
|
|
}}
|
|
/>
|
|
</div>
|
|
<style jsx>{`
|
|
@keyframes gradient {
|
|
0% {
|
|
background-position: 0% 50%;
|
|
}
|
|
50% {
|
|
background-position: 100% 50%;
|
|
}
|
|
100% {
|
|
background-position: 0% 50%;
|
|
}
|
|
}
|
|
.animate-gradient {
|
|
animation: gradient 2s linear infinite;
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
};
|