fix(images): resolve relative backend image URLs to absolute

API مسیرهای تصویر را نسبی برمی‌گرداند (/uploads/...) که next/image روی
دامنه localhost می‌جست و 404 می‌داد. helper جدید imageUrl مسیرهای نسبی
uploads را با NEXT_PUBLIC_API_URL کامل می‌کند (absolute و asset محلی
دست‌نخورده). اعمال روی لیست/جزئیات پزشک، نوبت، گالری کلینیک، آواتارها.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-15 00:44:27 +03:30
co-authored by Claude Opus 4.8
parent 05cf5fc585
commit e6570e06f6
10 changed files with 30 additions and 9 deletions
+2 -1
View File
@@ -5,6 +5,7 @@ import Link from "next/link";
import CustomLoading from "./loading/Custom";
import TextLoading from "./loading/Text";
import CircularLoading from "./loading/Circular";
import { imageUrl } from "@/helper";
// Icons
import ArrowLeftD from "@/components/icons/ArrowLeftD";
@@ -32,7 +33,7 @@ function ItemDoctor({ doctor, loading, setDoctors, priority = false }) {
width={72}
height={72}
alt="image-doctor"
src={doctor?.img?.[0]?.url || "/assets/images/user.png"}
src={imageUrl(doctor?.img?.[0]?.url)}
className="object-contain rounded-full w-[64px] md:w-[68px] lg:w-[72px] h-[64px] md:h-[68px] lg:h-[72px]"
priority={priority}
/>
+2 -1
View File
@@ -1,4 +1,5 @@
import Image from "next/image";
import { imageUrl } from "@/helper";
import { Button, CircularProgress } from "@mui/material";
// Icons
@@ -75,7 +76,7 @@ function ItemUser({ update, setUpdate, data }) {
<div className="flex items-center justify-start gap-2">
<Image
className="!w-[32px] !h-[32px]"
src={data.author?.picture[0]?.url}
src={imageUrl(data.author?.picture?.[0]?.url)}
width={40}
height={40}
alt="profile"
+2 -1
View File
@@ -1,8 +1,9 @@
import Image from "next/image";
import { imageUrl } from "@/helper";
function Head({ doctor }) {
// تبدیل img array به string
const doctorImage = doctor?.img?.[0]?.url || "/default-doctor.jpg";
const doctorImage = imageUrl(doctor?.img?.[0]?.url, "/default-doctor.jpg");
// تبدیل specialties array به string
const expertiseText = doctor?.specialties
+2 -1
View File
@@ -1,8 +1,9 @@
import Image from "next/image";
import { imageUrl } from "@/helper";
function Head({ doctor }) {
// تبدیل img array به string
const doctorImage = doctor?.img?.[0]?.url || "/default-doctor.jpg";
const doctorImage = imageUrl(doctor?.img?.[0]?.url, "/default-doctor.jpg");
// تبدیل specialties array به string
const expertiseText = doctor?.specialties
@@ -1,5 +1,6 @@
import CustomLoading from "@/app/component/loading/Custom";
import Image from "next/image";
import { imageUrl } from "@/helper";
function Slider({ data }) {
return (
@@ -11,7 +12,7 @@ function Slider({ data }) {
idx !== 0 ? (
<CustomLoading key={idx} width={90} height={90}>
<Image
src={item.url}
src={imageUrl(item.url)}
width={90}
height={90}
alt="image clinic"
@@ -5,6 +5,7 @@ import GreenCalendarTurn from "@/components/icons/GreenCalendarTurn";
import { Button } from "@mui/material";
import Image from "next/image";
import Link from "next/link";
import { imageUrl } from "@/helper";
function ItemAppointment({ turn, loading, doctorSlug }) {
return (
@@ -21,7 +22,7 @@ function ItemAppointment({ turn, loading, doctorSlug }) {
width={56}
height={56}
alt="doctor"
src={turn?.img[0]?.url}
src={imageUrl(turn?.img?.[0]?.url)}
/>
</CircularLoading>
<div className="flex flex-col items-start justify-center gap-2">
+2 -1
View File
@@ -5,6 +5,7 @@ import LikeDI from "@/components/icons/LikeDI";
import StarDI from "@/components/icons/StarDI";
import TickCircle from "@/components/icons/TickCircle";
import Image from "next/image";
import { imageUrl } from "@/helper";
function Title({ doctor }) {
return (
@@ -16,7 +17,7 @@ function Title({ doctor }) {
w-[40px] sm:w-[81px] md:w-[123px] lg:w-[164px]
h-[40px] sm:h-[81px] md:h-[123px] lg:h-[164px]
"
src={doctor?.img?.[0]?.url || "/assets/images/user.png"}
src={imageUrl(doctor?.img?.[0]?.url)}
alt="img-doctor"
height={164}
width={164}
+2 -1
View File
@@ -1,10 +1,11 @@
import Image from "next/image";
import { imageUrl } from "@/helper";
function ImageProfile({ data }) {
return (
<div className="bg-stroke-circle ml-[10px] mt-[57px] p-[9px] rounded-full overflow-hidden">
<Image
src={data?.img[0]?.url}
src={imageUrl(data?.img?.[0]?.url)}
width={173}
height={173}
alt="profile-user"
+2 -1
View File
@@ -1,5 +1,6 @@
import { getParsedUserInfo } from "@/helper";
import Image from "next/image";
import { imageUrl } from "@/helper";
function Head({ user }) {
const userInfo = getParsedUserInfo();
@@ -8,7 +9,7 @@ function Head({ user }) {
<div className="w-full flex flex-row md:flex-col items-center justify-center md:justify-start gap-[12px] md:gap-[4px]">
<Image
className="rounded-full border-[3px] border-solid border-[#F8F8FF] shadow-[0px_1px_25.6px_0px_rgba(85,_89,_205,_0.20)]"
src={userInfo?.picture[0]?.url}
src={imageUrl(userInfo?.picture?.[0]?.url)}
alt="profile"
height={73}
width={73}
+12
View File
@@ -12,6 +12,18 @@ export const numberToArStyle = (num) => num?.toLocaleString("ar-AE") || "";
export const isActiveURL = (link, name) => link === name;
// تبدیل مسیر نسبی تصویر (/uploads/...) به URL کامل backend.
// مسیرهای absolute (http...) و asset های محلی (/assets، /default) دست‌نخورده می‌مانند.
export const imageUrl = (url, fallback = "/assets/images/user.png") => {
if (!url) return fallback;
if (/^https?:\/\//.test(url)) return url;
if (url.startsWith("/uploads")) {
const base = process.env.NEXT_PUBLIC_API_URL || "";
return `${base}${url}`;
}
return url;
};
export const convertToJalali = (date) => {
const currentDate = date;