feat: artwork share system with modal, native Web Share API, and tracking

- Add ArtworkShareModal with glassmorphism UI (Facebook, X, Pinterest, Email, Copy Link, Embed Code)
- Add ArtworkShareButton with lazy-loaded modal and native share fallback
- Add useWebShare hook abstracting navigator.share with AbortError handling
- Add ShareToast auto-dismissing notification component
- Add share() endpoint to ArtworkInteractionController (POST /api/artworks/{id}/share)
- Add artwork_shares migration for Phase 2 share tracking
- Refactor ArtworkActionBar to use new ArtworkShareButton component
This commit is contained in:
2026-02-28 15:29:45 +01:00
parent 568b3f3abb
commit 90f244f264
8 changed files with 569 additions and 38 deletions

View File

@@ -0,0 +1,41 @@
import { useCallback, useMemo } from 'react'
/**
* useWebShare abstracts native Web Share API with a fallback callback.
*
* Usage:
* const { canNativeShare, share } = useWebShare({ onFallback })
* share({ title, text, url })
*
* If `navigator.share` is available the browser-native share sheet opens.
* Otherwise `onFallback({ title, text, url })` is called (e.g. open a modal).
*/
export default function useWebShare({ onFallback } = {}) {
const canNativeShare = useMemo(
() => typeof navigator !== 'undefined' && typeof navigator.share === 'function',
[],
)
const share = useCallback(
async ({ title, text, url }) => {
if (canNativeShare) {
try {
await navigator.share({ title, text, url })
return { shared: true, native: true }
} catch (err) {
// User cancelled the native share — don't fall through to modal
if (err?.name === 'AbortError') {
return { shared: false, native: true }
}
}
}
// Fallback — open modal
onFallback?.({ title, text, url })
return { shared: false, native: false }
},
[canNativeShare, onFallback],
)
return { canNativeShare, share }
}