diff --git a/android/app/build.gradle b/android/app/build.gradle index ee46509c..5aa20ecf 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.compassconnections.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 125 - versionName "1.29.1" + versionCode 126 + versionName "1.30.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/common/messages/de.json b/common/messages/de.json index 6b9210c3..8d81fac4 100644 --- a/common/messages/de.json +++ b/common/messages/de.json @@ -1067,6 +1067,7 @@ "profile.optional.subtitle": "Optionale Informationen", "profile.optional.title": "Mehr Informationen über mich", "profile.optional.university": "Universität", + "profile.optional.signal_placeholder": "Telefonnummer oder signal.me-Link", "profile.optional.username_or_url": "Benutzername oder URL", "profile.optional.want_kids": "Ich möchte Kinder haben", "profile.optional.work": "Arbeit", diff --git a/common/messages/fr.json b/common/messages/fr.json index 6ae4f132..793a4f81 100644 --- a/common/messages/fr.json +++ b/common/messages/fr.json @@ -1066,6 +1066,7 @@ "profile.optional.subtitle": "Informations optionnelles", "profile.optional.title": "Plus d'informations à mon sujet", "profile.optional.university": "Université", + "profile.optional.signal_placeholder": "Numéro de téléphone ou lien signal.me", "profile.optional.username_or_url": "Nom d'utilisateur ou URL", "profile.optional.want_kids": "Je souhaite avoir des enfants", "profile.optional.work": "Domaine de travail", diff --git a/common/src/socials.ts b/common/src/socials.ts index dfdc3bea..31f840fd 100644 --- a/common/src/socials.ts +++ b/common/src/socials.ts @@ -5,6 +5,7 @@ export const SITE_ORDER = [ 'site', // personal site 'x', // twitter 'discord', + 'signal', 'bluesky', 'mastodon', 'substack', @@ -55,6 +56,8 @@ const stripper: {[key in Site]: (input: string) => string} = { .replace(/^@/, '') .replace(/\/$/, ''), discord: (s) => s, + // strip a signal.me deep link down to the phone number / handle for display + signal: (s) => s.replace(/^(https?:\/\/)?(www\.)?signal\.me\/#[a-z]+\//i, '').replace(/\/$/, ''), paypal: (s) => s.replace(/^(https?:\/\/)?(www\.)?(\w+\.)?paypal\.com\/paypalme\//, '').replace(/\/$/, ''), patreon: (s) => s.replace(/^(https?:\/\/)?(www\.)?(\w+\.)?patreon\.com\//, '').replace(/\/$/, ''), @@ -100,6 +103,16 @@ const urler: {[key in Site]: (handle: string) => string} = { (s.length === 17 || s.length === 18) && !isNaN(parseInt(s, 10)) ? `https://discord.com/users/${s}` // discord user id : discordLink, // our server + // signal.me deep links open the Signal app directly. Phone numbers use #p/, + // a full pasted signal.me link (e.g. a username link, #eu/) is kept as-is. + signal: (s) => { + if (s.startsWith('http')) return s + const cleaned = s.replace(/[\s()\-.]/g, '') + if (/^\+?\d{7,15}$/.test(cleaned)) { + return `https://signal.me/#p/${cleaned.startsWith('+') ? cleaned : `+${cleaned}`}` + } + return `https://signal.me/#p/${s}` + }, bluesky: (s) => `https://bsky.app/profile/${s}`, mastodon: (s) => (s.includes('@') ? `https://${s.split('@')[1]}/@${s.split('@')[0]}` : s), substack: (s) => (s.startsWith('http') ? s : `https://${s}.substack.com`), @@ -121,6 +134,7 @@ export const PLATFORM_LABELS: {[key in Site]: string} = { site: 'Website', x: 'Twitter/X', discord: 'Discord', + signal: 'Signal', bluesky: 'Bluesky', mastodon: 'Mastodon', substack: 'Substack', diff --git a/common/tests/unit/socials.test.ts b/common/tests/unit/socials.test.ts index 3da34310..062d2f35 100644 --- a/common/tests/unit/socials.test.ts +++ b/common/tests/unit/socials.test.ts @@ -55,6 +55,15 @@ describe('strip', () => { expect(strip('linkedin', 'username')).toBe('username') }) }) + + describe('signal', () => { + it('should strip signal.me deep links', () => { + expect(strip('signal', 'https://signal.me/#p/+15551234567')).toBe('+15551234567') + expect(strip('signal', 'signal.me/#p/+15551234567')).toBe('+15551234567') + expect(strip('signal', 'https://signal.me/#eu/abc123')).toBe('abc123') + expect(strip('signal', '+15551234567')).toBe('+15551234567') + }) + }) }) describe('getSocialUrl', () => { @@ -69,6 +78,15 @@ describe('getSocialUrl', () => { expect(getSocialUrl('spotify', 'username')).toBe('https://open.spotify.com/user/username') }) + it('should build signal.me deep links from phone numbers', () => { + expect(getSocialUrl('signal', '+15551234567')).toBe('https://signal.me/#p/+15551234567') + expect(getSocialUrl('signal', '15551234567')).toBe('https://signal.me/#p/+15551234567') + expect(getSocialUrl('signal', '+1 (555) 123-4567')).toBe('https://signal.me/#p/+15551234567') + expect(getSocialUrl('signal', 'https://signal.me/#eu/abc123')).toBe( + 'https://signal.me/#eu/abc123', + ) + }) + it('should handle custom website URLs', () => { expect(getSocialUrl('site', 'example.com')).toBe('https://example.com') expect(getSocialUrl('site', 'https://example.com')).toBe('https://example.com') diff --git a/web/components/social-links-section.tsx b/web/components/social-links-section.tsx index 0eff99d2..09be2ddb 100644 --- a/web/components/social-links-section.tsx +++ b/web/components/social-links-section.tsx @@ -107,9 +107,11 @@ export function SocialLinksSection({profile, setProfile}: SocialLinksSectionProp ) => setNewLinkValue(e.target.value)} diff --git a/web/components/user/social.tsx b/web/components/user/social.tsx index 692022b9..e8a02801 100644 --- a/web/components/user/social.tsx +++ b/web/components/user/social.tsx @@ -2,6 +2,7 @@ import {DocumentTextIcon, LinkIcon} from '@heroicons/react/24/solid' import {Site} from 'common/socials' import {ReactNode} from 'react' import {LuBookmark, LuHandshake, LuHeart, LuUsers} from 'react-icons/lu' +import {SiSignal} from 'react-icons/si' import { TbBrandBluesky, TbBrandDiscord, @@ -23,6 +24,7 @@ export const PLATFORM_ICONS: { site: LinkIcon, x: TbBrandX, discord: TbBrandDiscord, + signal: SiSignal, bluesky: TbBrandBluesky, mastodon: TbBrandMastodon, substack: LuBookmark, diff --git a/web/components/user/user-handles.tsx b/web/components/user/user-handles.tsx index 824e36a2..0bfc3f3a 100644 --- a/web/components/user/user-handles.tsx +++ b/web/components/user/user-handles.tsx @@ -13,6 +13,7 @@ import {Row} from '../layout/row' import {SocialIcon} from './social' const LABELS_TO_RENDER = [ + 'signal', 'okcupid', 'calendly', 'datingdoc',