Skip to main content
Glama
northernvariables

FedMCP - Federal Parliamentary Information

MPModal.tsx8.85 kB
/** * MP Modal Component * * Displays detailed MP information in a modal dialog * - Large profile photo * - Full biographical info * - Quick stats (bills, votes, expenses) * - Link to full profile page */ 'use client'; import React from 'react'; import { X, ExternalLink, MapPin, Users, FileText, DollarSign, Phone, Mail, Play, Clock, Eye } from 'lucide-react'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { motion, AnimatePresence } from 'framer-motion'; import { getMPPhotoUrl } from '@/lib/utils/mpPhotoUrl'; interface MP { id: string; name: string; party: string; riding: string; cabinet_position?: string; email?: string; phone?: string; bench_section?: string; } interface MPModalProps { mp: MP | null; isOpen: boolean; onClose: () => void; } // Party colors const PARTY_COLORS: Record<string, string> = { 'Conservative': '#002395', 'Liberal': '#D71920', 'Bloc Québécois': '#33B2CC', 'NDP': '#F37021', 'New Democratic Party': '#F37021', 'Green Party': '#3D9B35', 'Independent': '#666666', }; export function MPModal({ mp, isOpen, onClose }: MPModalProps) { const router = useRouter(); if (!mp) return null; const partyColor = PARTY_COLORS[mp.party] || PARTY_COLORS['Independent']; // Get photo URL from GCS or fallback to ID-based construction const photoUrl = getMPPhotoUrl(mp); const [imageError, setImageError] = React.useState(false); const handleViewProfile = () => { onClose(); router.push(`/mp/${mp.id}`); }; return ( <AnimatePresence> {isOpen && ( <> {/* Backdrop */} <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} onClick={onClose} className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50" /> {/* Modal */} <motion.div initial={{ opacity: 0, scale: 0.95, y: 20 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.95, y: 20 }} className="fixed inset-0 z-50 flex items-center justify-center p-4" onClick={onClose} > <div onClick={(e) => e.stopPropagation()} className="bg-bg-elevated rounded-xl shadow-2xl border border-border-subtle max-w-2xl w-full max-h-[90vh] overflow-y-auto" > {/* Compact Header */} <div className="relative"> {/* Close Button */} <button onClick={onClose} className="absolute top-4 right-4 z-10 p-2 rounded-full bg-bg-elevated/90 hover:bg-bg-hover border border-border-subtle transition-colors" aria-label="Close" > <X className="h-5 w-5 text-text-primary" /> </button> {/* Party Color Banner */} <div className="h-2" style={{ backgroundColor: partyColor }} /> {/* Compact Header with Photo and Info */} <div className="px-6 py-4 flex items-center gap-4"> {/* Small Photo */} {photoUrl && !imageError ? ( <div className="flex-shrink-0"> <Image src={photoUrl} alt={mp.name} width={80} height={80} className="rounded-full border-3 shadow-md" style={{ borderColor: partyColor }} onError={() => setImageError(true)} /> </div> ) : ( <div className="flex-shrink-0 w-20 h-20 rounded-full border-3 shadow-md flex items-center justify-center" style={{ borderColor: partyColor, backgroundColor: `${partyColor}20` }} > <Users className="h-10 w-10 opacity-50" style={{ color: partyColor }} /> </div> )} {/* Name and Info */} <div className="flex-1 min-w-0"> <h2 className="text-2xl font-bold text-text-primary"> {mp.name} </h2> <p className="text-sm text-text-secondary mt-1"> {mp.party} • {mp.riding} </p> {mp.cabinet_position && ( <p className="text-sm text-accent-blue font-semibold mt-1"> ⭐ {mp.cabinet_position} </p> )} {mp.bench_section === 'speaker' && ( <p className="text-sm text-accent-blue font-semibold mt-1"> 🔨 Speaker of the House </p> )} <button onClick={handleViewProfile} className="mt-2 text-sm text-accent-blue hover:text-accent-blue/80 transition-colors font-medium flex items-center gap-1" > View Full Profile <ExternalLink className="h-3 w-3" /> </button> </div> </div> </div> {/* Content */} <div className="px-6 pb-6 space-y-4"> {/* Recent & Popular Videos */} <div className="bg-bg-secondary rounded-lg p-4 border border-border-subtle"> <h3 className="font-semibold text-text-primary mb-4 flex items-center gap-2"> <Play className="h-5 w-5" /> Recent & Popular Videos </h3> <div className="space-y-3"> {/* Mock video items - replace with real data */} {[ { title: 'Question Period - Response on Healthcare', date: '2024-01-15', duration: '4:32', views: '12.5K', }, { title: 'Committee Testimony - Climate Policy', date: '2024-01-10', duration: '12:45', views: '8.2K', }, { title: 'Debate on Bill C-249', date: '2024-01-08', duration: '8:15', views: '15.3K', }, ].map((video, index) => ( <button key={index} className="w-full flex items-start gap-3 p-3 rounded-lg hover:bg-bg-hover transition-colors border border-border-subtle group" > {/* Thumbnail placeholder */} <div className="flex-shrink-0 w-24 h-16 bg-black rounded flex items-center justify-center"> <Play className="h-6 w-6 text-white/70 group-hover:text-white transition-colors" /> </div> {/* Video info */} <div className="flex-1 text-left min-w-0"> <p className="text-sm font-medium text-text-primary group-hover:text-accent-blue transition-colors line-clamp-2"> {video.title} </p> <div className="flex items-center gap-3 mt-1 text-xs text-text-tertiary"> <span>{new Date(video.date).toLocaleDateString('en-CA', { month: 'short', day: 'numeric' })}</span> <span className="flex items-center gap-1"> <Clock className="h-3 w-3" /> {video.duration} </span> <span className="flex items-center gap-1"> <Eye className="h-3 w-3" /> {video.views} </span> </div> </div> </button> ))} </div> <p className="text-xs text-text-tertiary text-center mt-3"> Click any video to watch • View full profile for voting records, expenses, bills, and contact info </p> </div> </div> </div> </motion.div> </> )} </AnimatePresence> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/northernvariables/FedMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server