[SPRINT-1] feat: Resolve routing and improve auth session management

SUBTASK-001: Routing fixes
- Add lazy-loaded route for /portfolio/:portfolioId
- Add navigation links from PortfolioDashboard to portfolio detail
- Verify /settings/billing is intentional dual-route (no changes needed)

SUBTASK-002: Auth improvements
- Extend ActiveSession type with device details (deviceType, browser, os, location)
- DeviceCard now uses backend data when available, falls back to userAgent parsing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-02-03 23:49:26 -06:00
parent e639f36a22
commit 295bd5e31e
4 changed files with 50 additions and 13 deletions

View File

@ -41,6 +41,7 @@ const Assistant = lazy(() => import('./modules/assistant/pages/Assistant'));
// Lazy load modules - Portfolio
const PortfolioDashboard = lazy(() => import('./modules/portfolio/pages/PortfolioDashboard'));
const PortfolioDetailPage = lazy(() => import('./modules/portfolio/pages/PortfolioDetailPage'));
const CreatePortfolio = lazy(() => import('./modules/portfolio/pages/CreatePortfolio'));
const CreateGoal = lazy(() => import('./modules/portfolio/pages/CreateGoal'));
const EditAllocations = lazy(() => import('./modules/portfolio/pages/EditAllocations'));
@ -112,6 +113,7 @@ function App() {
<Route path="/portfolio" element={<PortfolioDashboard />} />
<Route path="/portfolio/new" element={<CreatePortfolio />} />
<Route path="/portfolio/goals/new" element={<CreateGoal />} />
<Route path="/portfolio/:portfolioId" element={<PortfolioDetailPage />} />
<Route path="/portfolio/:portfolioId/edit" element={<EditAllocations />} />
{/* Education */}

View File

@ -55,9 +55,21 @@ interface DeviceCardProps {
export function DeviceCard({ session, isRevoking, onRevoke }: DeviceCardProps) {
const [showConfirm, setShowConfirm] = useState(false);
const deviceInfo = authService.parseUserAgent(session.userAgent);
const parsedInfo = authService.parseUserAgent(session.userAgent);
const relativeTime = authService.formatRelativeTime(session.lastActiveAt);
// Use backend-provided device info if available, otherwise use parsed info
const deviceInfo = {
type: (session.deviceType as 'desktop' | 'mobile' | 'tablet' | 'unknown') || parsedInfo.type,
os: session.os || parsedInfo.os,
browser: session.browser || parsedInfo.browser,
};
// Format location from backend data
const location = session.city && session.countryCode
? `${session.city}, ${session.countryCode}`
: session.countryCode || null;
// Get device icon based on type
const DeviceIcon = {
desktop: DesktopIcon,
@ -117,7 +129,7 @@ export function DeviceCard({ session, isRevoking, onRevoke }: DeviceCardProps) {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
{session.ipAddress || 'Unknown IP'}
{location ? `${location} (${session.ipAddress || 'Unknown IP'})` : session.ipAddress || 'Unknown IP'}
</span>
</p>
<p className="flex items-center gap-2">

View File

@ -195,10 +195,10 @@ export default function PortfolioDashboard() {
{portfolios.length > 1 && (
<div className="flex gap-2 mb-6 overflow-x-auto pb-2">
{portfolios.map((p) => (
<div key={p.id} className="flex items-center gap-1">
<button
key={p.id}
onClick={() => selectPortfolio(p)}
className={`px-4 py-2 rounded-lg whitespace-nowrap transition-colors ${
className={`px-4 py-2 rounded-l-lg whitespace-nowrap transition-colors ${
selectedPortfolio?.id === p.id
? 'bg-blue-600 text-white'
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
@ -206,6 +206,18 @@ export default function PortfolioDashboard() {
>
{p.name}
</button>
<Link
to={`/portfolio/${p.id}`}
className={`px-2 py-2 rounded-r-lg whitespace-nowrap transition-colors ${
selectedPortfolio?.id === p.id
? 'bg-blue-700 text-white hover:bg-blue-800'
: 'bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-500'
}`}
title="Ver detalle"
>
<ArrowTrendingUpIcon className="w-4 h-4" />
</Link>
</div>
))}
</div>
)}
@ -388,6 +400,12 @@ export default function PortfolioDashboard() {
</span>
</div>
</div>
<Link
to={`/portfolio/${selectedPortfolio.id}`}
className="mt-4 w-full block text-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors"
>
Ver Detalle Completo
</Link>
</div>
</div>
</div>

View File

@ -14,6 +14,11 @@ export interface ActiveSession {
id: string;
userAgent: string;
ipAddress: string;
deviceType?: string;
browser?: string;
os?: string;
countryCode?: string;
city?: string;
createdAt: string;
lastActiveAt: string;
isCurrent: boolean;