changed reward on button
This commit is contained in:
49
src/hooks/useProfile.js
Normal file
49
src/hooks/useProfile.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { API_URL } from "../config/authConstants";
|
||||
import { apiFetch } from "../api/apiClient";
|
||||
|
||||
/**
|
||||
* useProfile — хук для получения свежих данных профиля с бэка.
|
||||
* Делает GET /api/v1/users/{id} и возвращает актуальные данные пользователя
|
||||
* (включая аватар), которые могут устареть в локальном контексте, если
|
||||
* профиль менялся с другого устройства.
|
||||
*
|
||||
* fetchProfile возвращает { success, data, error }.
|
||||
*/
|
||||
export function useProfile() {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const fetchProfile = useCallback(async (userId) => {
|
||||
if (!userId) {
|
||||
const message = t('profile.fetchMissingUserId');
|
||||
setError(message);
|
||||
return { success: false, data: null, error: message };
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError("");
|
||||
|
||||
try {
|
||||
const res = await apiFetch(`${API_URL}/api/v1/users/${userId}`);
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
throw new Error(data.message || t('profile.fetchErrorWithStatus', { status: res.status }));
|
||||
}
|
||||
|
||||
const user = await res.json();
|
||||
return { success: true, data: user, error: null };
|
||||
} catch (err) {
|
||||
const message = err.message || t('profile.fetchError');
|
||||
setError(message);
|
||||
return { success: false, data: null, error: message };
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
return { fetchProfile, loading, error };
|
||||
}
|
||||
71
src/hooks/useScrollDirection.ts
Normal file
71
src/hooks/useScrollDirection.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { RefObject } from "react";
|
||||
|
||||
export type ScrollDirection = "up" | "down";
|
||||
|
||||
/**
|
||||
* useScrollDirection — отслеживает направление скролла контейнера `containerRef`
|
||||
* и сообщает, когда тулбар должен быть скрыт.
|
||||
*
|
||||
* Логика (как в Telegram Web):
|
||||
* - при скролле вниз тулбар прячется (hidden = true);
|
||||
* - при скролле вверх (или подъезде в самое начало списка) — показывается;
|
||||
* - есть порог `showThreshold` у вершины, чтобы тулбар появлялся не раньше,
|
||||
* чем контент вернулся достаточно близко к началу.
|
||||
*
|
||||
* requestAnimationFrame + ref'ы: обработчик скролла НЕ триггерит ре-рендер
|
||||
* на каждое событие — состояние меняется только когда скрытость реально
|
||||
* пересекает границу, всё остальное живёт в ref'ах.
|
||||
*/
|
||||
export default function useScrollDirection<T extends HTMLElement>(
|
||||
containerRef: RefObject<T | null>,
|
||||
{ hideThreshold = 12, showThreshold = 88 }: { hideThreshold?: number; showThreshold?: number } = {},
|
||||
): boolean {
|
||||
const [hidden, setHidden] = useState(false);
|
||||
|
||||
const hiddenRef = useRef(false);
|
||||
const lastYRef = useRef(0);
|
||||
const rafRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
|
||||
lastYRef.current = el.scrollTop;
|
||||
|
||||
const onScroll = () => {
|
||||
if (rafRef.current != null) return;
|
||||
rafRef.current = requestAnimationFrame(() => {
|
||||
rafRef.current = null;
|
||||
const y = el.scrollTop;
|
||||
|
||||
const maxScroll = el.scrollHeight - el.clientHeight;
|
||||
const isAtTop = y <= showThreshold;
|
||||
const isAtBottom = maxScroll > 0 && y >= maxScroll - showThreshold;
|
||||
// Без сенсорных/фрикционных возражений: крошечные рывки не считаем за жест
|
||||
const dy = Math.abs(y - lastYRef.current);
|
||||
|
||||
let next = hiddenRef.current;
|
||||
if (isAtTop || isAtBottom) {
|
||||
next = false;
|
||||
} else if (dy >= hideThreshold) {
|
||||
next = y > lastYRef.current;
|
||||
}
|
||||
lastYRef.current = y;
|
||||
|
||||
if (next !== hiddenRef.current) {
|
||||
hiddenRef.current = next;
|
||||
setHidden(next);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
el.addEventListener("scroll", onScroll, { passive: true });
|
||||
return () => {
|
||||
el.removeEventListener("scroll", onScroll);
|
||||
if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
|
||||
};
|
||||
}, [containerRef, hideThreshold, showThreshold]);
|
||||
|
||||
return hidden;
|
||||
}
|
||||
Reference in New Issue
Block a user