o2o-infinith-frontend/src/shared/ui/channel-link-buttons.tsx

152 lines
4.3 KiB
TypeScript

/**
* ChannelLinkButtons — 등록된 플랫폼 URL/핸들로 새 탭 열기 버튼 묶음.
*
* Report / Plan 헤더 영역에서 공통 사용.
* - 입력은 normalized handle(@view_clinic) 또는 URL(view.co.kr) 모두 허용
* - 비어있는 플랫폼은 자동 생략
* - 디자인: white/60 backdrop-blur + 호버 시 더 진해짐 (ReportHeader 의 메타 칩과 동일 톤)
*/
import { ExternalLink, Heart, MapPin, Globe, Instagram, Youtube, Facebook, Music, FileText, type LucideIcon } from 'lucide-react';
export interface ChannelHandles {
website?: string | null;
instagram?: string | null;
youtube?: string | null;
facebook?: string | null;
tiktok?: string | null;
gangnamUnni?: string | null;
naverPlace?: string | null;
naverBlog?: string | null;
}
type PlatformKey = keyof ChannelHandles;
interface PlatformMeta {
label: string;
color: string;
Icon: LucideIcon;
buildUrl: (handle: string) => string;
}
const META: Record<PlatformKey, PlatformMeta> = {
website: {
label: '홈페이지',
color: '#6C5CE7',
Icon: Globe,
buildUrl: (h) => (/^https?:\/\//.test(h) ? h : `https://${h}`),
},
instagram: {
label: 'Instagram',
color: '#833AB4',
Icon: Instagram,
buildUrl: (h) => `https://instagram.com/${h.replace(/^@/, '')}`,
},
youtube: {
label: 'YouTube',
color: '#FF3D3D',
Icon: Youtube,
buildUrl: (h) => `https://youtube.com/${h.startsWith('@') ? h : `@${h}`}`,
},
facebook: {
label: 'Facebook',
color: '#1877F2',
Icon: Facebook,
buildUrl: (h) => (/^https?:\/\//.test(h) ? h : `https://facebook.com/${h}`),
},
tiktok: {
label: 'TikTok',
color: '#0A1128',
Icon: Music,
buildUrl: (h) => `https://tiktok.com/${h.startsWith('@') ? h : `@${h}`}`,
},
gangnamUnni: {
label: '강남언니',
color: '#FF6B8A',
Icon: Heart,
buildUrl: (h) => {
if (/^https?:\/\//.test(h)) return h;
if (/gangnamunni\.com\//.test(h)) return `https://www.${h.replace(/^\/+/, '')}`;
return `https://www.gangnamunni.com/hospitals/${h.replace(/^\//, '')}`;
},
},
naverPlace: {
label: '네이버 플레이스',
color: '#03C75A',
Icon: MapPin,
buildUrl: (h) => {
if (/^https?:\/\//.test(h)) return h;
if (/place\.naver\.com\//.test(h)) return `https://m.${h.replace(/^m\./, '').replace(/^\/+/, '')}/home`;
return `https://m.place.naver.com/hospital/${h.replace(/^\//, '')}/home`;
},
},
naverBlog: {
label: '네이버 블로그',
color: '#03C75A',
Icon: FileText,
buildUrl: (h) => {
if (/^https?:\/\//.test(h)) return h;
if (/blog\.naver\.com\//.test(h)) return `https://${h.replace(/^\/+/, '')}`;
return `https://blog.naver.com/${h.replace(/^\//, '')}`;
},
},
};
const ORDER: PlatformKey[] = [
'website',
'youtube',
'instagram',
'facebook',
'naverPlace',
'naverBlog',
'gangnamUnni',
'tiktok',
];
interface ChannelLinkButtonsProps {
handles: ChannelHandles;
variant?: 'light' | 'solid';
className?: string;
}
export function ChannelLinkButtons({
handles,
variant = 'light',
className,
}: ChannelLinkButtonsProps) {
const entries = ORDER.flatMap((key) => {
const handle = handles[key];
if (!handle) return [];
return [{ key, handle }];
});
if (entries.length === 0) return null;
const baseClass =
variant === 'light'
? 'bg-white/60 backdrop-blur-sm border border-white/40 hover:bg-white/80 text-slate-700'
: 'bg-white border border-slate-200 hover:border-slate-300 hover:bg-slate-50 text-slate-700';
return (
<div className={`flex flex-wrap gap-2 ${className ?? ''}`}>
{entries.map(({ key, handle }) => {
const meta = META[key];
const Icon = meta.Icon;
const url = meta.buildUrl(handle);
return (
<a
key={key}
href={url}
target="_blank"
rel="noopener noreferrer"
className={`inline-flex items-center gap-2 rounded-full px-3.5 py-2 text-xs font-medium transition-colors ${baseClass}`}
title={`${meta.label}${url}`}
>
<Icon size={16} className="shrink-0" style={{ color: meta.color }} />
<span className="truncate max-w-[140px]">{handle}</span>
<ExternalLink size={12} className="text-slate-400" />
</a>
);
})}
</div>
);
}