Sasha commited on
Commit
977232b
·
1 Parent(s): 5ba9d00

feat: fetch moderator avatars via API proxy with initials fallback

Browse files
client/src/App.jsx CHANGED
@@ -731,6 +731,7 @@ export default function App() {
731
  modProfiles={modProfiles}
732
  selectedMod={selectedMod}
733
  setSelectedMod={setSelectedMod}
 
734
  />
735
  )}
736
  </>
 
731
  modProfiles={modProfiles}
732
  selectedMod={selectedMod}
733
  setSelectedMod={setSelectedMod}
734
+ API_BASE={API_BASE}
735
  />
736
  )}
737
  </>
client/src/components/ModeratorTab.jsx CHANGED
@@ -76,6 +76,63 @@ function InitialsAvatar({ name, size = 40, border = '1px solid var(--color-borde
76
  );
77
  }
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  export default function ModeratorTab({
80
  twitchConfigured,
81
  auth,
@@ -83,7 +140,8 @@ export default function ModeratorTab({
83
  TWITCH_CHANNEL,
84
  modProfiles,
85
  selectedMod,
86
- setSelectedMod
 
87
  }) {
88
 
89
  // Set default selected moderator if not set yet
@@ -175,7 +233,7 @@ export default function ModeratorTab({
175
  {/* Header metadata */}
176
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '1.5rem' }}>
177
  <div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
178
- <InitialsAvatar name={mod.moderator} size={48} border={`2px solid ${trophyColor}`} />
179
  <div>
180
  <h4 style={{ margin: 0, fontSize: '1.1rem', fontWeight: 700, letterSpacing: '-0.01em' }}>{mod.moderator}</h4>
181
  <span style={{
@@ -269,7 +327,7 @@ export default function ModeratorTab({
269
  </td>
270
  <td style={{ padding: '0.9rem 0.5rem' }}>
271
  <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
272
- <InitialsAvatar name={mod.moderator} size={32} />
273
  <span style={{ fontWeight: 600, color: isSelected ? 'var(--color-text-main)' : 'var(--color-text-muted)' }}>{mod.moderator}</span>
274
  </div>
275
  </td>
@@ -327,7 +385,7 @@ export default function ModeratorTab({
327
  padding: '1.25rem'
328
  }}>
329
  <div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
330
- <InitialsAvatar name={selectedMod.moderator} size={54} border="2px solid var(--color-brand)" />
331
  <div>
332
  <h3 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 800 }}>{selectedMod.moderator}</h3>
333
  <span className="badge badge-mod" style={{ marginTop: '0.35rem', fontSize: '0.65rem' }}>Модератор</span>
 
76
  );
77
  }
78
 
79
+ // Custom Avatar Component that fetches from Twitch API Proxy, falls back to initials
80
+ function ModeratorAvatar({ name, size = 40, border = '1px solid var(--color-border)', API_BASE }) {
81
+ const [imgSrc, setImgSrc] = React.useState('');
82
+ const [hasError, setHasError] = React.useState(false);
83
+ const [loading, setLoading] = React.useState(true);
84
+
85
+ React.useEffect(() => {
86
+ if (!name) return;
87
+ const base = API_BASE || '';
88
+ setImgSrc(`${base}/api/avatar/${name.toLowerCase()}`);
89
+ setHasError(false);
90
+ setLoading(true);
91
+ }, [name, API_BASE]);
92
+
93
+ if (hasError || !name) {
94
+ return <InitialsAvatar name={name} size={size} border={border} />;
95
+ }
96
+
97
+ return (
98
+ <div style={{
99
+ width: size,
100
+ height: size,
101
+ borderRadius: '50%',
102
+ border,
103
+ display: 'inline-flex',
104
+ alignItems: 'center',
105
+ justifyContent: 'center',
106
+ overflow: 'hidden',
107
+ flexShrink: 0,
108
+ backgroundColor: 'rgba(255, 255, 255, 0.05)',
109
+ position: 'relative'
110
+ }}>
111
+ {loading && (
112
+ <div style={{
113
+ position: 'absolute',
114
+ inset: 0,
115
+ background: 'rgba(255, 255, 255, 0.05)',
116
+ borderRadius: '50%'
117
+ }} />
118
+ )}
119
+ <img
120
+ src={imgSrc}
121
+ alt={name}
122
+ onLoad={() => setLoading(false)}
123
+ onError={() => setHasError(true)}
124
+ style={{
125
+ width: '100%',
126
+ height: '100%',
127
+ objectFit: 'cover',
128
+ opacity: loading ? 0 : 1,
129
+ transition: 'opacity 0.2s ease-in-out'
130
+ }}
131
+ />
132
+ </div>
133
+ );
134
+ }
135
+
136
  export default function ModeratorTab({
137
  twitchConfigured,
138
  auth,
 
140
  TWITCH_CHANNEL,
141
  modProfiles,
142
  selectedMod,
143
+ setSelectedMod,
144
+ API_BASE
145
  }) {
146
 
147
  // Set default selected moderator if not set yet
 
233
  {/* Header metadata */}
234
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '1.5rem' }}>
235
  <div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
236
+ <ModeratorAvatar name={mod.moderator} size={48} border={`2px solid ${trophyColor}`} API_BASE={API_BASE} />
237
  <div>
238
  <h4 style={{ margin: 0, fontSize: '1.1rem', fontWeight: 700, letterSpacing: '-0.01em' }}>{mod.moderator}</h4>
239
  <span style={{
 
327
  </td>
328
  <td style={{ padding: '0.9rem 0.5rem' }}>
329
  <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
330
+ <ModeratorAvatar name={mod.moderator} size={32} API_BASE={API_BASE} />
331
  <span style={{ fontWeight: 600, color: isSelected ? 'var(--color-text-main)' : 'var(--color-text-muted)' }}>{mod.moderator}</span>
332
  </div>
333
  </td>
 
385
  padding: '1.25rem'
386
  }}>
387
  <div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
388
+ <ModeratorAvatar name={selectedMod.moderator} size={54} border="2px solid var(--color-brand)" API_BASE={API_BASE} />
389
  <div>
390
  <h3 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 800 }}>{selectedMod.moderator}</h3>
391
  <span className="badge badge-mod" style={{ marginTop: '0.35rem', fontSize: '0.65rem' }}>Модератор</span>