kintex/mobile/components/Avatar.tsx

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' },
});