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:
@@ -5,6 +5,7 @@ import Link from "next/link";
|
|||||||
import CustomLoading from "./loading/Custom";
|
import CustomLoading from "./loading/Custom";
|
||||||
import TextLoading from "./loading/Text";
|
import TextLoading from "./loading/Text";
|
||||||
import CircularLoading from "./loading/Circular";
|
import CircularLoading from "./loading/Circular";
|
||||||
|
import { imageUrl } from "@/helper";
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import ArrowLeftD from "@/components/icons/ArrowLeftD";
|
import ArrowLeftD from "@/components/icons/ArrowLeftD";
|
||||||
@@ -32,7 +33,7 @@ function ItemDoctor({ doctor, loading, setDoctors, priority = false }) {
|
|||||||
width={72}
|
width={72}
|
||||||
height={72}
|
height={72}
|
||||||
alt="image-doctor"
|
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]"
|
className="object-contain rounded-full w-[64px] md:w-[68px] lg:w-[72px] h-[64px] md:h-[68px] lg:h-[72px]"
|
||||||
priority={priority}
|
priority={priority}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { imageUrl } from "@/helper";
|
||||||
import { Button, CircularProgress } from "@mui/material";
|
import { Button, CircularProgress } from "@mui/material";
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
@@ -75,7 +76,7 @@ function ItemUser({ update, setUpdate, data }) {
|
|||||||
<div className="flex items-center justify-start gap-2">
|
<div className="flex items-center justify-start gap-2">
|
||||||
<Image
|
<Image
|
||||||
className="!w-[32px] !h-[32px]"
|
className="!w-[32px] !h-[32px]"
|
||||||
src={data.author?.picture[0]?.url}
|
src={imageUrl(data.author?.picture?.[0]?.url)}
|
||||||
width={40}
|
width={40}
|
||||||
height={40}
|
height={40}
|
||||||
alt="profile"
|
alt="profile"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { imageUrl } from "@/helper";
|
||||||
|
|
||||||
function Head({ doctor }) {
|
function Head({ doctor }) {
|
||||||
// تبدیل img array به string
|
// تبدیل 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
|
// تبدیل specialties array به string
|
||||||
const expertiseText = doctor?.specialties
|
const expertiseText = doctor?.specialties
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { imageUrl } from "@/helper";
|
||||||
|
|
||||||
function Head({ doctor }) {
|
function Head({ doctor }) {
|
||||||
// تبدیل img array به string
|
// تبدیل 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
|
// تبدیل specialties array به string
|
||||||
const expertiseText = doctor?.specialties
|
const expertiseText = doctor?.specialties
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import CustomLoading from "@/app/component/loading/Custom";
|
import CustomLoading from "@/app/component/loading/Custom";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { imageUrl } from "@/helper";
|
||||||
|
|
||||||
function Slider({ data }) {
|
function Slider({ data }) {
|
||||||
return (
|
return (
|
||||||
@@ -11,7 +12,7 @@ function Slider({ data }) {
|
|||||||
idx !== 0 ? (
|
idx !== 0 ? (
|
||||||
<CustomLoading key={idx} width={90} height={90}>
|
<CustomLoading key={idx} width={90} height={90}>
|
||||||
<Image
|
<Image
|
||||||
src={item.url}
|
src={imageUrl(item.url)}
|
||||||
width={90}
|
width={90}
|
||||||
height={90}
|
height={90}
|
||||||
alt="image clinic"
|
alt="image clinic"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import GreenCalendarTurn from "@/components/icons/GreenCalendarTurn";
|
|||||||
import { Button } from "@mui/material";
|
import { Button } from "@mui/material";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { imageUrl } from "@/helper";
|
||||||
|
|
||||||
function ItemAppointment({ turn, loading, doctorSlug }) {
|
function ItemAppointment({ turn, loading, doctorSlug }) {
|
||||||
return (
|
return (
|
||||||
@@ -21,7 +22,7 @@ function ItemAppointment({ turn, loading, doctorSlug }) {
|
|||||||
width={56}
|
width={56}
|
||||||
height={56}
|
height={56}
|
||||||
alt="doctor"
|
alt="doctor"
|
||||||
src={turn?.img[0]?.url}
|
src={imageUrl(turn?.img?.[0]?.url)}
|
||||||
/>
|
/>
|
||||||
</CircularLoading>
|
</CircularLoading>
|
||||||
<div className="flex flex-col items-start justify-center gap-2">
|
<div className="flex flex-col items-start justify-center gap-2">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import LikeDI from "@/components/icons/LikeDI";
|
|||||||
import StarDI from "@/components/icons/StarDI";
|
import StarDI from "@/components/icons/StarDI";
|
||||||
import TickCircle from "@/components/icons/TickCircle";
|
import TickCircle from "@/components/icons/TickCircle";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { imageUrl } from "@/helper";
|
||||||
|
|
||||||
function Title({ doctor }) {
|
function Title({ doctor }) {
|
||||||
return (
|
return (
|
||||||
@@ -16,7 +17,7 @@ function Title({ doctor }) {
|
|||||||
w-[40px] sm:w-[81px] md:w-[123px] lg:w-[164px]
|
w-[40px] sm:w-[81px] md:w-[123px] lg:w-[164px]
|
||||||
h-[40px] sm:h-[81px] md:h-[123px] lg:h-[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"
|
alt="img-doctor"
|
||||||
height={164}
|
height={164}
|
||||||
width={164}
|
width={164}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { imageUrl } from "@/helper";
|
||||||
|
|
||||||
function ImageProfile({ data }) {
|
function ImageProfile({ data }) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-stroke-circle ml-[10px] mt-[57px] p-[9px] rounded-full overflow-hidden">
|
<div className="bg-stroke-circle ml-[10px] mt-[57px] p-[9px] rounded-full overflow-hidden">
|
||||||
<Image
|
<Image
|
||||||
src={data?.img[0]?.url}
|
src={imageUrl(data?.img?.[0]?.url)}
|
||||||
width={173}
|
width={173}
|
||||||
height={173}
|
height={173}
|
||||||
alt="profile-user"
|
alt="profile-user"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { getParsedUserInfo } from "@/helper";
|
import { getParsedUserInfo } from "@/helper";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { imageUrl } from "@/helper";
|
||||||
|
|
||||||
function Head({ user }) {
|
function Head({ user }) {
|
||||||
const userInfo = getParsedUserInfo();
|
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]">
|
<div className="w-full flex flex-row md:flex-col items-center justify-center md:justify-start gap-[12px] md:gap-[4px]">
|
||||||
<Image
|
<Image
|
||||||
className="rounded-full border-[3px] border-solid border-[#F8F8FF] shadow-[0px_1px_25.6px_0px_rgba(85,_89,_205,_0.20)]"
|
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"
|
alt="profile"
|
||||||
height={73}
|
height={73}
|
||||||
width={73}
|
width={73}
|
||||||
|
|||||||
@@ -12,6 +12,18 @@ export const numberToArStyle = (num) => num?.toLocaleString("ar-AE") || "";
|
|||||||
|
|
||||||
export const isActiveURL = (link, name) => link === name;
|
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) => {
|
export const convertToJalali = (date) => {
|
||||||
const currentDate = date;
|
const currentDate = date;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user