Initial commit - modstagram-next
This commit is contained in:
24
src/hooks/getUserById.tsx
Normal file
24
src/hooks/getUserById.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
// useUserById.ts
|
||||
import { useState, useEffect } from "react";
|
||||
import useAxios from "./useAxios";
|
||||
import { User } from "@/types/types";
|
||||
|
||||
export const useUserById = (_id: string) => {
|
||||
const [user, setUser] = useState<User>();
|
||||
const { request } = useAxios();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUser = async () => {
|
||||
try {
|
||||
const response = await request<{ user: User }>("GET", `/profile/${_id}`);
|
||||
setUser(response.user);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (_id) fetchUser();
|
||||
}, [_id, request]);
|
||||
|
||||
return user;
|
||||
};
|
||||
19
src/hooks/use-mobile.ts
Normal file
19
src/hooks/use-mobile.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
}
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
|
||||
return !!isMobile
|
||||
}
|
||||
150
src/hooks/useAxios.tsx
Normal file
150
src/hooks/useAxios.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback } from "react";
|
||||
import axios, {
|
||||
AxiosRequestConfig,
|
||||
AxiosError,
|
||||
InternalAxiosRequestConfig,
|
||||
} from "axios";
|
||||
import toast from "react-hot-toast";
|
||||
import { BASE_URL as base } from "@/components/main/BaseUrl";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const BASE_URL = base;
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
timeout: 900000, // 5 دقیقه (300,000 میلیثانیه)
|
||||
maxContentLength: Infinity,
|
||||
maxBodyLength: Infinity,
|
||||
});
|
||||
|
||||
const getToken = (): string => {
|
||||
if (typeof window !== "undefined") {
|
||||
const token = Cookies.get("token");
|
||||
return token ? `Bearer ${token}` : "";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
// اضافه کردن توکن به هر درخواست
|
||||
axiosInstance.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
if (config.headers) {
|
||||
config.headers["Authorization"] = getToken();
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
// بررسی noToast در headers و جلوگیری از نمایش toast در پاسخها
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => {
|
||||
if (!response.config.headers?.["X-No-Toast"] && response.data?.message) {
|
||||
// toast.success(response.data.message);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
const { response, config } = error;
|
||||
|
||||
if (response) {
|
||||
const { status, data } = response;
|
||||
const noToast = config?.headers?.["X-No-Toast"];
|
||||
|
||||
if (status === 401 && !noToast) {
|
||||
handleUnauthorized();
|
||||
} else if (status === 422) {
|
||||
const errors = (data as { [key: string]: string[] }).errors || {};
|
||||
if (!noToast) {
|
||||
if (
|
||||
typeof data === "object" &&
|
||||
(data as { message?: string }).message
|
||||
) {
|
||||
toast.error((data as { message: string }).message);
|
||||
} else {
|
||||
Object.values(errors).forEach((messages) => {
|
||||
(messages as unknown as string[]).forEach((message) =>
|
||||
toast.error(message)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if ([429, 500, 503].includes(status)) {
|
||||
if (!noToast) {
|
||||
// toast.error("An unexpected error occurred. Please try again later.");
|
||||
}
|
||||
} else {
|
||||
if (!noToast) {
|
||||
// toast.error((data as any)?.message || "An error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
const handleUnauthorized = (): void => {
|
||||
if (typeof window !== "undefined") {
|
||||
// toast.error("برای انجام عملیات لطفا وارد حساب کاربری خود شوید");
|
||||
}
|
||||
};
|
||||
|
||||
// تایپ اصلاحشده برای درخواستها
|
||||
interface RequestConfig extends AxiosRequestConfig {
|
||||
noToast?: boolean;
|
||||
}
|
||||
|
||||
const useAxios = () => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<AxiosError | null>(null);
|
||||
|
||||
const request = useCallback(
|
||||
async <T,>(
|
||||
method: AxiosRequestConfig["method"],
|
||||
url: string,
|
||||
data: unknown = null,
|
||||
config: RequestConfig = {}
|
||||
): Promise<T> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const isFormData = data instanceof FormData;
|
||||
|
||||
const response = await axiosInstance({
|
||||
method,
|
||||
url,
|
||||
data,
|
||||
...config,
|
||||
headers: {
|
||||
|
||||
|
||||
...config.headers,
|
||||
...(isFormData ? {} : { "Content-Type": "application/json" }), // حذف Content-Type برای FormData
|
||||
...(config.noToast ? { "X-No-Toast": "true" } : {}), // ارسال noToast به عنوان یک هدر
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
setError(err as AxiosError);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
request,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export default useAxios;
|
||||
74
src/hooks/useInfiniteScroll.tsx
Normal file
74
src/hooks/useInfiniteScroll.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import useAxios from "@/hooks/useAxios";
|
||||
|
||||
interface FetchParams {
|
||||
endpoint: string;
|
||||
queryKey: (string | number)[];
|
||||
params?: Record<string, any>;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
const useInfiniteScroll = ({
|
||||
endpoint,
|
||||
queryKey,
|
||||
params = {},
|
||||
limit = 10,
|
||||
}: FetchParams) => {
|
||||
const { request } = useAxios();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const fetchData = async ({ pageParam = 1 }) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
...params,
|
||||
limit: limit.toString(),
|
||||
page: pageParam.toString(),
|
||||
});
|
||||
const data = await request<any>(
|
||||
"GET",
|
||||
`${endpoint}?${queryParams.toString()}`
|
||||
);
|
||||
return data || [];
|
||||
};
|
||||
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, refetch } =
|
||||
useInfiniteQuery({
|
||||
queryKey,
|
||||
queryFn: ({ pageParam }) => fetchData({ pageParam }),
|
||||
initialPageParam: 1,
|
||||
getNextPageParam: (lastPage, allPages) =>
|
||||
lastPage.totalPages > allPages.length ? allPages.length + 1 : undefined,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const container = containerRef.current || document.documentElement;
|
||||
if (!container || isFetchingNextPage) return;
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||
if (scrollTop + clientHeight >= scrollHeight - 50 && hasNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
};
|
||||
|
||||
const containerElement = containerRef.current;
|
||||
if (containerElement) {
|
||||
containerElement.addEventListener("scroll", handleScroll);
|
||||
} else {
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (containerElement) {
|
||||
containerElement.removeEventListener("scroll", handleScroll);
|
||||
} else {
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
};
|
||||
}, [hasNextPage, fetchNextPage, isFetchingNextPage]);
|
||||
|
||||
return { data, containerRef, isFetchingNextPage, isLoading, refetch };
|
||||
};
|
||||
|
||||
export default useInfiniteScroll;
|
||||
26
src/hooks/useNetworkStatus.ts
Normal file
26
src/hooks/useNetworkStatus.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export type NetworkState = "online" | "offline" | "syncing";
|
||||
|
||||
export function useNetworkStatus() {
|
||||
const [state, setState] = useState<NetworkState>("online");
|
||||
|
||||
useEffect(() => {
|
||||
const update = () =>
|
||||
setState(navigator.onLine ? "online" : "offline");
|
||||
update();
|
||||
window.addEventListener("online", update);
|
||||
window.addEventListener("offline", update);
|
||||
return () => {
|
||||
window.removeEventListener("online", update);
|
||||
window.removeEventListener("offline", update);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const startSync = () => setState("syncing");
|
||||
const endSync = () => setState(navigator.onLine ? "online" : "offline");
|
||||
|
||||
return { state, startSync, endSync, isOffline: state === "offline", isSyncing: state === "syncing" };
|
||||
}
|
||||
39
src/hooks/useScrollDirection.ts
Normal file
39
src/hooks/useScrollDirection.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
/** Returns true when user scrolls down past threshold (Safari-style compact header) */
|
||||
export function useScrollDirection(threshold = 16) {
|
||||
const [compact, setCompact] = useState(false);
|
||||
const lastY = useRef(0);
|
||||
const ticking = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
lastY.current = window.scrollY;
|
||||
|
||||
const update = () => {
|
||||
const y = window.scrollY;
|
||||
if (y <= threshold) {
|
||||
setCompact(false);
|
||||
} else if (y > lastY.current + 4) {
|
||||
setCompact(true);
|
||||
} else if (y < lastY.current - 4) {
|
||||
setCompact(false);
|
||||
}
|
||||
lastY.current = y;
|
||||
ticking.current = false;
|
||||
};
|
||||
|
||||
const onScroll = () => {
|
||||
if (!ticking.current) {
|
||||
ticking.current = true;
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
return () => window.removeEventListener("scroll", onScroll);
|
||||
}, [threshold]);
|
||||
|
||||
return compact;
|
||||
}
|
||||
23
src/hooks/useUser.tsx
Normal file
23
src/hooks/useUser.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { User } from "@/types/types";
|
||||
import useAxios from "./useAxios";
|
||||
|
||||
export const useUser = () => {
|
||||
const [user, setUser] = useState<User>();
|
||||
const { request } = useAxios();
|
||||
const fetchUser = async () => {
|
||||
try {
|
||||
const response = await request<{ user: User }>("GET", "/profile");
|
||||
setUser(response?.user);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user:", error);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchUser();
|
||||
}, []);
|
||||
|
||||
return user;
|
||||
};
|
||||
Reference in New Issue
Block a user