Доработки профиля
This commit is contained in:
@@ -4,14 +4,19 @@ import Picker from '@emoji-mart/react';
|
||||
import data from '@emoji-mart/data';
|
||||
import { Search, TrendingUp, Loader2 } from 'lucide-react';
|
||||
import { useLang } from '../lib/i18n';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
|
||||
interface TenorGif {
|
||||
interface KlipyGif {
|
||||
id: string;
|
||||
media_formats?: {
|
||||
gif?: { url: string };
|
||||
tinygif?: { url: string };
|
||||
images: {
|
||||
original: { url: string };
|
||||
fixed_height_small: { url: string };
|
||||
};
|
||||
content_description?: string;
|
||||
file: {
|
||||
sd?: { gif?: { url: string }; webp?: { url: string } };
|
||||
hd?: { gif?: { url: string }; webp?: { url: string } };
|
||||
};
|
||||
title?: string;
|
||||
}
|
||||
|
||||
interface EmojiPickerProps {
|
||||
@@ -20,36 +25,65 @@ interface EmojiPickerProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const getTenorKey = () => localStorage.getItem('vortex_tenor_key') || '';
|
||||
const getKlipyKey = () => import.meta.env.VITE_KLIPY_API_KEY || '';
|
||||
const getCustomerId = () => {
|
||||
const user = useAuthStore.getState().user;
|
||||
return user?.id || 'anonymous';
|
||||
};
|
||||
|
||||
export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPickerProps) {
|
||||
const { lang, t } = useLang();
|
||||
const [tab, setTab] = useState<'emoji' | 'gif'>('emoji');
|
||||
const [gifQuery, setGifQuery] = useState('');
|
||||
const [gifs, setGifs] = useState<TenorGif[]>([]);
|
||||
const [gifs, setGifs] = useState<KlipyGif[]>([]);
|
||||
const [gifLoading, setGifLoading] = useState(false);
|
||||
const [trendingGifs, setTrendingGifs] = useState<TenorGif[]>([]);
|
||||
const [trendingGifs, setTrendingGifs] = useState<KlipyGif[]>([]);
|
||||
const gifSearchRef = useRef<HTMLInputElement>(null);
|
||||
const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||
|
||||
// Load trending GIFs
|
||||
// Helper to safely extract GIF array from various possible Klipy API responses
|
||||
const extractGifs = (d: any): KlipyGif[] => {
|
||||
if (!d) return [];
|
||||
if (Array.isArray(d)) return d;
|
||||
if (Array.isArray(d.data)) return d.data;
|
||||
if (Array.isArray(d.result)) return d.result;
|
||||
if (d.result && Array.isArray(d.result.data)) return d.result.data;
|
||||
if (Array.isArray(d.gifs)) return d.gifs;
|
||||
return [];
|
||||
};
|
||||
|
||||
// Load trending GIFs (Klipy)
|
||||
useEffect(() => {
|
||||
if (tab === 'gif' && getTenorKey() && trendingGifs.length === 0) {
|
||||
if (tab === 'gif' && getKlipyKey() && trendingGifs.length === 0) {
|
||||
setGifLoading(true);
|
||||
fetch(`https://tenor.googleapis.com/v2/featured?key=${getTenorKey()}&limit=30&media_filter=gif,tinygif`)
|
||||
fetch(`https://api.klipy.com/api/v1/${getKlipyKey()}/gifs/trending?customer_id=${getCustomerId()}&per_page=30`)
|
||||
.then(r => r.json())
|
||||
.then(d => { setTrendingGifs(d.results || []); setGifLoading(false); })
|
||||
.catch(() => setGifLoading(false));
|
||||
.then(d => {
|
||||
setTrendingGifs(extractGifs(d));
|
||||
setGifLoading(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('Klipy trending error:', e);
|
||||
setTrendingGifs([]);
|
||||
setGifLoading(false);
|
||||
});
|
||||
}
|
||||
}, [tab]);
|
||||
}, [tab, trendingGifs.length]);
|
||||
|
||||
const searchGifs = useCallback((q: string) => {
|
||||
if (!getTenorKey() || !q.trim()) { setGifs([]); return; }
|
||||
if (!getKlipyKey() || !q.trim()) { setGifs([]); return; }
|
||||
setGifLoading(true);
|
||||
fetch(`https://tenor.googleapis.com/v2/search?key=${getTenorKey()}&q=${encodeURIComponent(q)}&limit=30&media_filter=gif,tinygif`)
|
||||
fetch(`https://api.klipy.com/api/v1/${getKlipyKey()}/gifs/search?customer_id=${getCustomerId()}&q=${encodeURIComponent(q)}&per_page=30`)
|
||||
.then(r => r.json())
|
||||
.then(d => { setGifs(d.results || []); setGifLoading(false); })
|
||||
.catch(() => setGifLoading(false));
|
||||
.then(d => {
|
||||
setGifs(extractGifs(d));
|
||||
setGifLoading(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('Klipy search error:', e);
|
||||
setGifs([]);
|
||||
setGifLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleGifSearch = (q: string) => {
|
||||
@@ -58,9 +92,9 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
|
||||
debounceRef.current = setTimeout(() => searchGifs(q), 400);
|
||||
};
|
||||
|
||||
const pickGif = (gif: TenorGif) => {
|
||||
const url = gif.media_formats?.gif?.url || gif.media_formats?.tinygif?.url || '';
|
||||
const preview = gif.media_formats?.tinygif?.url || url;
|
||||
const pickGif = (gif: KlipyGif) => {
|
||||
const url = gif.file?.hd?.gif?.url || gif.file?.sd?.gif?.url || '';
|
||||
const preview = gif.file?.sd?.webp?.url || gif.file?.sd?.gif?.url || url;
|
||||
if (onSelectGif && url) {
|
||||
onSelectGif(url, preview);
|
||||
}
|
||||
@@ -112,7 +146,7 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
|
||||
>
|
||||
EMOJI
|
||||
</button>
|
||||
{(getTenorKey() || onSelectGif) && (
|
||||
{(getKlipyKey() || onSelectGif) && (
|
||||
<button
|
||||
onClick={() => { setTab('gif'); setTimeout(() => gifSearchRef.current?.focus(), 100); }}
|
||||
className={`flex-1 py-2.5 text-xs font-semibold tracking-wide transition-colors ${tab === 'gif' ? 'text-white border-b-2 border-accent' : 'text-zinc-500 hover:text-zinc-300'}`}
|
||||
@@ -144,12 +178,12 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
|
||||
{/* GIF tab */}
|
||||
{tab === 'gif' && (
|
||||
<div className="flex flex-col h-[calc(100%-41px)]">
|
||||
{!getTenorKey() ? (
|
||||
{!getKlipyKey() ? (
|
||||
<div className="flex-1 flex flex-col items-center justify-center p-6 text-center">
|
||||
<p className="text-sm text-zinc-400 mb-2">{t('tenorKeyRequired')}</p>
|
||||
<p className="text-sm text-zinc-400 mb-2">Klipy API Key required</p>
|
||||
<p className="text-xs text-zinc-500 mb-3">{t('openConsoleRun')}</p>
|
||||
<code className="text-xs bg-black/30 px-3 py-1.5 rounded-lg text-vortex-400">
|
||||
localStorage.setItem('vortex_tenor_key', 'YOUR_KEY')
|
||||
VITE_KLIPY_API_KEY in .env
|
||||
</code>
|
||||
</div>
|
||||
) : (
|
||||
@@ -188,8 +222,8 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
|
||||
className="w-full mb-1.5 rounded-lg overflow-hidden hover:opacity-80 transition-opacity block"
|
||||
>
|
||||
<img
|
||||
src={gif.media_formats?.tinygif?.url || gif.media_formats?.gif?.url}
|
||||
alt={gif.content_description || 'GIF'}
|
||||
src={gif.file?.sd?.webp?.url || gif.file?.sd?.gif?.url || gif.file?.hd?.gif?.url}
|
||||
alt={gif.title || 'GIF'}
|
||||
className="w-full h-auto rounded-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user