All files / web/src/hooks useShareCode.ts

89.36% Statements 126/141
100% Branches 7/7
100% Functions 2/2
89.36% Lines 126/141

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 1421x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x                               24x 24x 24x 3x 3x 3x 24x 24x 24x 3x 3x 3x 24x 24x 24x 6x 2x 2x 2x 2x 2x 2x 2x 18x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x  
import { useCallback, useRef, useState } from 'react'
 
import { type ShareType, getShareUrl } from '@/lib/share/urls'
 
import { useClipboard } from './useClipboard'
 
export interface UseShareCodeOptions {
  /** The type of share (classroom, family, or room) */
  type: ShareType
  /** The share code */
  code: string
  /** Optional callback to regenerate the code */
  onRegenerate?: () => Promise<string>
}
 
export interface UseShareCodeReturn {
  // Data
  /** The share code */
  code: string
  /** The full share URL */
  shareUrl: string
 
  // Copy actions
  /** Copy the code to clipboard */
  copyCode: () => void
  /** Whether the code was recently copied */
  codeCopied: boolean
  /** Copy the share URL to clipboard */
  copyLink: () => void
  /** Whether the link was recently copied */
  linkCopied: boolean
 
  // Native share
  /** Whether the Web Share API is available */
  canShare: boolean
  /** Open the native share sheet */
  share: () => Promise<void>
  /** Whether a share was recently completed */
  shared: boolean
 
  // Regeneration
  /** Regenerate the code (if supported) */
  regenerate: (() => Promise<void>) | undefined
  /** Whether regeneration is in progress */
  isRegenerating: boolean
}
 
/**
 * Hook for managing share code functionality
 *
 * @example
 * ```tsx
 * const share = useShareCode({
 *   type: 'classroom',
 *   code: 'ABC123',
 * })
 *
 * return (
 *   <div>
 *     <button onClick={share.copyCode}>
 *       {share.codeCopied ? 'Copied!' : 'Copy Code'}
 *     </button>
 *     <button onClick={share.copyLink}>
 *       {share.linkCopied ? 'Copied!' : 'Copy Link'}
 *     </button>
 *   </div>
 * )
 * ```
 */
export function useShareCode({
  type,
  code,
  onRegenerate,
}: UseShareCodeOptions): UseShareCodeReturn {
  const shareUrl = getShareUrl(type, code)
 
  // Separate clipboard state for code and link
  const { copied: codeCopied, copy: copyCodeToClipboard, reset: resetCodeCopied } = useClipboard()
  const { copied: linkCopied, copy: copyLinkToClipboard, reset: resetLinkCopied } = useClipboard()
 
  const [isRegenerating, setIsRegenerating] = useState(false)
  const [shared, setShared] = useState(false)
  const shareTimeoutRef = useRef<ReturnType<typeof setTimeout>>()
 
  const canShare = typeof navigator !== 'undefined' && !!navigator.share
 
  const share = useCallback(async () => {
    if (!canShare) return
    try {
      await navigator.share({
        title: 'Join on Abaci',
        text: `Use code ${code} to join`,
        url: shareUrl,
      })
      setShared(true)
      clearTimeout(shareTimeoutRef.current)
      shareTimeoutRef.current = setTimeout(() => setShared(false), 1500)
    } catch (error) {
      // User cancelled the share sheet — silently ignore
      if (error instanceof Error && error.name === 'AbortError') return
      console.error('[useShareCode] Share failed:', error)
    }
  }, [canShare, code, shareUrl])
 
  const copyCode = useCallback(() => {
    // Reset link copied state when copying code
    resetLinkCopied()
    copyCodeToClipboard(code)
  }, [code, copyCodeToClipboard, resetLinkCopied])
 
  const copyLink = useCallback(() => {
    // Reset code copied state when copying link
    resetCodeCopied()
    copyLinkToClipboard(shareUrl)
  }, [shareUrl, copyLinkToClipboard, resetCodeCopied])
 
  const regenerate = onRegenerate
    ? async () => {
        setIsRegenerating(true)
        try {
          await onRegenerate()
        } finally {
          setIsRegenerating(false)
        }
      }
    : undefined
 
  return {
    code,
    shareUrl,
    copyCode,
    codeCopied,
    copyLink,
    linkCopied,
    canShare,
    share,
    shared,
    regenerate,
    isRegenerating,
  }
}