diff --git a/apps/web/package.json b/apps/web/package.json
index c4da4105..9df11539 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -27,7 +27,7 @@
"@profullstack/live-translate": "^0.3.0",
"@profullstack/notifications": "0.1.4",
"@profullstack/pairux-embed": "workspace:*",
- "@profullstack/profile": "0.1.6",
+ "@profullstack/profile": "0.1.7",
"@profullstack/redeem-credits": "^0.2.0",
"@profullstack/stack": "^0.5.0",
"@profullstack/x402-gateway": "^0.13.1",
diff --git a/apps/web/src/app/api/v1/coinpay/oauth/callback/route.ts b/apps/web/src/app/api/v1/coinpay/oauth/callback/route.ts
index 05d27d42..65c8dcbf 100644
--- a/apps/web/src/app/api/v1/coinpay/oauth/callback/route.ts
+++ b/apps/web/src/app/api/v1/coinpay/oauth/callback/route.ts
@@ -2,6 +2,8 @@ import { createServerClient, type CookieOptions } from '@supabase/ssr';
import type { SupabaseClient } from '@supabase/supabase-js';
import { NextResponse, type NextRequest } from 'next/server';
import { finishSignIn, sameState } from '@profullstack/coinpay-onboard/oauth';
+import { saveOpenProfileContact } from '@profullstack/profile';
+import { profileStore } from '@/lib/profile';
import { createClient } from '@/lib/supabase/server';
import { serviceClient } from '@/lib/supabase/service';
import {
@@ -80,6 +82,13 @@ export async function GET(request: NextRequest) {
return fail('failed');
}
if (!result.ok) return fail(result.code);
+ if (pending.mode === 'connect' && claims.openprofile_url) {
+ try {
+ await saveOpenProfileContact(profileStore(), result.userId, claims.openprofile_url);
+ } catch {
+ console.warn('[coinpay] OpenProfile import skipped');
+ }
+ }
if (pending.mode === 'connect') return go('/settings?coinpay=connected');
const minted = await mintMagicLinkHash(admin, result.userId);
diff --git a/apps/web/src/components/profile/ProfileCard.tsx b/apps/web/src/components/profile/ProfileCard.tsx
index cff15c90..25940359 100644
--- a/apps/web/src/components/profile/ProfileCard.tsx
+++ b/apps/web/src/components/profile/ProfileCard.tsx
@@ -73,6 +73,7 @@ export function ProfileCard() {
onChange={setValues}
errors={errors}
disabled={!loaded}
+ importOpenProfile
/>
{error &&
{error}
}
diff --git a/apps/web/src/lib/coinpay-signin.ts b/apps/web/src/lib/coinpay-signin.ts
index 695c5b46..4606e23a 100644
--- a/apps/web/src/lib/coinpay-signin.ts
+++ b/apps/web/src/lib/coinpay-signin.ts
@@ -149,6 +149,7 @@ export interface CoinpayClaims {
sub: string;
email: string | null;
name: string | null;
+ openprofile_url?: string;
}
/** Trim what CoinPay returned to what is stored; the email is contact info only. */
@@ -156,10 +157,17 @@ export function claimsFrom(r: {
sub: string;
email: string | null;
name: string | null;
+ userinfo?: object;
}): CoinpayClaims {
const email = typeof r.email === 'string' && r.email.includes('@') ? r.email.slice(0, 320) : null;
const name = typeof r.name === 'string' && r.name.trim() ? r.name.trim().slice(0, 200) : null;
- return { sub: r.sub, email, name };
+ const openprofileUrl = (r.userinfo as { openprofile_url?: unknown } | undefined)?.openprofile_url;
+ return {
+ sub: r.sub,
+ email,
+ name,
+ ...(typeof openprofileUrl === 'string' ? { openprofile_url: openprofileUrl } : {}),
+ };
}
export interface IdentityRow {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 90c2fcdc..3ba1f0f8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -399,7 +399,7 @@ importers:
version: 1.0.3
'@profullstack/footer':
specifier: latest
- version: 0.5.0(react@19.3.1)
+ version: 0.6.15(react@19.3.1)
'@profullstack/live-translate':
specifier: ^0.3.0
version: 0.3.0
@@ -410,8 +410,8 @@ importers:
specifier: workspace:*
version: link:../../packages/embed
'@profullstack/profile':
- specifier: 0.1.6
- version: 0.1.6(react@19.3.1)
+ specifier: 0.1.7
+ version: 0.1.7(react@19.3.1)
'@profullstack/redeem-credits':
specifier: ^0.2.0
version: 0.2.0
@@ -3007,8 +3007,8 @@ packages:
react:
optional: true
- '@profullstack/footer@0.5.0':
- resolution: {integrity: sha512-TrllS87nK7J0c/UgYckaFIWVhLOtr6m73XXFSHIuSfOg7NfuWUdwjtsfMXGaZiiUnmMYj3zhGcfzW0DicDwl8w==}
+ '@profullstack/footer@0.6.15':
+ resolution: {integrity: sha512-kkkSG8Xw0VnyzYk76ko0aiIgVT/MXmKkKUiJ5u1DZkeLZcDh0d2GjFShwTcCd8AsQr5fBet39wbzSwPeUYwuWg==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
@@ -3033,8 +3033,12 @@ packages:
resolution: {integrity: sha512-4RSq9C4ghgf/W1YGK6Ai9oMJWWLW2WOSn8cl/J7tbdK/c8nhIr3z3BjQcYJyjGGIMdRuUBGVBAJmp3uAtY28+A==}
engines: {node: '>=20.11'}
- '@profullstack/profile@0.1.6':
- resolution: {integrity: sha512-xkn0P3ANz151QJdlBwcBXkHk813qW9qAmuTZKcuvpEZYkyHDC9J9KfvpVSRepfjECzz4ONGHWO5Zwc4BHpfK8g==}
+ '@profullstack/openprofile@0.2.0':
+ resolution: {integrity: sha512-cdFsBbfRXurvy5KqHDNcKXxurK2s/YlfVgnILFRpVl2hq3860rExqDee8IfsFGxw5cPdqGv2fxUxtek1Xy2U5Q==}
+ engines: {node: '>=22'}
+
+ '@profullstack/profile@0.1.7':
+ resolution: {integrity: sha512-N7UkypkkY2JkSBKzmdmyy6+DCoxI048qUZQksEjSRwSIyy4mjU9H1qW4yhZD+LJBhTEupgJv//Uxwhiy2FuGmA==}
engines: {node: '>=20'}
hasBin: true
peerDependencies:
@@ -12531,7 +12535,7 @@ snapshots:
optionalDependencies:
react: 19.3.1
- '@profullstack/footer@0.5.0(react@19.3.1)':
+ '@profullstack/footer@0.6.15(react@19.3.1)':
optionalDependencies:
react: 19.3.1
@@ -12543,7 +12547,11 @@ snapshots:
'@profullstack/notifications@0.1.4': {}
- '@profullstack/profile@0.1.6(react@19.3.1)':
+ '@profullstack/openprofile@0.2.0': {}
+
+ '@profullstack/profile@0.1.7(react@19.3.1)':
+ dependencies:
+ '@profullstack/openprofile': 0.2.0
optionalDependencies:
react: 19.3.1