73 lines
2.4 KiB
TypeScript
73 lines
2.4 KiB
TypeScript
/*
|
|
* 공통 Avatar — 프로필 사진(photoUrl) 표시, 실패/부재 시 이름 이니셜 폴백. WISE 모바일 Avatar 이식.
|
|
* 레퍼런스: guardia-messenger/app/uiws/_lib/components/Avatar.tsx.
|
|
* 사진 서빙 엔드포인트는 인증 필요 — <Image>는 api 인터셉터를 거치지 않으므로 Bearer 헤더를 직접 부여.
|
|
* 색은 theme 토큰만 사용(신규 토큰 발명 금지).
|
|
*/
|
|
import React, { useEffect, useState } from 'react';
|
|
import { Image, StyleSheet, Text, View } from 'react-native';
|
|
import { getAccessToken } from '../lib/api';
|
|
import { colors } from '../theme';
|
|
|
|
interface AvatarProps {
|
|
photoUrl?: string | null;
|
|
name?: string | null;
|
|
size?: number;
|
|
testID?: string;
|
|
}
|
|
|
|
function initials(name?: string | null): string {
|
|
if (!name) return '?';
|
|
const trimmed = name.trim();
|
|
if (!trimmed) return '?';
|
|
const isAscii = /^[\x00-\x7F]+$/.test(trimmed);
|
|
if (isAscii) {
|
|
const parts = trimmed.split(/\s+/);
|
|
return (parts.length >= 2 ? parts[0][0] + parts[1][0] : trimmed.slice(0, 2)).toUpperCase();
|
|
}
|
|
return trimmed.slice(0, 1);
|
|
}
|
|
|
|
export function Avatar({ photoUrl, name, size = 48, testID }: AvatarProps) {
|
|
const [failed, setFailed] = useState(false);
|
|
const token = getAccessToken();
|
|
// photoUrl/token 변화 시 실패 상태 초기화 후 재시도(업로드·캐시버스터 반영).
|
|
useEffect(() => {
|
|
setFailed(false);
|
|
}, [photoUrl, token]);
|
|
|
|
const dim = { width: size, height: size, borderRadius: size / 2 };
|
|
const showImage = !!photoUrl && !!token && !failed;
|
|
|
|
if (showImage) {
|
|
return (
|
|
<Image
|
|
testID={testID}
|
|
source={{
|
|
uri: photoUrl as string,
|
|
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
|
}}
|
|
style={[dim, { borderWidth: 1, borderColor: colors.neutral200, backgroundColor: colors.primary050 }]}
|
|
onError={() => setFailed(true)}
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
);
|
|
}
|
|
return (
|
|
<View
|
|
testID={testID}
|
|
style={[dim, styles.fallback, { backgroundColor: colors.primary100, borderColor: colors.neutral200 }]}
|
|
accessibilityLabel={name ? `${name} 프로필` : '프로필'}
|
|
>
|
|
<Text style={[styles.initials, { color: colors.primary700, fontSize: size * 0.4 }]}>
|
|
{initials(name)}
|
|
</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
fallback: { alignItems: 'center', justifyContent: 'center', borderWidth: 1 },
|
|
initials: { fontWeight: '800' },
|
|
});
|