/**
* MPSearchSelect Component
*
* Reusable component for searching and selecting an MP.
* Extracted from verify-mp page for use in signup flow.
*/
'use client';
import { useState } from 'react';
import { Search, Loader2, CheckCircle, AlertCircle } from 'lucide-react';
import { AuthButton } from './AuthButton';
export interface MPSearchResult {
parl_mp_id: number;
name: string;
party: string;
riding: string;
photo_url?: string;
}
interface MPSearchSelectProps {
onSelect: (mp: MPSearchResult) => void;
selectedMP: MPSearchResult | null;
disabled?: boolean;
onSkip?: () => void;
showSkip?: boolean;
title?: string;
subtitle?: string;
}
const PARTY_COLORS: Record<string, string> = {
Liberal: 'bg-red-100 text-red-800 border-red-200',
Conservative: 'bg-blue-100 text-blue-800 border-blue-200',
NDP: 'bg-orange-100 text-orange-800 border-orange-200',
'Bloc Québécois': 'bg-sky-100 text-sky-800 border-sky-200',
Green: 'bg-emerald-100 text-emerald-800 border-emerald-200',
Independent: 'bg-gray-100 text-gray-800 border-gray-200',
};
export function MPSearchSelect({
onSelect,
selectedMP,
disabled = false,
onSkip,
showSkip = false,
title = 'Find Your MP Profile',
subtitle = "We couldn't match your email automatically. Please search for your profile.",
}: MPSearchSelectProps) {
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState<MPSearchResult[]>([]);
const [isSearching, setIsSearching] = useState(false);
const [searchError, setSearchError] = useState<string | null>(null);
const handleSearch = async () => {
if (!searchQuery.trim()) return;
setIsSearching(true);
setSearchError(null);
try {
const searchTerm = searchQuery.trim();
// Generate multiple search variations for case-insensitive matching
const titleCase = searchTerm
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
const firstWordCapitalized = searchTerm
.split(' ')
.map((word, i) =>
i === 0
? word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
: word.toLowerCase()
)
.join(' ');
const lowerCase = searchTerm.toLowerCase();
const graphqlEndpoint =
process.env.NEXT_PUBLIC_GRAPHQL_URL || 'http://localhost:4000/graphql';
const apiKey = process.env.NEXT_PUBLIC_GRAPHQL_API_KEY;
const response = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(apiKey ? { 'X-API-Key': apiKey } : {}),
},
body: JSON.stringify({
query: `
query SearchMPs($s1: String!, $s2: String!, $s3: String!, $s4: String!) {
mps(where: { current: true, OR: [
{ name_CONTAINS: $s1 },
{ name_CONTAINS: $s2 },
{ name_CONTAINS: $s3 },
{ name_CONTAINS: $s4 }
] }, options: { limit: 10 }) {
parl_mp_id
name
party
riding
photo_url
}
}
`,
variables: {
s1: searchTerm,
s2: titleCase,
s3: firstWordCapitalized,
s4: lowerCase,
},
}),
});
const json = await response.json();
if (json.errors) {
throw new Error(json.errors[0]?.message || 'Search failed');
}
if (json.data?.mps) {
setSearchResults(json.data.mps);
if (json.data.mps.length === 0) {
setSearchError('No MPs found matching your search. Try a different name.');
}
}
} catch (err) {
console.error('Error searching MPs:', err);
setSearchError('Failed to search MPs. Please try again.');
} finally {
setIsSearching(false);
}
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
handleSearch();
}
};
return (
<div className="bg-amber-50 border-2 border-amber-200 rounded-lg p-6">
<div className="flex items-center gap-2 mb-2">
<AlertCircle className="text-amber-600" size={20} />
<h3 className="font-semibold text-amber-900">{title}</h3>
</div>
<p className="text-amber-800 text-sm mb-4">{subtitle}</p>
<div className="flex gap-2 mb-4">
<input
type="text"
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Enter your name (e.g., Justin Trudeau)"
className="flex-1 px-4 py-2 border border-gray-300 rounded-md bg-white text-gray-900 placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent"
disabled={disabled || isSearching}
/>
<button
type="button"
onClick={handleSearch}
disabled={disabled || isSearching || !searchQuery.trim()}
className="px-4 py-2 bg-amber-600 text-white rounded-md hover:bg-amber-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
{isSearching ? (
<Loader2 className="animate-spin" size={18} />
) : (
<Search size={18} />
)}
Search
</button>
</div>
{searchError && (
<p className="text-red-600 text-sm mb-4">{searchError}</p>
)}
{searchResults.length > 0 && (
<div className="space-y-2 mb-4 max-h-64 overflow-y-auto">
{searchResults.map(mp => {
const isSelected = selectedMP?.parl_mp_id === mp.parl_mp_id;
const partyColorClass =
PARTY_COLORS[mp.party] || PARTY_COLORS.Independent;
return (
<button
key={mp.parl_mp_id}
type="button"
onClick={() => onSelect(mp)}
disabled={disabled}
className={`
w-full p-3 border-2 rounded-lg text-left transition-all
${
isSelected
? 'border-amber-500 bg-amber-100'
: 'border-gray-200 bg-white hover:border-amber-300 hover:bg-amber-50'
}
disabled:opacity-50 disabled:cursor-not-allowed
`}
>
<div className="flex items-center gap-3">
{mp.photo_url ? (
<img
src={mp.photo_url}
alt={mp.name}
className="w-10 h-10 rounded-full object-cover"
/>
) : (
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center">
<span className="text-gray-500">{mp.name.charAt(0)}</span>
</div>
)}
<div className="flex-1 min-w-0">
<div className="font-medium text-gray-900 truncate">
{mp.name}
</div>
<div className="text-sm text-gray-600 truncate">
{mp.riding}
</div>
<span
className={`inline-block mt-1 px-2 py-0.5 text-xs font-medium rounded-full border ${partyColorClass}`}
>
{mp.party}
</span>
</div>
{isSelected && (
<CheckCircle className="text-amber-600 flex-shrink-0" size={20} />
)}
</div>
</button>
);
})}
</div>
)}
{selectedMP && (
<p className="text-xs text-amber-700 mb-4">
Your MP status will be verified when you confirm your email.
</p>
)}
{showSkip && (
<div className="pt-4 border-t border-amber-200">
<button
type="button"
onClick={onSkip}
disabled={disabled}
className="text-sm text-amber-700 hover:text-amber-900 underline"
>
Skip for now - I'll verify later
</button>
<p className="text-xs text-amber-600 mt-1">
You can verify your MP status anytime from your profile settings.
</p>
</div>
)}
</div>
);
}