diff --git a/next.config.ts b/next.config.ts index 4254575..39d0fe4 100644 --- a/next.config.ts +++ b/next.config.ts @@ -49,7 +49,9 @@ const nextConfig = { }, ]; }, - +server: { + port: 3004, + }, // ۴. تنظیمات هدرها async headers() { return [ diff --git a/package.json b/package.json index 3b9165f..178101c 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,9 @@ "version": "0.1.0", "private": true, "scripts": { - "start": "next start -p 3000 -H 0.0.0.0", - "dev": "next dev -p 3000 -H 0.0.0.0", + "dev": "next dev --turbopack --port 3004", "build": "next build", + "start": "next start --port 3004", "lint": "next lint", "postbuild": "next-sitemap" }, diff --git a/public/sitemap-0.xml b/public/sitemap-0.xml index 35f8079..7d0b127 100644 --- a/public/sitemap-0.xml +++ b/public/sitemap-0.xml @@ -1,11 +1,11 @@ -https://modstagram.com/sitemap.xml2026-07-02T19:42:50.918Zdaily0.7 -https://modstagram.com/robots.txt2026-07-02T19:42:50.919Zdaily0.7 -https://modstagram.com/explore2026-07-02T19:42:50.919Zdaily0.7 -https://modstagram.com/academy/payment/failed2026-07-02T19:42:50.919Zdaily0.7 -https://modstagram.com/academy/payment/success2026-07-02T19:42:50.919Zdaily0.7 -https://modstagram.com/about-us2026-07-02T19:42:50.919Zmonthly0.8 +https://modstagram.com/robots.txt2026-07-08T02:15:31.278Zdaily0.7 +https://modstagram.com/sitemap.xml2026-07-08T02:15:31.278Zdaily0.7 +https://modstagram.com/explore2026-07-08T02:15:31.278Zdaily0.7 +https://modstagram.com/academy/payment/failed2026-07-08T02:15:31.278Zdaily0.7 +https://modstagram.com/academy/payment/success2026-07-08T02:15:31.279Zdaily0.7 +https://modstagram.com/about-us2026-07-08T02:15:31.279Zmonthly0.8 https://modstagram.comdaily1 https://modstagram.com/projectsdaily0.9 https://modstagram.com/billboardsdaily0.9 diff --git a/src/app/(auth)/verify/avatar/page.tsx b/src/app/(auth)/verify/avatar/page.tsx index 038b2ef..2621531 100644 --- a/src/app/(auth)/verify/avatar/page.tsx +++ b/src/app/(auth)/verify/avatar/page.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState, useCallback } from "react"; import Container from "@/components/elements/Container"; import AuthNextButton from "@/components/auth/AuthNextButton"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import useAxios from "@/hooks/useAxios"; import { IProfileData } from "@/types/types"; import Image from "next/image"; diff --git a/src/app/(auth)/verify/confirm/page.tsx b/src/app/(auth)/verify/confirm/page.tsx index fd2709b..12aff28 100644 --- a/src/app/(auth)/verify/confirm/page.tsx +++ b/src/app/(auth)/verify/confirm/page.tsx @@ -9,7 +9,7 @@ import Modal from "@/components/elements/Modal"; import Link from "next/link"; import Image from "next/image"; import { useUser } from "@/hooks/useUser"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; function Confirm() { @@ -41,7 +41,7 @@ function Confirm() { width={280} height={280} alt={user?.user_name || "user profile"} - src={`${IMAGE_BASE_URL}${user?.profile_image}`} + src={buildStorageUrl(user?.profile_image)} unoptimized={true} />
diff --git a/src/app/(auth)/verify/national-cart/page.tsx b/src/app/(auth)/verify/national-cart/page.tsx index 9fcc53f..c62d1dd 100644 --- a/src/app/(auth)/verify/national-cart/page.tsx +++ b/src/app/(auth)/verify/national-cart/page.tsx @@ -9,7 +9,7 @@ import AuthNextButton from "@/components/auth/AuthNextButton"; import toast from "react-hot-toast"; import AuthUserDetails from "@/components/auth/AuthUserDetails"; import Image from "next/image"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import AuthRules from "@/components/auth/AuthRules"; function NationalCart() { diff --git a/src/app/(projects)/academy/[id]/[title]/CourseClient.tsx b/src/app/(projects)/academy/[id]/[title]/CourseClient.tsx index 78179ea..538aa9f 100644 --- a/src/app/(projects)/academy/[id]/[title]/CourseClient.tsx +++ b/src/app/(projects)/academy/[id]/[title]/CourseClient.tsx @@ -20,7 +20,7 @@ import { import { Course } from "@/types/types"; import useAxios from "@/hooks/useAxios"; import { Comments } from "@/components/academy/CommentsModal"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import { usePathname } from "next/navigation"; import Cookies from "js-cookie"; @@ -670,7 +670,7 @@ useEffect(() => { alt={item?.user_id?.user_name || "کاربر"} src={ item?.user_id?.profile_image - ? `${IMAGE_BASE_URL}${item.user_id.profile_image}` + ? buildStorageUrl(item.user_id.profile_image) : "/images/default-avatar.png" } priority={true} diff --git a/src/app/(projects)/academy/profile/[academyId]/page.tsx b/src/app/(projects)/academy/profile/[academyId]/page.tsx index 9710d5f..90de6c8 100644 --- a/src/app/(projects)/academy/profile/[academyId]/page.tsx +++ b/src/app/(projects)/academy/profile/[academyId]/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import React, { useEffect, useState } from "react"; import { Academy, Course } from "@/types/types"; import Container from "@/components/elements/Container"; diff --git a/src/app/billboards/profile/[...id]/page.tsx b/src/app/billboards/profile/[...id]/page.tsx index 15bf7e9..52eff0f 100644 --- a/src/app/billboards/profile/[...id]/page.tsx +++ b/src/app/billboards/profile/[...id]/page.tsx @@ -1,4 +1,4 @@ -import { BASE_URL, IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { BASE_URL, IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import React, { cache } from "react"; import { cookies } from "next/headers"; import { IAdvertisingProfile } from "@/types/types"; diff --git a/src/app/billboards/profile/[id]/[title]/page.tsx b/src/app/billboards/profile/[id]/[title]/page.tsx index 15bf7e9..52eff0f 100644 --- a/src/app/billboards/profile/[id]/[title]/page.tsx +++ b/src/app/billboards/profile/[id]/[title]/page.tsx @@ -1,4 +1,4 @@ -import { BASE_URL, IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { BASE_URL, IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import React, { cache } from "react"; import { cookies } from "next/headers"; import { IAdvertisingProfile } from "@/types/types"; diff --git a/src/app/posts/[id]/page.tsx b/src/app/posts/[id]/page.tsx index 487a9f6..bbd298f 100644 --- a/src/app/posts/[id]/page.tsx +++ b/src/app/posts/[id]/page.tsx @@ -1,7 +1,7 @@ import { Metadata } from "next"; import { fetchPostById } from "@/api/fetchPostById"; import PostFeedView from "@/components/posts/PostFeedView"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import { Suspense } from "react"; import PageLoader from "@/components/ui/PageLoader"; diff --git a/src/app/settings/academy/storeProfile/page.tsx b/src/app/settings/academy/storeProfile/page.tsx index e45b4f1..03197c5 100644 --- a/src/app/settings/academy/storeProfile/page.tsx +++ b/src/app/settings/academy/storeProfile/page.tsx @@ -28,7 +28,7 @@ import "leaflet/dist/leaflet.css"; import { useRouter } from "next/navigation"; import useAxios from "@/hooks/useAxios"; import { Academy } from "@/types/types"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; // Zod schema for store settings const storeSchema = z.object({ diff --git a/src/app/settings/chats/page.tsx b/src/app/settings/chats/page.tsx index da32a79..96efd3a 100644 --- a/src/app/settings/chats/page.tsx +++ b/src/app/settings/chats/page.tsx @@ -2,7 +2,7 @@ import Container from "@/components/elements/Container"; import RoundedInput from "@/components/elements/RoundedInput"; -import { IMAGE_BASE_URL, SOCKET_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, SOCKET_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import PageTitle from "@/components/settings/PageTitle"; import UserDetails from "@/components/settings/UserDetails"; import useInfiniteScroll from "@/hooks/useInfiniteScroll"; @@ -115,7 +115,7 @@ function Chats() { width={56} height={56} alt={item?.user_name} - src={IMAGE_BASE_URL + item?.profile_image} + src={buildStorageUrl(item?.profile_image)} className="h-14 w-14 rounded-full object-cover" /> ) : ( diff --git a/src/app/settings/edit/Authentication/page.tsx b/src/app/settings/edit/Authentication/page.tsx index 669ad3b..c88ffcb 100644 --- a/src/app/settings/edit/Authentication/page.tsx +++ b/src/app/settings/edit/Authentication/page.tsx @@ -9,7 +9,7 @@ import AuthNextButton from "@/components/auth/AuthNextButton"; import toast from "react-hot-toast"; import AuthUserDetails from "@/components/auth/AuthUserDetails"; import Image from "next/image"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import AuthRules from "@/components/auth/AuthRules"; function NationalCart() { diff --git a/src/app/settings/edit/License/page.tsx b/src/app/settings/edit/License/page.tsx index a7a586f..229951a 100644 --- a/src/app/settings/edit/License/page.tsx +++ b/src/app/settings/edit/License/page.tsx @@ -7,7 +7,7 @@ import React, { useState, useEffect } from "react"; import AuthNextButton from "@/components/auth/AuthNextButton"; import Image from "next/image"; import { useUser } from "@/hooks/useUser"; -import { BASE_URL, IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { BASE_URL, IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import useAxios from "@/hooks/useAxios"; import toast from "react-hot-toast"; import Cookies from "js-cookie"; @@ -185,7 +185,7 @@ function LicensePage() { alt={user?.user_name || "user profile"} src={ user?.profile_image - ? `${IMAGE_BASE_URL}${user.profile_image}` + ? buildStorageUrl(user.profile_image) : "/images/placeholder.png" } unoptimized={true} diff --git a/src/app/settings/edit/avatar/page.tsx b/src/app/settings/edit/avatar/page.tsx index 339edf9..1e72cb7 100644 --- a/src/app/settings/edit/avatar/page.tsx +++ b/src/app/settings/edit/avatar/page.tsx @@ -2,7 +2,7 @@ import AuthNextButton from "@/components/auth/AuthNextButton"; import Container from "@/components/elements/Container"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import useAxios from "@/hooks/useAxios"; import { useUser } from "@/hooks/useUser"; import Image from "next/image"; diff --git a/src/app/users/[username]/page.tsx b/src/app/users/[username]/page.tsx index 346109b..a6069e7 100644 --- a/src/app/users/[username]/page.tsx +++ b/src/app/users/[username]/page.tsx @@ -1,4 +1,4 @@ -import { BASE_URL, IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { BASE_URL, IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import React from "react"; import { cookies } from "next/headers"; import { User } from "@/types/types"; @@ -30,7 +30,7 @@ export async function generateMetadata({ params }: IUserProps): Promise ) : ( diff --git a/src/components/billboards/BillboardPage/BillboardImageSlider.tsx b/src/components/billboards/BillboardPage/BillboardImageSlider.tsx index 33b82df..669ec59 100644 --- a/src/components/billboards/BillboardPage/BillboardImageSlider.tsx +++ b/src/components/billboards/BillboardPage/BillboardImageSlider.tsx @@ -5,7 +5,7 @@ import { Swiper, SwiperSlide } from "swiper/react"; import "swiper/css"; import { Pagination } from "swiper/modules"; import Image from "next/image"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; interface BillboardImageSliderProps { images: string[]; diff --git a/src/components/billboards/MainBillboardCard/BillboardCommentsModal.tsx b/src/components/billboards/MainBillboardCard/BillboardCommentsModal.tsx index 02d62a1..13507cb 100644 --- a/src/components/billboards/MainBillboardCard/BillboardCommentsModal.tsx +++ b/src/components/billboards/MainBillboardCard/BillboardCommentsModal.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import Modal from "../../elements/Modal"; import Image from "next/image"; import useInfiniteScroll from "@/hooks/useInfiniteScroll"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import RoundedInput from "@/components/elements/RoundedInput"; import useAxios from "@/hooks/useAxios"; @@ -81,7 +81,7 @@ function BillboardCommentsModal({ width={50} height={50} alt={item?.userId?.user_name} - src={`${IMAGE_BASE_URL}${item?.userId?.profile_image}`} + src={buildStorageUrl(item?.userId?.profile_image)} priority={true} unoptimized={true} /> diff --git a/src/components/billboards/MainBillboardCard/MainBillboardCard.tsx b/src/components/billboards/MainBillboardCard/MainBillboardCard.tsx index 10a506d..d4dc95a 100644 --- a/src/components/billboards/MainBillboardCard/MainBillboardCard.tsx +++ b/src/components/billboards/MainBillboardCard/MainBillboardCard.tsx @@ -1,5 +1,5 @@ import RoundedDiv from "@/components/elements/RoundedDiv"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import { IAdvertising } from "@/types/types"; import Image from "next/image"; import Link from "next/link"; diff --git a/src/components/billboards/NewBillboard/step1/ImageUploader.tsx b/src/components/billboards/NewBillboard/step1/ImageUploader.tsx index 5d75032..90dce75 100644 --- a/src/components/billboards/NewBillboard/step1/ImageUploader.tsx +++ b/src/components/billboards/NewBillboard/step1/ImageUploader.tsx @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import { useState } from "react"; type ImageUploaderProps = { diff --git a/src/components/billboards/NewBillboard/submitBillboard.ts b/src/components/billboards/NewBillboard/submitBillboard.ts index 8be4f91..be55fb2 100644 --- a/src/components/billboards/NewBillboard/submitBillboard.ts +++ b/src/components/billboards/NewBillboard/submitBillboard.ts @@ -1,147 +1,147 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { dataURLtoBlob } from "@/helpers/helpers"; -import { BillboardFormData } from "@/contexts/BillboardFormContext"; - -interface CreateAdvertisingResponse { - success: boolean; - message: string; - data: { - id: string; - }; -} - -type RequestFn = ( - method: "GET" | "POST" | "PUT" | "DELETE", - url: string, - data?: unknown -) => Promise; - -export async function submitBillboard( - formData: BillboardFormData, - isEditing: boolean, - request: RequestFn -): Promise<{ id: string } | null> { - const { - adsTitle, - categoryId, - description, - stateId, - cityId, - neighbourhood, - address, - markerCoordinate, - services, - selectedFeatures, - landline, - mobile, - telegram, - whatsapp, - instagram, - saveData, - images, - } = formData; - - const data = new FormData(); - data.append("title", adsTitle); - data.append("category", categoryId); - if (description) { - data.append("description", description); - } - data.append("province", stateId ? stateId : ""); - data.append("city", cityId ? cityId : ""); - if (neighbourhood) { - data.append("neighbourhood", neighbourhood); - } - if (address) { - data.append("address", address); - } - if (markerCoordinate) { - data.append("lat", markerCoordinate[0]); - data.append("lng", markerCoordinate[1]); - } - data.append("services", JSON.stringify(services)); - data.append("features", JSON.stringify(selectedFeatures)); - if (landline) { - data.append("contactInfo[phone]", landline); - } - if (mobile) { - data.append("contactInfo[mobile]", mobile); - } - if (telegram) { - data.append("contactInfo[telegramLink]", telegram); - } - if (whatsapp) { - data.append("contactInfo[whatsappNumber]", whatsapp); - } - if (instagram) { - data.append("contactInfo[instagramLink]", instagram); - } - if (saveData !== null && typeof saveData === "boolean") { - data.append("contactInfo[saveInfoForNextAds]", String(saveData)); - } - - data.append("type", isEditing ? formData.selectedType || "free" : "free"); - - const mostDiscountPercentage = - services.length > 0 - ? services.reduce((max, service) => { - const discount = - typeof service.discountPercentage === "number" - ? service.discountPercentage - : 0; - return discount > max ? discount : max; - }, 0) - : 0; - - if (mostDiscountPercentage) { - data.append("mostDiscountPercentage", String(mostDiscountPercentage)); - } - - images?.forEach((image) => { - if (typeof image === "string" && image.startsWith("/advertising/")) { - data.append("existingImages[]", image); - } else { - const blob = dataURLtoBlob(image); - data.append("images", blob); - } - }); - - services.forEach((service) => { - if ( - typeof service.image === "string" && - service.image.startsWith("/services/") - ) { - data.append(`existingServiceImages[${service.id}]`, service.image); - } else if (service.image) { - const blob = dataURLtoBlob(service.image); - data.append( - `serviceImages[${service.id}]`, - blob, - `service-${service.id}.jpg` - ); - } - }); - - if (isEditing) { - await request<{ id: string; message: string }>( - "POST", - `/advertising/edit/${formData?._id}`, - data - ); - return null; - } - - const response = await request( - "POST", - "/advertising/create", - data - ); - - await request("POST", "/advertising/initiate-payment-web", { - item_name: "free", - advertisingId: response.data.id, - showDiscount: false, - }); - - return { id: response.data.id }; -} +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { dataURLtoBlob } from "@/helpers/helpers"; +import { BillboardFormData } from "@/contexts/BillboardFormContext"; + +interface CreateAdvertisingResponse { + success: boolean; + message: string; + data: { + id: string; + }; +} + +type RequestFn = ( + method: "GET" | "POST" | "PUT" | "DELETE", + url: string, + data?: unknown +) => Promise; + +export async function submitBillboard( + formData: BillboardFormData, + isEditing: boolean, + request: RequestFn +): Promise<{ id: string } | null> { + const { + adsTitle, + categoryId, + description, + stateId, + cityId, + neighbourhood, + address, + markerCoordinate, + services, + selectedFeatures, + landline, + mobile, + telegram, + whatsapp, + instagram, + saveData, + images, + } = formData; + + const data = new FormData(); + data.append("title", adsTitle); + data.append("category", categoryId); + if (description) { + data.append("description", description); + } + data.append("province", stateId ? stateId : ""); + data.append("city", cityId ? cityId : ""); + if (neighbourhood) { + data.append("neighbourhood", neighbourhood); + } + if (address) { + data.append("address", address); + } + if (markerCoordinate) { + data.append("lat", markerCoordinate[0]); + data.append("lng", markerCoordinate[1]); + } + data.append("services", JSON.stringify(services)); + data.append("features", JSON.stringify(selectedFeatures)); + if (landline) { + data.append("contactInfo[phone]", landline); + } + if (mobile) { + data.append("contactInfo[mobile]", mobile); + } + if (telegram) { + data.append("contactInfo[telegramLink]", telegram); + } + if (whatsapp) { + data.append("contactInfo[whatsappNumber]", whatsapp); + } + if (instagram) { + data.append("contactInfo[instagramLink]", instagram); + } + if (saveData !== null && typeof saveData === "boolean") { + data.append("contactInfo[saveInfoForNextAds]", String(saveData)); + } + + data.append("type", isEditing ? formData.selectedType || "free" : "free"); + + const mostDiscountPercentage = + services.length > 0 + ? services.reduce((max, service) => { + const discount = + typeof service.discountPercentage === "number" + ? service.discountPercentage + : 0; + return discount > max ? discount : max; + }, 0) + : 0; + + if (mostDiscountPercentage) { + data.append("mostDiscountPercentage", String(mostDiscountPercentage)); + } + + images?.forEach((image) => { + if (typeof image === "string" && image.startsWith("/advertising/")) { + data.append("existingImages[]", image); + } else { + const blob = dataURLtoBlob(image); + data.append("images", blob); + } + }); + + services.forEach((service) => { + if ( + typeof service.image === "string" && + service.image.startsWith("/services/") + ) { + data.append(`existingServiceImages[${service.id}]`, service.image); + } else if (service.image) { + const blob = dataURLtoBlob(service.image); + data.append( + `serviceImages[${service.id}]`, + blob, + `service-${service.id}.jpg` + ); + } + }); + + if (isEditing) { + await request<{ id: string; message: string }>( + "POST", + `/advertising/edit/${formData?._id}`, + data + ); + return null; + } + + const response = await request( + "POST", + "/advertising/create", + data + ); + + await request("POST", "/advertising/initiate-payment-web", { + item_name: "free", + advertisingId: response.data.id, + showDiscount: false, + }); + + return { id: response.data.id }; +} diff --git a/src/components/billboards/Profile/AdProfileContentPosts.tsx b/src/components/billboards/Profile/AdProfileContentPosts.tsx index 47c4c14..a255465 100644 --- a/src/components/billboards/Profile/AdProfileContentPosts.tsx +++ b/src/components/billboards/Profile/AdProfileContentPosts.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ "use client"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import React, { useState } from "react"; import Modal from "react-modal"; import { Swiper, SwiperSlide } from "swiper/react"; diff --git a/src/components/billboards/Profile/AdProfileHead.tsx b/src/components/billboards/Profile/AdProfileHead.tsx index 6ac8e8d..59fc2c0 100644 --- a/src/components/billboards/Profile/AdProfileHead.tsx +++ b/src/components/billboards/Profile/AdProfileHead.tsx @@ -1,5 +1,5 @@ "use client" -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import { IAdvertisingProfile } from "@/types/types"; import Image from "next/image"; import React, { useState } from "react"; @@ -45,7 +45,7 @@ function AdProfileHead({ profile, id }: AdProfileHeadProps) { width={120} height={120} alt={profile?.vitrine_name || "Name"} - src={`${IMAGE_BASE_URL}${profile_image}`} + src={buildStorageUrl(profile_image)} unoptimized={true} /> ) : ( @@ -97,7 +97,7 @@ function AdProfileHead({ profile, id }: AdProfileHeadProps) { width={120} height={120} alt={profile?.vitrine_name || "Name"} - src={`${IMAGE_BASE_URL}${profile_image}`} + src={buildStorageUrl(profile_image)} unoptimized={true} /> ) : ( diff --git a/src/components/chart-area-interactive.tsx b/src/components/chart-area-interactive.tsx index a765a05..d55e2fa 100644 --- a/src/components/chart-area-interactive.tsx +++ b/src/components/chart-area-interactive.tsx @@ -1,449 +1,449 @@ -"use client"; - -import * as React from "react"; -import { useState, useEffect } from "react"; -import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"; - -import { useIsMobile } from "@/hooks/use-mobile"; -import { - Card, - CardAction, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - ChartConfig, - ChartContainer, - ChartTooltip, - ChartTooltipContent, -} from "@/components/ui/chart"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; -import useAxios from "@/hooks/useAxios"; -import toast from "react-hot-toast"; - -interface Payment { - _id: string; - price: number; - course_name: string; - course_id: any; - academy_id: string; - status: string; // "success" = پرداخت نشده, "settled" = پرداخت شده - createdAt: string; - updatedAt: string; - payment_authority: string; - payment_ref_id: string; - discountAmount: number; - taxAmount: number; - taxRate: number; - user_id: any; -} - -interface ChartDataItem { - date: string; - amount: number; - count: number; - formattedDate: string; - persianDate: string; - jalaliDate: string; -} - -const chartConfig = { - amount: { - label: "مبلغ فروش", - color: "#ff107d", - }, - count: { - label: "تعداد فروش", - color: "#10b981", - }, -} satisfies ChartConfig; - -// تبدیل تاریخ میلادی به شمسی -const toJalaliDate = (date: Date): string => { - return new Intl.DateTimeFormat("fa-IR", { - year: "numeric", - month: "numeric", - day: "numeric", - }).format(date); -}; - -// نمایش ساده برای محور X -const getJalaliDayMonth = (date: Date): string => { - return new Intl.DateTimeFormat("fa-IR", { - month: "numeric", - day: "numeric", - }).format(date); -}; - -// تابع برای تولید تمام روزهای بازه زمانی -const generateDateRange = (startDate: Date, endDate: Date): string[] => { - const dates: string[] = []; - const currentDate = new Date(startDate); - - while (currentDate <= endDate) { - dates.push(currentDate.toISOString().split("T")[0]); - currentDate.setDate(currentDate.getDate() + 1); - } - - return dates; -}; - -export function ChartAreaInteractive() { - const isMobile = useIsMobile(); - const [timeRange, setTimeRange] = useState("90d"); - const [isLoading, setIsLoading] = useState(false); - const [chartData, setChartData] = useState([]); - const [totalSales, setTotalSales] = useState(0); // مجموع فروش‌های موفق (تسویه نشده) - const [totalCount, setTotalCount] = useState(0); - const [averageAmount, setAverageAmount] = useState(0); - const [settledAmount, setSettledAmount] = useState(0); // مبلغ تسویه شده (پرداخت شده) - - const { request } = useAxios(); - - useEffect(() => { - if (isMobile) { - setTimeRange("30d"); - } - }, [isMobile]); - - // محاسبه تاریخ شروع بر اساس بازه زمانی - const getStartDate = (range: string, endDate: Date): Date => { - const start = new Date(endDate); - switch (range) { - case "7d": - start.setDate(start.getDate() - 6); - break; - case "30d": - start.setDate(start.getDate() - 29); - break; - case "90d": - start.setDate(start.getDate() - 89); - break; - default: - start.setDate(start.getDate() - 89); - } - start.setHours(0, 0, 0, 0); - return start; - }; - - // پردازش داده‌ها و تکمیل روزهای بدون فروش - const processPaymentsData = (payments: Payment[], range: string) => { - const now = new Date(); - now.setHours(23, 59, 59, 999); - const startDate = getStartDate(range, now); - - // گروه‌بندی پرداخت‌های موجود بر اساس تاریخ - const groupedByDate: { [key: string]: { amount: number; count: number } } = - {}; - let settledTotal = 0; // مجموع مبالغ تسویه شده (پرداخت شده) - - payments.forEach((payment) => { - const date = new Date(payment.createdAt); - const dateKey = date.toISOString().split("T")[0]; - - // مبلغ: price یا taxAmount - const amount = payment.taxAmount || 0; - - // محاسبه مبالغ تسویه شده (status = settled) - if (payment.status === "settled") { - settledTotal += amount; - } - - // فقط پرداخت‌های داخل بازه زمانی را برای چارت در نظر بگیر - // برای چارت، فقط فروش‌های موفق (success) را نشان می‌دهیم - if (payment.status === "success" && date >= startDate && date <= now) { - if (!groupedByDate[dateKey]) { - groupedByDate[dateKey] = { amount: 0, count: 0 }; - } - groupedByDate[dateKey].amount += amount; - groupedByDate[dateKey].count += 1; - } - if (payment.status === "settled" && date >= startDate && date <= now) { - if (!groupedByDate[dateKey]) { - groupedByDate[dateKey] = { amount: 0, count: 0 }; - } - groupedByDate[dateKey].amount += amount; - groupedByDate[dateKey].count += 1; - } - }); - - setSettledAmount(settledTotal); - - // تولید تمام روزهای بازه زمانی - const allDates = generateDateRange(startDate, now); - - // ساخت آرایه نهایی با تمام روزها (روزهای بدون فروش مقدار صفر دارند) - const result: ChartDataItem[] = allDates.map((date) => { - const existing = groupedByDate[date]; - const currentDate = new Date(date); - - return { - date: date, - amount: existing?.amount || 0, - count: existing?.count || 0, - formattedDate: currentDate.toLocaleDateString("fa-IR", { - month: "numeric", - day: "numeric", - }), - persianDate: toJalaliDate(currentDate), - jalaliDate: getJalaliDayMonth(currentDate), - }; - }); - - // محاسبه مجموع کل فروش در بازه زمانی (فقط success ها) - const total = result.reduce((sum, item) => sum + item.amount, 0); - const count = result.reduce((sum, item) => sum + item.count, 0); - const avg = count > 0 ? Math.round(total / count) : 0; - - setTotalSales(total); - setTotalCount(count); - setAverageAmount(avg); - - return result; - }; - - const fetchCoursesPayment = async () => { - setIsLoading(true); - try { - // دریافت تمام پرداخت‌ها (هم success و هم settled) - const response = await request( - "GET", - `/academy/course/getAllAcademyPayments` - ); - - console.log("تمامی پرداخت‌ها:", response?.data?.payments); - - if (response?.data?.payments && Array.isArray(response.data.payments)) { - const processedData = processPaymentsData( - response.data.payments, - timeRange - ); - setChartData(processedData); - } else { - // اگر داده‌ای نبود، یک بازه خالی با تمام روزها و مقدار صفر ایجاد کن - const now = new Date(); - const startDate = getStartDate(timeRange, now); - const allDates = generateDateRange(startDate, now); - const emptyData: ChartDataItem[] = allDates.map((date) => ({ - date: date, - amount: 0, - count: 0, - formattedDate: new Date(date).toLocaleDateString("fa-IR", { - month: "numeric", - day: "numeric", - }), - persianDate: toJalaliDate(new Date(date)), - jalaliDate: getJalaliDayMonth(new Date(date)), - })); - setChartData(emptyData); - setTotalSales(0); - setTotalCount(0); - setAverageAmount(0); - setSettledAmount(0); - } - } catch (err) { - console.log("get error:", err); - toast.error("خطا در دریافت اطلاعات"); - setChartData([]); - } finally { - setIsLoading(false); - } - }; - - useEffect(() => { - fetchCoursesPayment(); - }, []); - - useEffect(() => { - if (chartData.length > 0) { - fetchCoursesPayment(); - } - }, [timeRange]); - - if (isLoading) { - return ( - - - مقدار فروش - در حال بارگذاری داده‌ها... - - -
-
-
- ); - } - - // فرمتر محور X با تاریخ شمسی - const formatXAxis = (value: string) => { - const date = new Date(value); - return getJalaliDayMonth(date); - }; - - return ( - - -
-
- - آمار فروش آکادمی - -
- - - ۹۰ روز - ۳۰ روز - ۷ روز - - - -
-
- - {chartData.length === 0 ? ( -
-
- - - -

هیچ داده‌ای برای نمایش وجود ندارد

-

هنوز فروشی ثبت نشده است

-
-
- ) : ( - - - - - - - - - - - - - - - { - const date = new Date(value); - return toJalaliDate(date); - }} - formatter={(value, name) => { - if (name === "amount") { - return [ - `${Number(value).toLocaleString("fa-IR")}`, - "مبلغ فروش", - ]; - } - if (name === "count") { - return [`${value}`, "تعداد فروش"]; - } - return [value, name]; - }} - indicator="dot" - /> - } - /> - - - - - )} -
-
- ); -} +"use client"; + +import * as React from "react"; +import { useState, useEffect } from "react"; +import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"; + +import { useIsMobile } from "@/hooks/use-mobile"; +import { + Card, + CardAction, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; +import useAxios from "@/hooks/useAxios"; +import toast from "react-hot-toast"; + +interface Payment { + _id: string; + price: number; + course_name: string; + course_id: any; + academy_id: string; + status: string; // "success" = پرداخت نشده, "settled" = پرداخت شده + createdAt: string; + updatedAt: string; + payment_authority: string; + payment_ref_id: string; + discountAmount: number; + taxAmount: number; + taxRate: number; + user_id: any; +} + +interface ChartDataItem { + date: string; + amount: number; + count: number; + formattedDate: string; + persianDate: string; + jalaliDate: string; +} + +const chartConfig = { + amount: { + label: "مبلغ فروش", + color: "#ff107d", + }, + count: { + label: "تعداد فروش", + color: "#10b981", + }, +} satisfies ChartConfig; + +// تبدیل تاریخ میلادی به شمسی +const toJalaliDate = (date: Date): string => { + return new Intl.DateTimeFormat("fa-IR", { + year: "numeric", + month: "numeric", + day: "numeric", + }).format(date); +}; + +// نمایش ساده برای محور X +const getJalaliDayMonth = (date: Date): string => { + return new Intl.DateTimeFormat("fa-IR", { + month: "numeric", + day: "numeric", + }).format(date); +}; + +// تابع برای تولید تمام روزهای بازه زمانی +const generateDateRange = (startDate: Date, endDate: Date): string[] => { + const dates: string[] = []; + const currentDate = new Date(startDate); + + while (currentDate <= endDate) { + dates.push(currentDate.toISOString().split("T")[0]); + currentDate.setDate(currentDate.getDate() + 1); + } + + return dates; +}; + +export function ChartAreaInteractive() { + const isMobile = useIsMobile(); + const [timeRange, setTimeRange] = useState("90d"); + const [isLoading, setIsLoading] = useState(false); + const [chartData, setChartData] = useState([]); + const [totalSales, setTotalSales] = useState(0); // مجموع فروش‌های موفق (تسویه نشده) + const [totalCount, setTotalCount] = useState(0); + const [averageAmount, setAverageAmount] = useState(0); + const [settledAmount, setSettledAmount] = useState(0); // مبلغ تسویه شده (پرداخت شده) + + const { request } = useAxios(); + + useEffect(() => { + if (isMobile) { + setTimeRange("30d"); + } + }, [isMobile]); + + // محاسبه تاریخ شروع بر اساس بازه زمانی + const getStartDate = (range: string, endDate: Date): Date => { + const start = new Date(endDate); + switch (range) { + case "7d": + start.setDate(start.getDate() - 6); + break; + case "30d": + start.setDate(start.getDate() - 29); + break; + case "90d": + start.setDate(start.getDate() - 89); + break; + default: + start.setDate(start.getDate() - 89); + } + start.setHours(0, 0, 0, 0); + return start; + }; + + // پردازش داده‌ها و تکمیل روزهای بدون فروش + const processPaymentsData = (payments: Payment[], range: string) => { + const now = new Date(); + now.setHours(23, 59, 59, 999); + const startDate = getStartDate(range, now); + + // گروه‌بندی پرداخت‌های موجود بر اساس تاریخ + const groupedByDate: { [key: string]: { amount: number; count: number } } = + {}; + let settledTotal = 0; // مجموع مبالغ تسویه شده (پرداخت شده) + + payments.forEach((payment) => { + const date = new Date(payment.createdAt); + const dateKey = date.toISOString().split("T")[0]; + + // مبلغ: price یا taxAmount + const amount = payment.taxAmount || 0; + + // محاسبه مبالغ تسویه شده (status = settled) + if (payment.status === "settled") { + settledTotal += amount; + } + + // فقط پرداخت‌های داخل بازه زمانی را برای چارت در نظر بگیر + // برای چارت، فقط فروش‌های موفق (success) را نشان می‌دهیم + if (payment.status === "success" && date >= startDate && date <= now) { + if (!groupedByDate[dateKey]) { + groupedByDate[dateKey] = { amount: 0, count: 0 }; + } + groupedByDate[dateKey].amount += amount; + groupedByDate[dateKey].count += 1; + } + if (payment.status === "settled" && date >= startDate && date <= now) { + if (!groupedByDate[dateKey]) { + groupedByDate[dateKey] = { amount: 0, count: 0 }; + } + groupedByDate[dateKey].amount += amount; + groupedByDate[dateKey].count += 1; + } + }); + + setSettledAmount(settledTotal); + + // تولید تمام روزهای بازه زمانی + const allDates = generateDateRange(startDate, now); + + // ساخت آرایه نهایی با تمام روزها (روزهای بدون فروش مقدار صفر دارند) + const result: ChartDataItem[] = allDates.map((date) => { + const existing = groupedByDate[date]; + const currentDate = new Date(date); + + return { + date: date, + amount: existing?.amount || 0, + count: existing?.count || 0, + formattedDate: currentDate.toLocaleDateString("fa-IR", { + month: "numeric", + day: "numeric", + }), + persianDate: toJalaliDate(currentDate), + jalaliDate: getJalaliDayMonth(currentDate), + }; + }); + + // محاسبه مجموع کل فروش در بازه زمانی (فقط success ها) + const total = result.reduce((sum, item) => sum + item.amount, 0); + const count = result.reduce((sum, item) => sum + item.count, 0); + const avg = count > 0 ? Math.round(total / count) : 0; + + setTotalSales(total); + setTotalCount(count); + setAverageAmount(avg); + + return result; + }; + + const fetchCoursesPayment = async () => { + setIsLoading(true); + try { + // دریافت تمام پرداخت‌ها (هم success و هم settled) + const response = await request( + "GET", + `/academy/course/getAllAcademyPayments` + ); + + console.log("تمامی پرداخت‌ها:", response?.data?.payments); + + if (response?.data?.payments && Array.isArray(response.data.payments)) { + const processedData = processPaymentsData( + response.data.payments, + timeRange + ); + setChartData(processedData); + } else { + // اگر داده‌ای نبود، یک بازه خالی با تمام روزها و مقدار صفر ایجاد کن + const now = new Date(); + const startDate = getStartDate(timeRange, now); + const allDates = generateDateRange(startDate, now); + const emptyData: ChartDataItem[] = allDates.map((date) => ({ + date: date, + amount: 0, + count: 0, + formattedDate: new Date(date).toLocaleDateString("fa-IR", { + month: "numeric", + day: "numeric", + }), + persianDate: toJalaliDate(new Date(date)), + jalaliDate: getJalaliDayMonth(new Date(date)), + })); + setChartData(emptyData); + setTotalSales(0); + setTotalCount(0); + setAverageAmount(0); + setSettledAmount(0); + } + } catch (err) { + console.log("get error:", err); + toast.error("خطا در دریافت اطلاعات"); + setChartData([]); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchCoursesPayment(); + }, []); + + useEffect(() => { + if (chartData.length > 0) { + fetchCoursesPayment(); + } + }, [timeRange]); + + if (isLoading) { + return ( + + + مقدار فروش + در حال بارگذاری داده‌ها... + + +
+
+
+ ); + } + + // فرمتر محور X با تاریخ شمسی + const formatXAxis = (value: string) => { + const date = new Date(value); + return getJalaliDayMonth(date); + }; + + return ( + + +
+
+ + آمار فروش آکادمی + +
+ + + ۹۰ روز + ۳۰ روز + ۷ روز + + + +
+
+ + {chartData.length === 0 ? ( +
+
+ + + +

هیچ داده‌ای برای نمایش وجود ندارد

+

هنوز فروشی ثبت نشده است

+
+
+ ) : ( + + + + + + + + + + + + + + + { + const date = new Date(value); + return toJalaliDate(date); + }} + formatter={(value, name) => { + if (name === "amount") { + return [ + `${Number(value).toLocaleString("fa-IR")}`, + "مبلغ فروش", + ]; + } + if (name === "count") { + return [`${value}`, "تعداد فروش"]; + } + return [value, name]; + }} + indicator="dot" + /> + } + /> + + + + + )} +
+
+ ); +} diff --git a/src/components/chat/ChatHeader.tsx b/src/components/chat/ChatHeader.tsx index 5b91735..33cb5fb 100644 --- a/src/components/chat/ChatHeader.tsx +++ b/src/components/chat/ChatHeader.tsx @@ -5,7 +5,7 @@ import Image from "next/image"; import Link from "next/link"; import { useState } from "react"; import { FiChevronRight } from "react-icons/fi"; -import { IMAGE_BASE_URL } from "../main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "../main/BaseUrl"; import { useRouter } from "next/navigation"; import ChatHeaderMenu from "./ChatHeaderMenu"; @@ -46,7 +46,7 @@ function ChatHeader({ width={40} height={40} alt="" - src={IMAGE_BASE_URL + user.profile_image} + src={buildStorageUrl(user.profile_image)} className="h-10 w-10 shrink-0 rounded-full object-cover" /> ) : ( diff --git a/src/components/chat/ChatMessageCard.tsx b/src/components/chat/ChatMessageCard.tsx index 31ae52a..392a82b 100644 --- a/src/components/chat/ChatMessageCard.tsx +++ b/src/components/chat/ChatMessageCard.tsx @@ -5,7 +5,7 @@ import { useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; import Link from "next/link"; import { User } from "@/types/types"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import ReadReceipt from "./ReadReceipt"; import VoiceMessagePlayer from "./VoiceMessagePlayer"; import FileMessageBubble from "./FileMessageBubble"; diff --git a/src/components/chat/ForwardMessageModal.tsx b/src/components/chat/ForwardMessageModal.tsx index 08382d4..07c308c 100644 --- a/src/components/chat/ForwardMessageModal.tsx +++ b/src/components/chat/ForwardMessageModal.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; import Image from "next/image"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import useAxios from "@/hooks/useAxios"; import toast from "react-hot-toast"; import IOSSpinner from "@/components/ui/IOSSpinner"; @@ -147,7 +147,7 @@ export default function ForwardMessageModal({ > {u.profile_image ? ( - - Drag to reorder - - ) -} - -const columns: ColumnDef>[] = [ - { - id: "drag", - header: () => null, - cell: ({ row }) => , - }, - { - id: "select", - header: ({ table }) => ( -
- table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - /> -
- ), - cell: ({ row }) => ( -
- row.toggleSelected(!!value)} - aria-label="Select row" - /> -
- ), - enableSorting: false, - enableHiding: false, - }, - { - accessorKey: "header", - header: "Header", - cell: ({ row }) => { - return - }, - enableHiding: false, - }, - { - accessorKey: "type", - header: "Section Type", - cell: ({ row }) => ( -
- - {row.original.type} - -
- ), - }, - { - accessorKey: "status", - header: "Status", - cell: ({ row }) => ( - - {row.original.status === "Done" ? ( - - ) : ( - - )} - {row.original.status} - - ), - }, - { - accessorKey: "target", - header: () =>
Target
, - cell: ({ row }) => ( -
{ - e.preventDefault() - toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { - loading: `Saving ${row.original.header}`, - success: "Done", - error: "Error", - }) - }} - > - - -
- ), - }, - { - accessorKey: "limit", - header: () =>
Limit
, - cell: ({ row }) => ( -
{ - e.preventDefault() - toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { - loading: `Saving ${row.original.header}`, - success: "Done", - error: "Error", - }) - }} - > - - -
- ), - }, - { - accessorKey: "reviewer", - header: "Reviewer", - cell: ({ row }) => { - const isAssigned = row.original.reviewer !== "Assign reviewer" - - if (isAssigned) { - return row.original.reviewer - } - - return ( - <> - - - - ) - }, - }, - { - id: "actions", - cell: () => ( - - - - - - Edit - Make a copy - Favorite - - Delete - - - ), - }, -] - -function DraggableRow({ row }: { row: Row> }) { - const { transform, transition, setNodeRef, isDragging } = useSortable({ - id: row.original.id, - }) - - return ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ) -} - -export function DataTable({ - data: initialData, -}: { - data: z.infer[] -}) { - const [data, setData] = React.useState(() => initialData) - const [rowSelection, setRowSelection] = React.useState({}) - const [columnVisibility, setColumnVisibility] = - React.useState({}) - const [columnFilters, setColumnFilters] = React.useState( - [] - ) - const [sorting, setSorting] = React.useState([]) - const [pagination, setPagination] = React.useState({ - pageIndex: 0, - pageSize: 10, - }) - const sortableId = React.useId() - const sensors = useSensors( - useSensor(MouseSensor, {}), - useSensor(TouchSensor, {}), - useSensor(KeyboardSensor, {}) - ) - - const dataIds = React.useMemo( - () => data?.map(({ id }) => id) || [], - [data] - ) - - const table = useReactTable({ - data, - columns, - state: { - sorting, - columnVisibility, - rowSelection, - columnFilters, - pagination, - }, - getRowId: (row) => row.id.toString(), - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - onPaginationChange: setPagination, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - }) - - function handleDragEnd(event: DragEndEvent) { - const { active, over } = event - if (active && over && active.id !== over.id) { - setData((data) => { - const oldIndex = dataIds.indexOf(active.id) - const newIndex = dataIds.indexOf(over.id) - return arrayMove(data, oldIndex, newIndex) - }) - } - } - - return ( - -
- - - - Outline - - Past Performance 3 - - - Key Personnel 2 - - Focus Documents - -
- - - - - - {table - .getAllColumns() - .filter( - (column) => - typeof column.accessorFn !== "undefined" && - column.getCanHide() - ) - .map((column) => { - return ( - - column.toggleVisibility(!!value) - } - > - {column.id} - - ) - })} - - - -
-
- -
- - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ) - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - - {table.getRowModel().rows.map((row) => ( - - ))} - - ) : ( - - - No results. - - - )} - -
-
-
-
-
- {table.getFilteredSelectedRowModel().rows.length} of{" "} - {table.getFilteredRowModel().rows.length} row(s) selected. -
-
-
- - -
-
- Page {table.getState().pagination.pageIndex + 1} of{" "} - {table.getPageCount()} -
-
- - - - -
-
-
-
- -
-
- -
-
- -
-
-
- ) -} - -const chartData = [ - { month: "January", desktop: 186, mobile: 80 }, - { month: "February", desktop: 305, mobile: 200 }, - { month: "March", desktop: 237, mobile: 120 }, - { month: "April", desktop: 73, mobile: 190 }, - { month: "May", desktop: 209, mobile: 130 }, - { month: "June", desktop: 214, mobile: 140 }, -] - -const chartConfig = { - desktop: { - label: "Desktop", - color: "var(--primary)", - }, - mobile: { - label: "Mobile", - color: "var(--primary)", - }, -} satisfies ChartConfig - -function TableCellViewer({ item }: { item: z.infer }) { - const isMobile = useIsMobile() - - return ( - - - - - - - {item.header} - - Showing total visitors for the last 6 months - - -
- {!isMobile && ( - <> - - - - value.slice(0, 3)} - hide - /> - } - /> - - - - - -
-
- Trending up by 5.2% this month{" "} - -
-
- Showing total visitors for the last 6 months. This is just - some random text to test the layout. It spans multiple lines - and should wrap around. -
-
- - - )} -
-
- - -
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - -
-
-
- - - - - - -
-
- ) -} +"use client" + +import * as React from "react" +import { + closestCenter, + DndContext, + KeyboardSensor, + MouseSensor, + TouchSensor, + useSensor, + useSensors, + type DragEndEvent, + type UniqueIdentifier, +} from "@dnd-kit/core" +import { restrictToVerticalAxis } from "@dnd-kit/modifiers" +import { + arrayMove, + SortableContext, + useSortable, + verticalListSortingStrategy, +} from "@dnd-kit/sortable" +import { CSS } from "@dnd-kit/utilities" +import { + IconChevronDown, + IconChevronLeft, + IconChevronRight, + IconChevronsLeft, + IconChevronsRight, + IconCircleCheckFilled, + IconDotsVertical, + IconGripVertical, + IconLayoutColumns, + IconLoader, + IconPlus, + IconTrendingUp, +} from "@tabler/icons-react" +import { + ColumnDef, + ColumnFiltersState, + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + Row, + SortingState, + useReactTable, + VisibilityState, +} from "@tanstack/react-table" +import { Area, AreaChart, CartesianGrid, XAxis } from "recharts" +import { toast } from "sonner" +import { z } from "zod" + +import { useIsMobile } from "@/hooks/use-mobile" +import { Badge } from "@/components/ui/badge" +import { Button } from "./ui/button" +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart" +import { Checkbox } from "@/components/ui/checkbox" +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer" +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Input } from "./ui/input" +import { Label } from "./ui/label" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Separator } from "@/components/ui/separator" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs" + +export const schema = z.object({ + id: z.number(), + header: z.string(), + type: z.string(), + status: z.string(), + target: z.string(), + limit: z.string(), + reviewer: z.string(), +}) + +// Create a separate component for the drag handle +function DragHandle({ id }: { id: number }) { + const { attributes, listeners } = useSortable({ + id, + }) + + return ( + + ) +} + +const columns: ColumnDef>[] = [ + { + id: "drag", + header: () => null, + cell: ({ row }) => , + }, + { + id: "select", + header: ({ table }) => ( +
+ table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> +
+ ), + cell: ({ row }) => ( +
+ row.toggleSelected(!!value)} + aria-label="Select row" + /> +
+ ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "header", + header: "Header", + cell: ({ row }) => { + return + }, + enableHiding: false, + }, + { + accessorKey: "type", + header: "Section Type", + cell: ({ row }) => ( +
+ + {row.original.type} + +
+ ), + }, + { + accessorKey: "status", + header: "Status", + cell: ({ row }) => ( + + {row.original.status === "Done" ? ( + + ) : ( + + )} + {row.original.status} + + ), + }, + { + accessorKey: "target", + header: () =>
Target
, + cell: ({ row }) => ( +
{ + e.preventDefault() + toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { + loading: `Saving ${row.original.header}`, + success: "Done", + error: "Error", + }) + }} + > + + +
+ ), + }, + { + accessorKey: "limit", + header: () =>
Limit
, + cell: ({ row }) => ( +
{ + e.preventDefault() + toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { + loading: `Saving ${row.original.header}`, + success: "Done", + error: "Error", + }) + }} + > + + +
+ ), + }, + { + accessorKey: "reviewer", + header: "Reviewer", + cell: ({ row }) => { + const isAssigned = row.original.reviewer !== "Assign reviewer" + + if (isAssigned) { + return row.original.reviewer + } + + return ( + <> + + + + ) + }, + }, + { + id: "actions", + cell: () => ( + + + + + + Edit + Make a copy + Favorite + + Delete + + + ), + }, +] + +function DraggableRow({ row }: { row: Row> }) { + const { transform, transition, setNodeRef, isDragging } = useSortable({ + id: row.original.id, + }) + + return ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ) +} + +export function DataTable({ + data: initialData, +}: { + data: z.infer[] +}) { + const [data, setData] = React.useState(() => initialData) + const [rowSelection, setRowSelection] = React.useState({}) + const [columnVisibility, setColumnVisibility] = + React.useState({}) + const [columnFilters, setColumnFilters] = React.useState( + [] + ) + const [sorting, setSorting] = React.useState([]) + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 10, + }) + const sortableId = React.useId() + const sensors = useSensors( + useSensor(MouseSensor, {}), + useSensor(TouchSensor, {}), + useSensor(KeyboardSensor, {}) + ) + + const dataIds = React.useMemo( + () => data?.map(({ id }) => id) || [], + [data] + ) + + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnVisibility, + rowSelection, + columnFilters, + pagination, + }, + getRowId: (row) => row.id.toString(), + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + onPaginationChange: setPagination, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + }) + + function handleDragEnd(event: DragEndEvent) { + const { active, over } = event + if (active && over && active.id !== over.id) { + setData((data) => { + const oldIndex = dataIds.indexOf(active.id) + const newIndex = dataIds.indexOf(over.id) + return arrayMove(data, oldIndex, newIndex) + }) + } + } + + return ( + +
+ + + + Outline + + Past Performance 3 + + + Key Personnel 2 + + Focus Documents + +
+ + + + + + {table + .getAllColumns() + .filter( + (column) => + typeof column.accessorFn !== "undefined" && + column.getCanHide() + ) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ) + })} + + + +
+
+ +
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + + {table.getRowModel().rows.map((row) => ( + + ))} + + ) : ( + + + No results. + + + )} + +
+
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+
+ + +
+
+ Page {table.getState().pagination.pageIndex + 1} of{" "} + {table.getPageCount()} +
+
+ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ ) +} + +const chartData = [ + { month: "January", desktop: 186, mobile: 80 }, + { month: "February", desktop: 305, mobile: 200 }, + { month: "March", desktop: 237, mobile: 120 }, + { month: "April", desktop: 73, mobile: 190 }, + { month: "May", desktop: 209, mobile: 130 }, + { month: "June", desktop: 214, mobile: 140 }, +] + +const chartConfig = { + desktop: { + label: "Desktop", + color: "var(--primary)", + }, + mobile: { + label: "Mobile", + color: "var(--primary)", + }, +} satisfies ChartConfig + +function TableCellViewer({ item }: { item: z.infer }) { + const isMobile = useIsMobile() + + return ( + + + + + + + {item.header} + + Showing total visitors for the last 6 months + + +
+ {!isMobile && ( + <> + + + + value.slice(0, 3)} + hide + /> + } + /> + + + + + +
+
+ Trending up by 5.2% this month{" "} + +
+
+ Showing total visitors for the last 6 months. This is just + some random text to test the layout. It spans multiple lines + and should wrap around. +
+
+ + + )} +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + + + + + +
+
+ ) +} diff --git a/src/components/explore/ExploreGrid.tsx b/src/components/explore/ExploreGrid.tsx index abad38f..37c256e 100644 --- a/src/components/explore/ExploreGrid.tsx +++ b/src/components/explore/ExploreGrid.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { useInfiniteQuery } from "@tanstack/react-query"; import { fetchPosts } from "@/api/fetchPosts"; import { Post } from "@/types/types"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import Cookies from "js-cookie"; import { useCallback, useEffect } from "react"; import PageLoader from "@/components/ui/PageLoader"; diff --git a/src/components/main/BaseUrl.ts b/src/components/main/BaseUrl.ts index 072aca3..e678633 100644 --- a/src/components/main/BaseUrl.ts +++ b/src/components/main/BaseUrl.ts @@ -2,6 +2,24 @@ // export const BASE_URL = "http://localhost:8000/api/v1"; // export const IMAGE_BASE_URL = "http://localhost:8000/storage"; -export const SOCKET_URL = "https://app.modstagram.ir"; -export const BASE_URL = "https://app.modstagram.ir/api/v1"; -export const IMAGE_BASE_URL = "https://app.modstagram.ir/storage"; \ No newline at end of file +export const SOCKET_URL = "https://api.modstagram.com"; +export const BASE_URL = "https://api.modstagram.com/api/v1"; +export const IMAGE_BASE_URL = "https://api.modstagram.com/storage"; + +/** ساخت URL کامل فایل storage — مسیرهای DB را یکسان می‌کند */ +export function buildStorageUrl(path: string | null | undefined): string { + if (!path) return ""; + if (path.startsWith("http://") || path.startsWith("https://")) return path; + + let normalized = String(path).replace(/\\/g, "/"); + normalized = normalized.replace("/root/modstagram-back/storage", ""); + + if (normalized.startsWith("/storage/")) { + normalized = normalized.slice("/storage".length); + } else if (normalized.startsWith("storage/")) { + normalized = `/${normalized.slice("storage/".length)}`; + } + + if (!normalized.startsWith("/")) normalized = `/${normalized}`; + return `${IMAGE_BASE_URL.replace(/\/$/, "")}${normalized}`; +} diff --git a/src/components/main/PrivacyPolicyModal.tsx b/src/components/main/PrivacyPolicyModal.tsx index 7c1881f..2b0b730 100644 --- a/src/components/main/PrivacyPolicyModal.tsx +++ b/src/components/main/PrivacyPolicyModal.tsx @@ -1,69 +1,69 @@ -"use client"; - -import React, { useEffect, useState } from "react"; -import Cookies from "js-cookie"; -import Modal from "../elements/Modal"; -import RoundedButton from "../elements/RoundedButton"; -import { Checkbox } from "@/components/ui/checkbox"; - -const STORAGE_KEY = "privacyPolicyDismissed"; - -function PrivacyPolicyModal() { - const [isOpen, setIsOpen] = useState(false); - const [dontShowAgain, setDontShowAgain] = useState(false); - - useEffect(() => { - const token = Cookies.get("token"); - if (!token) return; - - const dismissed = - typeof window !== "undefined" && - localStorage.getItem(STORAGE_KEY) === "true"; - - if (!dismissed) { - setIsOpen(true); - } - }, []); - - const handleConfirm = () => { - if (dontShowAgain) { - localStorage.setItem(STORAGE_KEY, "true"); - } - setIsOpen(false); - }; - - return ( - -
-

- سیاست حفظ حریم‌خصوصی -

-

- کاربر گرامی پلتفرم مدستاگرام به منظور خدمات رسانی هرچه بیشتر و کارایی - بالاتر نیازمند دسترسی های فوق می باشد. دسترسی هایی نظیر لوکیشن و صدا و - فایل و عکس و دوربین برای استفاده بهتر در قسمت پیامرسان پلتفرم می باشد - و مجموعه مدستاگرام متعهد می گردد در حفظ و نگهداری اطلاعات کاربران - کوشا باشد. و به هیچ عنوان از این اطلاعات سو استفاده نخواهد شد و در - اختیار شخص ثالث قرار نخواهد گرفت. -

- - - تایید - -
-
- ); -} - -export default PrivacyPolicyModal; +"use client"; + +import React, { useEffect, useState } from "react"; +import Cookies from "js-cookie"; +import Modal from "../elements/Modal"; +import RoundedButton from "../elements/RoundedButton"; +import { Checkbox } from "@/components/ui/checkbox"; + +const STORAGE_KEY = "privacyPolicyDismissed"; + +function PrivacyPolicyModal() { + const [isOpen, setIsOpen] = useState(false); + const [dontShowAgain, setDontShowAgain] = useState(false); + + useEffect(() => { + const token = Cookies.get("token"); + if (!token) return; + + const dismissed = + typeof window !== "undefined" && + localStorage.getItem(STORAGE_KEY) === "true"; + + if (!dismissed) { + setIsOpen(true); + } + }, []); + + const handleConfirm = () => { + if (dontShowAgain) { + localStorage.setItem(STORAGE_KEY, "true"); + } + setIsOpen(false); + }; + + return ( + +
+

+ سیاست حفظ حریم‌خصوصی +

+

+ کاربر گرامی پلتفرم مدستاگرام به منظور خدمات رسانی هرچه بیشتر و کارایی + بالاتر نیازمند دسترسی های فوق می باشد. دسترسی هایی نظیر لوکیشن و صدا و + فایل و عکس و دوربین برای استفاده بهتر در قسمت پیامرسان پلتفرم می باشد + و مجموعه مدستاگرام متعهد می گردد در حفظ و نگهداری اطلاعات کاربران + کوشا باشد. و به هیچ عنوان از این اطلاعات سو استفاده نخواهد شد و در + اختیار شخص ثالث قرار نخواهد گرفت. +

+ + + تایید + +
+
+ ); +} + +export default PrivacyPolicyModal; diff --git a/src/components/main/Services/AddServiceModal.tsx b/src/components/main/Services/AddServiceModal.tsx index 452a786..1461b6f 100644 --- a/src/components/main/Services/AddServiceModal.tsx +++ b/src/components/main/Services/AddServiceModal.tsx @@ -4,7 +4,7 @@ import AuthNextButton from "@/components/auth/AuthNextButton"; import Modal from "@/components/elements/Modal"; import { Service } from "@/types/types"; import React, { useState } from "react"; -import { IMAGE_BASE_URL } from "../BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "../BaseUrl"; import Image from "next/image"; import toast from "react-hot-toast"; import { v4 as uuidv4 } from "uuid"; // ✅ استفاده از uuid diff --git a/src/components/main/Services/ServiceItem.tsx b/src/components/main/Services/ServiceItem.tsx index de0aef3..a4d0431 100644 --- a/src/components/main/Services/ServiceItem.tsx +++ b/src/components/main/Services/ServiceItem.tsx @@ -3,7 +3,7 @@ import { Service } from "@/types/types"; import Image from "next/image"; import React from "react"; -import { IMAGE_BASE_URL } from "../BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "../BaseUrl"; interface ServiceItemProps { service: Service; diff --git a/src/components/main/UserInfo.tsx b/src/components/main/UserInfo.tsx index 7dcc7b5..1b8b046 100644 --- a/src/components/main/UserInfo.tsx +++ b/src/components/main/UserInfo.tsx @@ -1,6 +1,6 @@ import Image from "next/image"; import React from "react"; -import { IMAGE_BASE_URL } from "./BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "./BaseUrl"; import Link from "next/link"; interface IUserInfoProps { @@ -31,7 +31,7 @@ function UserInfo({ width={75} height={75} alt={user_name || ""} - src={`${IMAGE_BASE_URL}${profile_image}`} + src={buildStorageUrl(profile_image)} unoptimized={true} /> ) : ( @@ -52,7 +52,7 @@ function UserInfo({ width={75} height={75} alt={user_name || ""} - src={`${IMAGE_BASE_URL}${profile_image}`} + src={buildStorageUrl(profile_image)} unoptimized={true} /> ) : ( diff --git a/src/components/models/MainModelCard/CommentsModal.tsx b/src/components/models/MainModelCard/CommentsModal.tsx index e7182b3..0921aac 100644 --- a/src/components/models/MainModelCard/CommentsModal.tsx +++ b/src/components/models/MainModelCard/CommentsModal.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"; import Modal from "../../elements/Modal"; import Image from "next/image"; import useInfiniteScroll from "@/hooks/useInfiniteScroll"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import RoundedInput from "@/components/elements/RoundedInput"; import useAxios from "@/hooks/useAxios"; import toast from "react-hot-toast"; @@ -124,7 +124,7 @@ function CommentsModal({ isOpen, onClose, userId, postId }: CommentsModalProps) width={50} height={50} alt={item?.user?.user_name} - src={`${IMAGE_BASE_URL}${item?.user?.profile_image}`} + src={buildStorageUrl(item?.user?.profile_image)} unoptimized />
diff --git a/src/components/models/MainModelCard/MainModelCard.tsx b/src/components/models/MainModelCard/MainModelCard.tsx index 790d644..a538050 100644 --- a/src/components/models/MainModelCard/MainModelCard.tsx +++ b/src/components/models/MainModelCard/MainModelCard.tsx @@ -11,7 +11,7 @@ import { Post } from "@/types/types"; import Image from "next/image"; import MainModelCardActions from "./MainModelCardActions"; import Link from "next/link"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import { useUserById } from "@/hooks/getUserById"; import "slick-carousel/slick/slick.css"; import "slick-carousel/slick/slick-theme.css"; @@ -170,13 +170,14 @@ function MainModelCard({ postData }: { postData: Post }) { {user_name}
diff --git a/src/components/models/ModelPage/MainModelCardPost.tsx b/src/components/models/ModelPage/MainModelCardPost.tsx index e61030b..8049e81 100644 --- a/src/components/models/ModelPage/MainModelCardPost.tsx +++ b/src/components/models/ModelPage/MainModelCardPost.tsx @@ -5,7 +5,7 @@ import { Post } from "@/types/types"; import Image from "next/image"; import MainModelCardActions from "../MainModelCard/MainModelCardActions"; import Link from "next/link"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import { useUserById } from "@/hooks/getUserById"; import Slider from "react-slick"; import "slick-carousel/slick/slick.css"; @@ -169,7 +169,7 @@ function MainModelCard({ postData }: { postData: Post }) { {user_name} ) : ( diff --git a/src/components/nav-secondary.tsx b/src/components/nav-secondary.tsx index 3f3636f..8f9e8a0 100644 --- a/src/components/nav-secondary.tsx +++ b/src/components/nav-secondary.tsx @@ -1,42 +1,42 @@ -"use client" - -import * as React from "react" -import { type Icon } from "@tabler/icons-react" - -import { - SidebarGroup, - SidebarGroupContent, - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, -} from "@/components/ui/sidebar" - -export function NavSecondary({ - items, - ...props -}: { - items: { - title: string - url: string - icon: Icon - }[] -} & React.ComponentPropsWithoutRef) { - return ( - - - - {items.map((item) => ( - - - - - {item.title} - - - - ))} - - - - ) -} +"use client" + +import * as React from "react" +import { type Icon } from "@tabler/icons-react" + +import { + SidebarGroup, + SidebarGroupContent, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" + +export function NavSecondary({ + items, + ...props +}: { + items: { + title: string + url: string + icon: Icon + }[] +} & React.ComponentPropsWithoutRef) { + return ( + + + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + + + + ) +} diff --git a/src/components/posts/TagUsersPicker.tsx b/src/components/posts/TagUsersPicker.tsx index 5549c11..1b4b221 100644 --- a/src/components/posts/TagUsersPicker.tsx +++ b/src/components/posts/TagUsersPicker.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import Image from "next/image"; import useAxios from "@/hooks/useAxios"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import { FiUserPlus, FiX } from "react-icons/fi"; import toast from "react-hot-toast"; @@ -111,7 +111,7 @@ export default function TagUsersPicker({ > {u.profile_image ? ( )} diff --git a/src/components/section-cards.tsx b/src/components/section-cards.tsx index 235ed7e..23b6912 100644 --- a/src/components/section-cards.tsx +++ b/src/components/section-cards.tsx @@ -1,201 +1,201 @@ -"use client" -import { IconTrendingUp } from "@tabler/icons-react" - -import { Badge } from "@/components/ui/badge" -import { - Card, - CardAction, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card" -import { useTranslation } from "react-i18next" -import { useEffect, useState } from "react" -import useAxios from "@/hooks/useAxios" -import toast from "react-hot-toast" - -export function SectionCards() { - const [totalCourses, setTotalCourses] = useState(0); - const [totalSell, setTotalSell] = useState(0); - const [totalSettledSell, setTotalSettledSell] = useState(0); - const [totalSettled, setTotalSettled] = useState(0); - const [totalPrice, setTotalPrice] = useState(0); - const [isLoading, setIsLoading] = useState(false); - const { request, loading, error } = useAxios(); - - const fetchCourses = async (page = 1) => { - setIsLoading(true); - try { - const response = await request( - "GET", - `/academy/academy/get/course` - ); - - console.log("پاسخ سرور:", response); - - // توجه: با توجه به ساختار دیتای شما - if (response?.data?.courses) { - - setTotalCourses(response.data.pagination?.totalItems || 0); - - } else if (response?.courses) { - - setTotalCourses(response.pagination?.totalItems || 0); - - } else { - console.error("ساختار پاسخ نامعتبر:", response); - toast.error("خطا در ساختار داده دریافت شده"); - } - } catch (err) { - console.log("get error:", err); - toast.error("خطا در دریافت اطلاعات"); - } finally { - setIsLoading(false); - } - }; - const fetchCoursesPayment = async (page = 1) => { - setIsLoading(true); - try { - const response = await request( - "GET", - `/academy/course/getAllAcademyPayments?status=success` - ); - - console.log("پاسخ سرور:", response); - - setTotalPrice(response.data.stats.totalAmount) - setTotalSell(response.data.payments.length) - - console.log(response.data.payments); - - - - } catch (err) { - console.log("get error:", err); - toast.error("خطا در دریافت اطلاعات"); - } finally { - setIsLoading(false); - } - }; - const fetchCoursesPayment2 = async (page = 1) => { - setIsLoading(true); - try { - const response = await request( - "GET", - `/academy/course/getAllAcademyPayments?status=settled` - ); - - console.log("پاسخ سرور:", response); - - setTotalSettled(response.data.stats.totalAmount) - setTotalSettledSell(response.data.payments.length) - - - } catch (err) { - console.log("get error:", err); - toast.error("خطا در دریافت اطلاعات"); - } finally { - setIsLoading(false); - } - }; - - useEffect(() => { - fetchCourses(1); - fetchCoursesPayment(1); - fetchCoursesPayment2(1); - }, []); - return ( -
- - - {"مبلغ کل فروش"} - - - {(totalPrice + totalSettled).toLocaleString('fa-IR', { - minimumFractionDigits: 0, - maximumFractionDigits: 2 - })} تومان - - - {/* - - - +12.5% - - */} - - -
- {"کسب درامد"} -
-
- {"مبلغ کل فروش شما در این ماه"} -
-
-
- - - {"تعداد پکیج ها"} - - {totalCourses} - - - - -
- {"پکیج های خود را افزایش دهید"} {/* */} -
-
- {"درامدی بیشتر با پکیج های بیشتر"} -
-
-
- - - {"پکیج فروخته شده"} - - {totalSell + totalSettledSell} - - {/* - - - +12.5% - - */} - - -
- {"پکیج های فروخته شده در این ماه"} -
-
{"با اراعه پکیج های کامل بیشتر بفروشید"}
-
-
- - - {"مبلغ تسویه نشده"} - - - {(totalPrice).toLocaleString('fa-IR', { - minimumFractionDigits: 0, - maximumFractionDigits: 2 - })} تومان - - - {/* - - - +4.5% - - */} - - -
- {"مقدار بدهیه مدستاگرام به شما"} -
-
{"هر پکیج فروخته شده تا 15 روز بعد تسویه میشود"}
-
-
-
- ) -} +"use client" +import { IconTrendingUp } from "@tabler/icons-react" + +import { Badge } from "@/components/ui/badge" +import { + Card, + CardAction, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { useTranslation } from "react-i18next" +import { useEffect, useState } from "react" +import useAxios from "@/hooks/useAxios" +import toast from "react-hot-toast" + +export function SectionCards() { + const [totalCourses, setTotalCourses] = useState(0); + const [totalSell, setTotalSell] = useState(0); + const [totalSettledSell, setTotalSettledSell] = useState(0); + const [totalSettled, setTotalSettled] = useState(0); + const [totalPrice, setTotalPrice] = useState(0); + const [isLoading, setIsLoading] = useState(false); + const { request, loading, error } = useAxios(); + + const fetchCourses = async (page = 1) => { + setIsLoading(true); + try { + const response = await request( + "GET", + `/academy/academy/get/course` + ); + + console.log("پاسخ سرور:", response); + + // توجه: با توجه به ساختار دیتای شما + if (response?.data?.courses) { + + setTotalCourses(response.data.pagination?.totalItems || 0); + + } else if (response?.courses) { + + setTotalCourses(response.pagination?.totalItems || 0); + + } else { + console.error("ساختار پاسخ نامعتبر:", response); + toast.error("خطا در ساختار داده دریافت شده"); + } + } catch (err) { + console.log("get error:", err); + toast.error("خطا در دریافت اطلاعات"); + } finally { + setIsLoading(false); + } + }; + const fetchCoursesPayment = async (page = 1) => { + setIsLoading(true); + try { + const response = await request( + "GET", + `/academy/course/getAllAcademyPayments?status=success` + ); + + console.log("پاسخ سرور:", response); + + setTotalPrice(response.data.stats.totalAmount) + setTotalSell(response.data.payments.length) + + console.log(response.data.payments); + + + + } catch (err) { + console.log("get error:", err); + toast.error("خطا در دریافت اطلاعات"); + } finally { + setIsLoading(false); + } + }; + const fetchCoursesPayment2 = async (page = 1) => { + setIsLoading(true); + try { + const response = await request( + "GET", + `/academy/course/getAllAcademyPayments?status=settled` + ); + + console.log("پاسخ سرور:", response); + + setTotalSettled(response.data.stats.totalAmount) + setTotalSettledSell(response.data.payments.length) + + + } catch (err) { + console.log("get error:", err); + toast.error("خطا در دریافت اطلاعات"); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchCourses(1); + fetchCoursesPayment(1); + fetchCoursesPayment2(1); + }, []); + return ( +
+ + + {"مبلغ کل فروش"} + + + {(totalPrice + totalSettled).toLocaleString('fa-IR', { + minimumFractionDigits: 0, + maximumFractionDigits: 2 + })} تومان + + + {/* + + + +12.5% + + */} + + +
+ {"کسب درامد"} +
+
+ {"مبلغ کل فروش شما در این ماه"} +
+
+
+ + + {"تعداد پکیج ها"} + + {totalCourses} + + + + +
+ {"پکیج های خود را افزایش دهید"} {/* */} +
+
+ {"درامدی بیشتر با پکیج های بیشتر"} +
+
+
+ + + {"پکیج فروخته شده"} + + {totalSell + totalSettledSell} + + {/* + + + +12.5% + + */} + + +
+ {"پکیج های فروخته شده در این ماه"} +
+
{"با اراعه پکیج های کامل بیشتر بفروشید"}
+
+
+ + + {"مبلغ تسویه نشده"} + + + {(totalPrice).toLocaleString('fa-IR', { + minimumFractionDigits: 0, + maximumFractionDigits: 2 + })} تومان + + + {/* + + + +4.5% + + */} + + +
+ {"مقدار بدهیه مدستاگرام به شما"} +
+
{"هر پکیج فروخته شده تا 15 روز بعد تسویه میشود"}
+
+
+
+ ) +} diff --git a/src/components/settings/UserDetails.tsx b/src/components/settings/UserDetails.tsx index 74e431a..3eae01e 100644 --- a/src/components/settings/UserDetails.tsx +++ b/src/components/settings/UserDetails.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; import React, { useEffect, useState } from "react"; -import { IMAGE_BASE_URL } from "../main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "../main/BaseUrl"; import CompleteRegister from "./settings/CompleteRegister"; import Link from "next/link"; import { useUser } from "@/hooks/useUser"; @@ -29,7 +29,7 @@ function UserDetails() { width={120} height={120} alt={user?.user_name || "user profile"} - src={`${IMAGE_BASE_URL}${user?.profile_image}`} + src={buildStorageUrl(user?.profile_image)} unoptimized={true} />
diff --git a/src/components/settings/my-billboards/Profile/AvatarUploader.tsx b/src/components/settings/my-billboards/Profile/AvatarUploader.tsx index 0ebc8fd..fd40e52 100644 --- a/src/components/settings/my-billboards/Profile/AvatarUploader.tsx +++ b/src/components/settings/my-billboards/Profile/AvatarUploader.tsx @@ -1,7 +1,7 @@ // components/AvatarUploader.tsx import React from "react"; import Image from "next/image"; -import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; interface AvatarUploaderProps { avatar: string | null; diff --git a/src/components/ui/Textarea.tsx b/src/components/ui/Textarea.tsx index 7f21b5e..6d460e7 100644 --- a/src/components/ui/Textarea.tsx +++ b/src/components/ui/Textarea.tsx @@ -1,18 +1,18 @@ -import * as React from "react" - -import { cn } from "@/lib/utils" - -function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { - return ( -