diff --git a/README.md b/README.md index 136600f..2723bf9 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Each exchange is manual, error-prone, and slow. DevCard fixes this. - 🔗 **Universal Profile Aggregation** — GitHub, LinkedIn, Twitter/X, GitLab, Devfolio, and 10+ more platforms - 📱 **QR Code Sharing** — Show your QR, they scan, done - ⚡ **One-Screen Multi-Platform Connect** — Follow on GitHub, Connect on LinkedIn, all from one card +- 📥 **vCard Export & Contact Saving** — Save developer contact cards (.vcf) natively with one tap to your phone book - 📈 **Advanced Analytics** — Track who viewed your card, when, and from where (Web, QR, App) - 🔌 **Per-Platform OAuth Integrations** — Securely connect accounts for "Silent Follows" - 🎯 **Context Cards** — Different cards for different situations (Professional, Hackathon, Community) @@ -48,6 +49,7 @@ Each exchange is manual, error-prone, and slow. DevCard fixes this. - 🔒 **Privacy-First** — No tracking, no data selling, your data stays yours - 🛠️ **Open Source** — Apache 2.0 licensed, community-governed + ## Quick Start ### Prerequisites @@ -273,6 +275,31 @@ New to open source? We've got you covered! Check out our [Good First Issues](htt See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions, coding standards, and PR process. +## vCard Export Support + +DevCard supports exporting contact cards in the industry-standard `.vcf` (vCard 3.0) format directly from both Svelte web and React Native mobile applications. This allows seamless integration with native OS contact books. + +### Supported Fields + +The shared generator maps the following profile attributes dynamically: +- **Full Name (`FN` / `N`)**: Parsed and split into first and last names. +- **Organization (`ORG`)**: Maps to the user's `company`. +- **Title (`TITLE`)**: Maps to the user's `role`. +- **Email (`EMAIL`)**: Automatically checks and extracts public email from profile settings and connected platform links. +- **Phone (`TEL`)**: Automatically parses public phone/WhatsApp URLs and numbers from custom touchpoints. +- **Bio/Notes (`NOTE`)**: Combines personal bio, pronouns, and role description, with proper character escaping. +- **Avatar (`PHOTO`)**: Embeds profile picture via high-performance native image URL loading. +- **Touchpoints (`URL`)**: Appends up to 10+ social/profile links as separate labelled URL entries. + +### Platform Compatibility + +Verified and tested on the following native contact managers and apps: +- **iOS Contacts** (Native contact save flow via Blob + Mobile Safari) +- **Android Contacts** (Native Google Contacts save flow via Mobile Chrome and Native Share Sheet) +- **Apple Contacts & macOS** (Seamless importing of `.vcf` files) +- **Google Contacts** (Standard desktop web and mobile compatibility) +- **Microsoft Outlook** (Clean parsing and field population) + ## License DevCard is licensed under the [Apache License 2.0](./LICENSE). @@ -282,3 +309,4 @@ DevCard is licensed under the [Apache License 2.0](./LICENSE).
Built with ❤️ by the developer community, for the developer community.
+ diff --git a/apps/mobile/src/components/SaveContactButton.tsx b/apps/mobile/src/components/SaveContactButton.tsx new file mode 100644 index 0000000..98f0be2 --- /dev/null +++ b/apps/mobile/src/components/SaveContactButton.tsx @@ -0,0 +1,147 @@ +import React, { useState } from 'react'; +import { + TouchableOpacity, + Text, + StyleSheet, + ActivityIndicator, + Share, + Platform, + Alert, + View, +} from 'react-native'; +import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; +import { getProfileVCard, encodeBase64 } from '../utils/vcard'; +import { APP_URL } from '../config'; + +interface PlatformLink { + id: string; + platform: string; + username: string; + url: string; + displayOrder: number; +} + +interface ProfileData { + username: string; + displayName: string; + bio: string | null; + pronouns: string | null; + role: string | null; + company: string | null; + avatarUrl: string | null; + accentColor: string; + links: PlatformLink[]; +} + +interface SaveContactButtonProps { + profile: ProfileData; +} + +export function SaveContactButton({ profile }: SaveContactButtonProps) { + const [loading, setLoading] = useState(false); + + const handleSaveContact = async () => { + setLoading(true); + try { + const devcardUrl = `${APP_URL}/u/${profile.username}`; + + const vcardContent = getProfileVCard({ + displayName: profile.displayName, + username: profile.username, + bio: profile.bio, + pronouns: profile.pronouns, + role: profile.role, + company: profile.company, + avatarUrl: profile.avatarUrl, + links: profile.links, + devcardUrl, + }); + + if (Platform.OS === 'ios') { + const base64Vcf = encodeBase64(vcardContent); + const fileUri = `data:text/vcard;charset=utf-8;base64,${base64Vcf}`; + + await Share.share({ + url: fileUri, + message: `Save contact info for ${profile.displayName}`, + }); + } else { + // Android and others: Share the vCard content directly as message text + await Share.share({ + message: vcardContent, + title: `Save ${profile.displayName}'s Contact`, + }); + } + } catch (err) { + console.error('Failed to share contact:', err); + Alert.alert('Error', 'Failed to export contact card.'); + } finally { + setLoading(false); + } + }; + + const accentColor = profile.accentColor || COLORS.primary; + + return ( +{errorMsg}
+{/if} + + diff --git a/apps/web/src/lib/index.ts b/apps/web/src/lib/index.ts index 856f2b6..c3e8db5 100644 --- a/apps/web/src/lib/index.ts +++ b/apps/web/src/lib/index.ts @@ -1 +1,2 @@ // place files you want to import through the `$lib` alias in this folder. +export { default as SaveContactButton } from './components/SaveContactButton.svelte'; diff --git a/apps/web/src/routes/u/[username]/+page.svelte b/apps/web/src/routes/u/[username]/+page.svelte index bb23cca..564f4b0 100644 --- a/apps/web/src/routes/u/[username]/+page.svelte +++ b/apps/web/src/routes/u/[username]/+page.svelte @@ -1,5 +1,6 @@