File size: 4,075 Bytes
cd99321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import React, { useState, useEffect, useRef } from 'react'
import { getCategoryIcon } from './categoryIcons'
import { getCached, isLoading, fetchPhoto, onThumbReady } from '../../services/photoService'
import { useAuthStore } from '../../store/authStore'
import type { Place } from '../../types'

interface Category {
  color?: string
  icon?: string
}

interface PlaceAvatarProps {
  place: Pick<Place, 'id' | 'name' | 'image_url' | 'google_place_id' | 'osm_id' | 'lat' | 'lng'>
  size?: number
  category?: Category | null
}

export default React.memo(function PlaceAvatar({ place, size = 32, category }: PlaceAvatarProps) {
  const [photoSrc, setPhotoSrc] = useState<string | null>(place.image_url || null)
  const [visible, setVisible] = useState(false)
  const imageUrlFailed = useRef(false)
  const ref = useRef<HTMLDivElement>(null)
  const placesPhotosEnabled = useAuthStore(s => s.placesPhotosEnabled)

  // Observe visibility — fetch photo only when avatar enters viewport
  useEffect(() => {
    if (place.image_url) { setVisible(true); return }
    if (!placesPhotosEnabled) return
    const el = ref.current
    if (!el) return
    // Check if already cached — show immediately without waiting for intersection
    const photoId = place.google_place_id || place.osm_id
    const cacheKey = photoId || `${place.lat},${place.lng}`
    if (cacheKey && getCached(cacheKey)) { setVisible(true); return }

    const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setVisible(true); io.disconnect() } }, { rootMargin: '200px' })
    io.observe(el)
    return () => io.disconnect()
  }, [place.id])

  useEffect(() => {
    if (!visible) return
    if (place.image_url) { setPhotoSrc(place.image_url); return }
    if (!placesPhotosEnabled) return
    const photoId = place.google_place_id || place.osm_id
    if (!photoId && !(place.lat && place.lng)) { setPhotoSrc(null); return }

    const cacheKey = photoId || `${place.lat},${place.lng}`

    const cached = getCached(cacheKey)
    if (cached) {
      setPhotoSrc(cached.thumbDataUrl || cached.photoUrl)
      if (!cached.thumbDataUrl && cached.photoUrl) {
        return onThumbReady(cacheKey, thumb => setPhotoSrc(thumb))
      }
      return
    }

    if (isLoading(cacheKey)) {
      return onThumbReady(cacheKey, thumb => setPhotoSrc(thumb))
    }

    fetchPhoto(cacheKey, photoId || `coords:${place.lat}:${place.lng}`, place.lat, place.lng, place.name,
      entry => { setPhotoSrc(entry.thumbDataUrl || entry.photoUrl) }
    )
    return onThumbReady(cacheKey, thumb => setPhotoSrc(thumb))
  }, [visible, place.id, place.image_url, place.google_place_id, place.osm_id])

  const bgColor = category?.color || '#6366f1'
  const IconComp = getCategoryIcon(category?.icon)
  const iconSize = Math.round(size * 0.46)

  const containerStyle: React.CSSProperties = {
    width: size, height: size,
    borderRadius: '50%',
    overflow: 'hidden',
    flexShrink: 0,
    backgroundColor: bgColor,
    display: 'flex', alignItems: 'center', justifyContent: 'center',
  }

  if (photoSrc) {
    return (
      <div ref={ref} style={containerStyle}>
        <img
          src={photoSrc}
          alt={place.name}
          decoding="async"
          style={{ width: '100%', height: '100%', objectFit: 'cover' }}
          onError={() => {
            if (!imageUrlFailed.current && photoSrc === place.image_url && (place.google_place_id || place.osm_id)) {
              imageUrlFailed.current = true
              const photoId = place.google_place_id || place.osm_id!
              const cacheKey = `refetch:${photoId}`
              fetchPhoto(cacheKey, photoId, place.lat ?? undefined, place.lng ?? undefined, place.name,
                entry => { setPhotoSrc(entry.thumbDataUrl || entry.photoUrl) }
              )
            } else {
              setPhotoSrc(null)
            }
          }}
        />
      </div>
    )
  }

  return (
    <div ref={ref} style={containerStyle}>
      <IconComp size={iconSize} strokeWidth={1.8} color="rgba(255,255,255,0.92)" />
    </div>
  )
})