Fix contact management and import workflow

- Restore single Import button on ContactListPage that navigates to ImportPage
- Add Gmail as third import option on ImportPage (Contacts, Gmail, LinkedIn)
- Fix TypeScript build errors by resolving import conflicts
- Remove unused variables and clean up component exports
- Ensure all builds pass successfully

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
main
Oliver Sylvester-Bradley 2 months ago
parent 451d2a2908
commit 0aa92fdf3a
  1. 24
      public/import-sources.json
  2. 44
      src/components/account/MyCollectionPage.tsx
  3. 2
      src/components/account/MyHomePage.tsx
  4. 25
      src/components/account/PersonhoodCredentials.tsx
  5. 1
      src/components/account/RCardPrivacySettings.tsx
  6. 9
      src/pages/AccountPage.tsx
  7. 40
      src/pages/ContactListPage.tsx
  8. 7
      src/pages/ContactViewPage.tsx
  9. 4
      src/pages/ImportPage.tsx

@ -1,12 +1,4 @@
[ [
{
"id": "linkedin",
"name": "LinkedIn",
"type": "linkedin",
"icon": "LinkedInIcon",
"description": "Import your LinkedIn connections",
"isAvailable": true
},
{ {
"id": "contacts", "id": "contacts",
"name": "Contacts", "name": "Contacts",
@ -14,5 +6,21 @@
"icon": "ContactsIcon", "icon": "ContactsIcon",
"description": "Import from your device contacts", "description": "Import from your device contacts",
"isAvailable": true "isAvailable": true
},
{
"id": "gmail",
"name": "Gmail",
"type": "gmail",
"icon": "GmailIcon",
"description": "Import your Gmail contacts",
"isAvailable": true
},
{
"id": "linkedin",
"name": "LinkedIn",
"type": "linkedin",
"icon": "LinkedInIcon",
"description": "Import your LinkedIn connections",
"isAvailable": true
} }
] ]

@ -41,11 +41,7 @@ import {
PostAdd, PostAdd,
Visibility, Visibility,
FolderOpen, FolderOpen,
QueryStats,
Psychology,
Send, Send,
AutoFixHigh,
StarOutline,
AutoAwesome, AutoAwesome,
} from '@mui/icons-material'; } from '@mui/icons-material';
import type { BookmarkedItem, Collection, CollectionFilter, CollectionStats } from '../../types/collection'; import type { BookmarkedItem, Collection, CollectionFilter, CollectionStats } from '../../types/collection';
@ -67,7 +63,7 @@ const MyCollectionPage = ({}: MyCollectionPageProps) => {
const [menuAnchor, setMenuAnchor] = useState<{ [key: string]: HTMLElement | null }>({}); const [menuAnchor, setMenuAnchor] = useState<{ [key: string]: HTMLElement | null }>({});
const [showQueryDialog, setShowQueryDialog] = useState(false); const [showQueryDialog, setShowQueryDialog] = useState(false);
const [queryText, setQueryText] = useState(''); const [queryText, setQueryText] = useState('');
const [stats, setStats] = useState<CollectionStats>({ const [/* stats */] = useState<CollectionStats>({
totalItems: 0, totalItems: 0,
unreadItems: 0, unreadItems: 0,
favoriteItems: 0, favoriteItems: 0,
@ -231,25 +227,25 @@ const MyCollectionPage = ({}: MyCollectionPageProps) => {
setFilteredItems(mockItems); setFilteredItems(mockItems);
// Calculate stats // Calculate stats
const categories = [...new Set(mockItems.map(item => item.category).filter(Boolean))]; // const categories = [...new Set(mockItems.map(item => item.category).filter(Boolean))];
const types = [...new Set(mockItems.map(item => item.type))]; // const types = [...new Set(mockItems.map(item => item.type))];
const recentDate = new Date(Date.now() - 1000 * 60 * 60 * 24 * 7); // const recentDate = new Date(Date.now() - 1000 * 60 * 60 * 24 * 7);
const newStats: CollectionStats = { // const newStats: CollectionStats = {
totalItems: mockItems.length, // totalItems: mockItems.length,
unreadItems: mockItems.filter(item => !item.isRead).length, // unreadItems: mockItems.filter(item => !item.isRead).length,
favoriteItems: mockItems.filter(item => item.isFavorite).length, // favoriteItems: mockItems.filter(item => item.isFavorite).length,
byType: types.reduce((acc, type) => ({ // byType: types.reduce((acc, type) => ({
...acc, // ...acc,
[type]: mockItems.filter(item => item.type === type).length // [type]: mockItems.filter(item => item.type === type).length
}), {}), // }), {}),
byCategory: categories.reduce((acc, category) => ({ // byCategory: categories.reduce((acc, category) => ({
...acc, // ...acc,
[category || 'Uncategorized']: mockItems.filter(item => item.category === category).length // [category || 'Uncategorized']: mockItems.filter(item => item.category === category).length
}), {}), // }), {}),
recentlyAdded: mockItems.filter(item => item.bookmarkedAt > recentDate).length, // recentlyAdded: mockItems.filter(item => item.bookmarkedAt > recentDate).length,
}; // };
setStats(newStats); // setStats(newStats);
}, []); }, []);
// Filter and sort items // Filter and sort items

@ -11,7 +11,6 @@ import {
MenuItem, MenuItem,
TextField, TextField,
InputAdornment, InputAdornment,
Grid,
Button, Button,
Divider, Divider,
} from '@mui/material'; } from '@mui/material';
@ -24,7 +23,6 @@ import {
Edit, Edit,
Delete, Delete,
Share, Share,
ThumbUp,
Comment, Comment,
Download, Download,
Launch, Launch,

@ -8,7 +8,6 @@ import {
Chip, Chip,
Button, Button,
Grid, Grid,
LinearProgress,
IconButton, IconButton,
Dialog, Dialog,
DialogTitle, DialogTitle,
@ -30,17 +29,14 @@ import {
Share, Share,
Timeline, Timeline,
LocationOn, LocationOn,
CalendarToday,
TrendingUp,
Security, Security,
People,
Star, Star,
Refresh, Refresh,
Info, Info,
Close, Close,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { QRCodeSVG } from 'qrcode.react'; import { QRCodeSVG } from 'qrcode.react';
import type { PersonhoodCredentials, PersonhoodVerification } from '../../types/personhood'; import type { PersonhoodCredentials } from '../../types/personhood';
interface PersonhoodCredentialsProps { interface PersonhoodCredentialsProps {
credentials: PersonhoodCredentials; credentials: PersonhoodCredentials;
@ -48,7 +44,7 @@ interface PersonhoodCredentialsProps {
onRefreshCredentials: () => void; onRefreshCredentials: () => void;
} }
const PersonhoodCredentials = ({ const PersonhoodCredentialsComponent = ({
credentials, credentials,
onGenerateQR, onGenerateQR,
onRefreshCredentials onRefreshCredentials
@ -57,13 +53,13 @@ const PersonhoodCredentials = ({
const [showQRDialog, setShowQRDialog] = useState(false); const [showQRDialog, setShowQRDialog] = useState(false);
const [showHistoryDialog, setShowHistoryDialog] = useState(false); const [showHistoryDialog, setShowHistoryDialog] = useState(false);
const getCredibilityLevel = (score: number) => { // const getCredibilityLevel = (score: number) => {
if (score >= 90) return { label: 'Excellent', color: 'success' as const }; // if (score >= 90) return { label: 'Excellent', color: 'success' as const };
if (score >= 75) return { label: 'High', color: 'info' as const }; // if (score >= 75) return { label: 'High', color: 'info' as const };
if (score >= 60) return { label: 'Good', color: 'warning' as const }; // if (score >= 60) return { label: 'Good', color: 'warning' as const };
if (score >= 40) return { label: 'Fair', color: 'warning' as const }; // if (score >= 40) return { label: 'Fair', color: 'warning' as const };
return { label: 'Low', color: 'error' as const }; // return { label: 'Low', color: 'error' as const };
}; // };
const formatDate = (date: Date) => { const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('en-US', { return new Intl.DateTimeFormat('en-US', {
@ -85,7 +81,6 @@ const PersonhoodCredentials = ({
return `${Math.floor(diffInDays / 365)} years ago`; return `${Math.floor(diffInDays / 365)} years ago`;
}; };
const credibilityLevel = getCredibilityLevel(credentials.credibilityScore);
return ( return (
<Box> <Box>
@ -342,4 +337,4 @@ const PersonhoodCredentials = ({
); );
}; };
export default PersonhoodCredentials; export default PersonhoodCredentialsComponent;

@ -12,7 +12,6 @@ import {
InputLabel, InputLabel,
Slider, Slider,
Divider, Divider,
Chip,
alpha, alpha,
} from '@mui/material'; } from '@mui/material';
import { import {

@ -12,7 +12,6 @@ import {
Avatar, Avatar,
Button, Button,
IconButton, IconButton,
Chip,
useTheme, useTheme,
alpha, alpha,
} from '@mui/material'; } from '@mui/material';
@ -37,8 +36,8 @@ import RCardPrivacySettings from '../components/account/RCardPrivacySettings';
import RCardManagement from '../components/account/RCardManagement'; import RCardManagement from '../components/account/RCardManagement';
import MyHomePage from '../components/account/MyHomePage'; import MyHomePage from '../components/account/MyHomePage';
import MyCollectionPage from '../components/account/MyCollectionPage'; import MyCollectionPage from '../components/account/MyCollectionPage';
import PersonhoodCredentials from '../components/account/PersonhoodCredentials'; import PersonhoodCredentialsComponent from '../components/account/PersonhoodCredentials';
import type { PersonhoodCredentials as PersonhoodCredentialsType } from '../types/personhood'; import type { PersonhoodCredentials } from '../types/personhood';
interface TabPanelProps { interface TabPanelProps {
children?: React.ReactNode; children?: React.ReactNode;
@ -61,7 +60,7 @@ const AccountPage = () => {
const [selectedRCard, setSelectedRCard] = useState<RCardWithPrivacy | null>(null); const [selectedRCard, setSelectedRCard] = useState<RCardWithPrivacy | null>(null);
const [showRCardManagement, setShowRCardManagement] = useState(false); const [showRCardManagement, setShowRCardManagement] = useState(false);
const [editingRCard, setEditingRCard] = useState<RCardWithPrivacy | null>(null); const [editingRCard, setEditingRCard] = useState<RCardWithPrivacy | null>(null);
const [personhoodCredentials, setPersonhoodCredentials] = useState<PersonhoodCredentialsType>({ const [personhoodCredentials] = useState<PersonhoodCredentials>({
userId: 'user-123', userId: 'user-123',
totalVerifications: 12, totalVerifications: 12,
uniqueVerifiers: 8, uniqueVerifiers: 8,
@ -344,7 +343,7 @@ const AccountPage = () => {
{/* Personhood Credentials Section */} {/* Personhood Credentials Section */}
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>
<PersonhoodCredentials <PersonhoodCredentialsComponent
credentials={personhoodCredentials} credentials={personhoodCredentials}
onGenerateQR={handleGenerateQR} onGenerateQR={handleGenerateQR}
onRefreshCredentials={handleRefreshCredentials} onRefreshCredentials={handleRefreshCredentials}

@ -5,7 +5,6 @@ import {
Box, Box,
Tabs, Tabs,
Tab, Tab,
Avatar,
Chip, Chip,
TextField, TextField,
InputAdornment, InputAdornment,
@ -30,8 +29,7 @@ import {
Group, Group,
CloudDownload, CloudDownload,
VerifiedUser, VerifiedUser,
Favorite, Favorite
MailOutline
} from '@mui/icons-material'; } from '@mui/icons-material';
import { dataService } from '../services/dataService'; import { dataService } from '../services/dataService';
import type { Contact } from '../types/contact'; import type { Contact } from '../types/contact';
@ -115,45 +113,13 @@ const ContactListPage = () => {
</Typography> </Typography>
</Box> </Box>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Button
variant="outlined"
startIcon={<MailOutline />}
onClick={() => navigate('/import/gmail')}
sx={{
borderRadius: 2,
color: '#EA4335',
borderColor: '#EA4335',
'&:hover': {
borderColor: '#EA4335',
backgroundColor: alpha('#EA4335', 0.04)
}
}}
>
Import Gmail
</Button>
<Button
variant="outlined"
startIcon={<LinkedIn />}
onClick={() => navigate('/import/linkedin')}
sx={{
borderRadius: 2,
color: '#0077b5',
borderColor: '#0077b5',
'&:hover': {
borderColor: '#0077b5',
backgroundColor: alpha('#0077b5', 0.04)
}
}}
>
Import LinkedIn
</Button>
<Button <Button
variant="outlined" variant="outlined"
startIcon={<CloudDownload />} startIcon={<CloudDownload />}
onClick={() => navigate('/import/contacts')} onClick={() => navigate('/import')}
sx={{ borderRadius: 2 }} sx={{ borderRadius: 2 }}
> >
Import Contacts Import
</Button> </Button>
<Button <Button
variant="outlined" variant="outlined"

@ -4,12 +4,10 @@ import {
Typography, Typography,
Box, Box,
Paper, Paper,
Avatar,
Button, Button,
Chip, Chip,
Divider, Divider,
Grid, Grid,
IconButton,
Card, Card,
CardContent, CardContent,
Alert, Alert,
@ -23,16 +21,11 @@ import {
Phone, Phone,
Business, Business,
LinkedIn, LinkedIn,
Edit,
Share,
Delete,
Person, Person,
Schedule, Schedule,
Source, Source,
Group, Group,
Add, Add,
ThumbUp,
Star,
Send, Send,
VerifiedUser, VerifiedUser,
Favorite Favorite

@ -15,7 +15,7 @@ import {
Box, Box,
Alert, Alert,
} from '@mui/material'; } from '@mui/material';
import { LinkedIn, Contacts, CloudDownload } from '@mui/icons-material'; import { LinkedIn, Contacts, CloudDownload, MailOutline } from '@mui/icons-material';
import { dataService } from '../services/dataService'; import { dataService } from '../services/dataService';
import type { ImportSource } from '../types/contact'; import type { ImportSource } from '../types/contact';
@ -78,6 +78,8 @@ const ImportPage = () => {
return <LinkedIn sx={{ fontSize: 40, color: '#0077b5' }} />; return <LinkedIn sx={{ fontSize: 40, color: '#0077b5' }} />;
case 'ContactsIcon': case 'ContactsIcon':
return <Contacts sx={{ fontSize: 40, color: '#4caf50' }} />; return <Contacts sx={{ fontSize: 40, color: '#4caf50' }} />;
case 'GmailIcon':
return <MailOutline sx={{ fontSize: 40, color: '#EA4335' }} />;
default: default:
return <CloudDownload sx={{ fontSize: 40 }} />; return <CloudDownload sx={{ fontSize: 40 }} />;
} }

Loading…
Cancel
Save