This commit is contained in:
payacom
2026-07-11 21:38:41 +03:30
parent 402f199e58
commit d83e91dd2d
11 changed files with 79 additions and 5 deletions

View File

@@ -10,6 +10,7 @@ import Rate from "rc-rate";
import "rc-rate/assets/index.css";
import { useUser } from "@/hooks/useUser";
import { Course } from "@/types/types";
import { useRequireAuth } from "@/lib/auth/useRequireAuth";
export interface Comments {
_id: string;
@@ -60,6 +61,7 @@ function CommentsModal({
const { request } = useAxios();
const containerRef = useRef<HTMLDivElement>(null);
const currentUser = useUser();
const requireAuth = useRequireAuth();
const [hasRatedCourse, setHasRatedCourse] = useState(false);
// تابع دریافت اطلاعات کاربر با caching
@@ -215,6 +217,7 @@ function CommentsModal({
// ارسال کامنت جدید
const sendMessageHandler = async () => {
if (!requireAuth()) return;
if (!newMessage.trim()) {
toast.error("ثبت نظر الزامی است.");
return;

View File

@@ -14,6 +14,7 @@ import { useUserById } from "@/hooks/getUserById";
import { AxiosError } from "axios";
import useInfiniteScroll from "@/hooks/useInfiniteScroll";
import { useRouter } from "next/navigation";
import { useRequireAuth } from "@/lib/auth/useRequireAuth";
function MainModelCardActions({ postData }: { postData: Course }) {
const { _id, user_id, likes } = postData;
@@ -35,6 +36,7 @@ function MainModelCardActions({ postData }: { postData: Course }) {
const { request, loading } = useAxios();
const router = useRouter();
const requireAuth = useRequireAuth();
useEffect(() => {
const fetchData = async () => {
try {
@@ -56,6 +58,7 @@ function MainModelCardActions({ postData }: { postData: Course }) {
}, []);
const toggleLike = async () => {
if (!requireAuth()) return;
if (loading) return;
if (liked === false) {

View File

@@ -6,6 +6,7 @@ import VerificationBadge from "@/components/main/VerificationBadge";
import ProfileAvatar from "@/components/main/ProfileAvatar";
import RoundedInput from "@/components/elements/RoundedInput";
import useAxios from "@/hooks/useAxios";
import { useRequireAuth } from "@/lib/auth/useRequireAuth";
export interface Comments {
_id: string;
@@ -46,7 +47,9 @@ function BillboardCommentsModal({
});
const [newMessage, setNewMessage] = useState("");
const { request } = useAxios();
const requireAuth = useRequireAuth();
const sendMessageHandler = async () => {
if (!requireAuth()) return;
try {
const data = {
text: newMessage,

View File

@@ -8,6 +8,7 @@ import Rate from "rc-rate";
import "rc-rate/assets/index.css";
import BoldIcon from "@/components/ui/BoldIcon";
import ProfileAvatar from "@/components/main/ProfileAvatar";
import { useRequireAuth } from "@/lib/auth/useRequireAuth";
export interface Comments {
_id: string;
@@ -40,6 +41,7 @@ function CommentsModal({ isOpen, onClose, userId, postId }: CommentsModalProps)
const [newMessage, setNewMessage] = useState("");
const [rating, setRating] = useState<number>(0);
const { request } = useAxios();
const requireAuth = useRequireAuth();
const { data, containerRef, isFetchingNextPage, refetch } = useInfiniteScroll({
endpoint: "/users/comments",
@@ -66,6 +68,7 @@ function CommentsModal({ isOpen, onClose, userId, postId }: CommentsModalProps)
: allComments;
const sendMessageHandler = async () => {
if (!requireAuth()) return;
if (!newMessage.trim()) {
toast.error("ثبت نظر الزامی است.");
return;

View File

@@ -14,6 +14,7 @@ import { useUserById } from "@/hooks/getUserById";
import { AxiosError } from "axios";
import useInfiniteScroll from "@/hooks/useInfiniteScroll";
import SharePostModal from "@/components/main/SharePostModal";
import { useRequireAuth } from "@/lib/auth/useRequireAuth";
function MainModelCardActions({ postData }: { postData: Post }) {
const {
@@ -41,8 +42,10 @@ function MainModelCardActions({ postData }: { postData: Post }) {
const [likesCountState, setLikesCount] = useState<number>(likesCount || 0);
const { request, loading } = useAxios();
const requireAuth = useRequireAuth();
const toggleLike = async () => {
if (!requireAuth()) return;
if (!user_id || !postId) {
toast.error("لطفاً وارد حساب کاربری خود شوید یا ثبت‌نام کنید");
return;

View File

@@ -14,6 +14,7 @@ import Cookies from "js-cookie";
import { useRouter } from "next/navigation";
import toast from "react-hot-toast";
import { debounce } from "lodash";
import { useRequireAuth } from "@/lib/auth/useRequireAuth";
const LocationModal = dynamic(() => import("./LocationModal"), {
@@ -52,7 +53,9 @@ function ModelContent({ user, searchParams }: ModelContentProps) {
}, []);
const router = useRouter();
const requireAuth = useRequireAuth();
const sendMsgHandler = () => {
if (!requireAuth()) return;
if (user?.offer_status) {
if (usertype) {
router.push(`/settings/chats/${user?.user_name}/${user?._id}`);
@@ -87,6 +90,11 @@ function ModelContent({ user, searchParams }: ModelContentProps) {
fetchData();
}, [searchParams]);
const openCollaboration = () => {
if (!requireAuth()) return;
setShowModelDetailModal((prev) => !prev);
};
// Debounce برای جلوگیری از باز شدن سریع مودال
const openLocationModal = debounce(() => {
if (user?.show_location) {
@@ -189,7 +197,7 @@ function ModelContent({ user, searchParams }: ModelContentProps) {
) : user?.user_type == "user" ? (
<div className={`grid gap-1 md:gap-4 mt-2 md:mt-4 ${!user?.show_location? "grid-cols-2" : "grid-cols-3"}`}>
<RoundedButton
onClick={() => setShowModelDetailModal(!showModelDetailModal)}
onClick={openCollaboration}
className="text-xs h-7 md:h-8 max-w-full max-sm:text-[8px]"
>
درخواست همکاری
@@ -211,7 +219,7 @@ function ModelContent({ user, searchParams }: ModelContentProps) {
) : (
<div className="grid grid-cols-2 gap-1 md:gap-2 mt-2 mx-auto justify-center max-w-xl">
<RoundedButton
onClick={() => setShowModelDetailModal(!showModelDetailModal)}
onClick={openCollaboration}
className="text-xs h-7 md:h-8 max-w-full max-sm:text-[8px]"
>
دعوت به همکاری

View File

@@ -17,6 +17,7 @@ import SharePostModal from "@/components/main/SharePostModal";
import BoldIcon from "@/components/ui/BoldIcon";
import Cookies from "js-cookie";
import { trackExploreInteraction } from "@/api/trackExploreInteraction";
import { useRequireAuth } from "@/lib/auth/useRequireAuth";
interface ReelsPostActionsProps {
postData: Post;
@@ -50,8 +51,10 @@ export default function ReelsPostActions({ postData }: ReelsPostActionsProps) {
const [likesCountState, setLikesCount] = useState<number>(likesCount || 0);
const { request, loading } = useAxios();
const requireAuth = useRequireAuth();
const toggleLike = async () => {
if (!requireAuth()) return;
if (!user_id || !postId) {
toast.error("لطفاً وارد حساب کاربری خود شوید یا ثبت‌نام کنید");
return;

View File

@@ -117,6 +117,11 @@ axiosInstance.interceptors.response.use(
const handleUnauthorized = (): void => {
if (typeof window === "undefined") return;
// مهمان (بدون توکن) نباید به صفحه ورود هدایت شود؛ فقط آزادانه صفحات را می‌بیند
// و درخواست‌های محافظت‌شده بی‌سروصدا رد می‌شوند. عملیات‌ها خودشان کاربر را به ورود می‌برند.
const hasToken = Boolean(Cookies.get("token"));
if (!hasToken) return;
const path = window.location.pathname;
if (
path.startsWith("/login") ||

View File

@@ -3,7 +3,7 @@
import { useState, useEffect } from "react";
import { User } from "@/types/types";
import useAxios from "./useAxios";
import { getStoredUserId } from "@/lib/auth/session";
import { getStoredUserId, isAuthenticated } from "@/lib/auth/session";
function readStoredUser(): User | undefined {
const id = getStoredUserId();
@@ -23,6 +23,8 @@ export const useUser = () => {
}
};
useEffect(() => {
// مهمان پروفایل شخصی ندارد؛ از فچ بی‌مورد /profile جلوگیری می‌شود
if (!isAuthenticated()) return;
fetchUser();
}, []);

View File

@@ -0,0 +1,34 @@
"use client";
import { useCallback } from "react";
import { useRouter } from "next/navigation";
import toast from "react-hot-toast";
import { isAuthenticated } from "@/lib/auth/session";
/**
* گارد احراز هویت برای عملیات‌ها (لایک، کامنت، فالو، ارسال پیام، درخواست همکاری و ...).
*
* کاربر مهمان می‌تواند آزادانه صفحات را ببیند؛ فقط زمانی که بخواهد عملیاتی انجام دهد،
* پیام کوتاهی نمایش داده و به صفحه ورود با آدرس بازگشت (redirect) هدایت می‌شود.
*
* خروجی `true` یعنی کاربر وارد شده و می‌توان عملیات را ادامه داد.
*/
export function useRequireAuth() {
const router = useRouter();
return useCallback(
(message: string | null = "برای انجام این کار وارد حساب کاربری خود شوید"): boolean => {
if (isAuthenticated()) return true;
if (message) toast.error(message);
const current =
typeof window !== "undefined"
? window.location.pathname + window.location.search
: "/";
router.push(`/login?redirect=${encodeURIComponent(current)}`);
return false;
},
[router]
);
}

View File

@@ -12,7 +12,14 @@ export function middleware(req: NextRequest) {
const token = req.cookies.get("token")?.value?.trim();
if (req.nextUrl.pathname.startsWith("/settings") && !token) {
// صفحاتی که ذاتاً نیازمند ورود هستند (حساب کاربری و ساخت محتوا)
// بقیه صفحات برای مهمان آزاد است و فقط هنگام انجام عملیات به ورود هدایت می‌شود.
const protectedPaths = ["/settings", "/new-post", "/new-project"];
const needsAuth = protectedPaths.some((p) =>
req.nextUrl.pathname.startsWith(p)
);
if (needsAuth && !token) {
const loginUrl = new URL("/login", req.url);
loginUrl.searchParams.set("redirect", req.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
@@ -22,6 +29,6 @@ export function middleware(req: NextRequest) {
}
//
export const config = {
matcher: ["/settings/:path*"],
matcher: ["/settings/:path*", "/new-post/:path*", "/new-project/:path*"],
};