feat: Revert homepage changes, fix Persian slug 404, and make various article and about us adjustments
- Removed the newly added city-focused section from the homepage. - Fixed 404 error for Persian slugs in specialties and blog pages by implementing a decode helper. - Removed the "مخصوص شهر" label from article headers. - Linked article tags to their respective filter pages. - Adjusted related content images to prevent distortion. - Added a section in the "About Us" page for physician registration guidance. - Updated the logo in the "About Us" header to the correct version. - Added tests for the new functionality and ensured existing tests are updated accordingly.
This commit is contained in:
@@ -1,15 +1,11 @@
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
import SiteLogo from "@/components/common/SiteLogo";
|
||||
|
||||
function Head({ siteName, slogan }) {
|
||||
function Head({ siteName, slogan, city }) {
|
||||
return (
|
||||
<div className="bg-[#5559C2] rounded-none md:rounded-lg w-full py-[24px] flex flex-col items-center justify-center gap-[13px] md:gap-[16px]">
|
||||
<Image
|
||||
src="/assets/images/logo-2.png"
|
||||
height={46}
|
||||
width={46}
|
||||
alt="logo"
|
||||
/>
|
||||
{/* رنگ پیشفرض لوگو #5559CE است و روی همین پسزمینه گم میشود؛ نسخهٔ روشن. */}
|
||||
<SiteLogo city={city} stroke="#FAFAFA" className="w-[46px] h-[46px]" />
|
||||
<h1 className="text-[#FAFAFA] text-[16px] sm:text-[18px] md:text-[22px] lg:text-[24px] font-bold">
|
||||
درباره {siteName}
|
||||
</h1>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import Head from "@/components/aboutUs/Head";
|
||||
|
||||
const CITY = { name: "یاسوج", site_name: "یاسوج نوبت", logo_url: null };
|
||||
|
||||
describe("سربرگ درباره ما", () => {
|
||||
it("شهر بدون logo_url لوگوی درونخطی با رنگ روشن میگیرد", () => {
|
||||
const { container } = render(
|
||||
<Head siteName={CITY.site_name} slogan="کنار شما" city={CITY} />
|
||||
);
|
||||
|
||||
const path = container.querySelector("svg path");
|
||||
|
||||
// پسزمینهٔ هدر #5559C2 است؛ رنگ پیشفرض لوگو روی آن دیده نمیشود.
|
||||
expect(path).toHaveAttribute("stroke", "#FAFAFA");
|
||||
});
|
||||
|
||||
it("شهر دارای logo_url همان تصویر را نشان میدهد", () => {
|
||||
render(
|
||||
<Head
|
||||
siteName={CITY.site_name}
|
||||
city={{ ...CITY, logo_url: "https://cdn.test/logo.png" }}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole("img", { name: "logo" })).toHaveAttribute(
|
||||
"src",
|
||||
"https://cdn.test/logo.png"
|
||||
);
|
||||
});
|
||||
|
||||
it("لوگوی قدیمی logo-2.png دیگر رندر نمیشود", () => {
|
||||
const { container } = render(<Head siteName={CITY.site_name} city={CITY} />);
|
||||
|
||||
expect(container.innerHTML).not.toContain("logo-2");
|
||||
});
|
||||
|
||||
it("شعار نداشتن، سربرگ را نمیشکند", () => {
|
||||
render(<Head siteName={CITY.site_name} city={CITY} />);
|
||||
|
||||
expect(
|
||||
screen.getByRole("heading", { name: `درباره ${CITY.site_name}` })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ async function AboutUsPage() {
|
||||
<div
|
||||
className=" pt-[24px] sm:pt-[56px] md:pt-[88px] lg:pt-[120px] mt-[56px] px-0 md:px-[80px] lg:px-[108px] "
|
||||
>
|
||||
<Head siteName={siteName} slogan={slogan} />
|
||||
<Head siteName={siteName} slogan={slogan} city={matchedCity} />
|
||||
<div
|
||||
className=" text-[#525252] text-[16px] font-normal mt-[32px] leading-[28px] md:leading-[32px] lg:leading-[40px] px-[16px] sm:px-[52px] md:px-[15%] text-justify md:text-center flex flex-col gap-[16px] "
|
||||
>
|
||||
@@ -27,6 +27,22 @@ async function AboutUsPage() {
|
||||
نوبت خود را ثبت کنید. هدف ما کاهش زمان انتظار و ایجاد ارتباط مؤثر میان
|
||||
بیماران، پزشکان و کلینیکهاست.
|
||||
</p>
|
||||
<p>
|
||||
اگر پزشک هستید و میخواهید در {siteName} نوبتدهی آنلاین داشته باشید،
|
||||
ثبتنام از طریق سامانهی کلینیکپرو انجام میشود. حساب مطب خود را
|
||||
آنجا بسازید؛ پس از تأیید، پروفایل و برنامهی نوبتدهی شما روی{" "}
|
||||
{siteName} نمایش داده میشود.
|
||||
</p>
|
||||
<div className="flex justify-center mt-[8px]">
|
||||
<a
|
||||
href="https://clinic-pro.ir/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-[24px] py-[12px] rounded-lg bg-[#5559CE] border border-[#5559CE] text-[#FFF] text-[14px] md:text-[16px] font-medium hover:bg-[#FFF] hover:text-[#5559CE] transition-all duration-500"
|
||||
>
|
||||
ثبتنام پزشکان در کلینیکپرو
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Services />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import Head from "@/components/blog/head";
|
||||
|
||||
const BLOG = {
|
||||
title: "آلرژی دارویی",
|
||||
author: "دکتر رضایی",
|
||||
created: 1750000000,
|
||||
reading_time: 5,
|
||||
tag: [{ name: "آلرژی" }, { name: "قلب و عروق" }],
|
||||
};
|
||||
|
||||
describe("سربرگ مقاله", () => {
|
||||
it("برچسب «مخصوص شهر» را نشان نمیدهد", () => {
|
||||
render(<Head data={BLOG} />);
|
||||
|
||||
expect(screen.queryByText("مخصوص شهر:")).toBeNull();
|
||||
});
|
||||
|
||||
it("نویسنده و تاریخ انتشار سر جایشان میمانند", () => {
|
||||
render(<Head data={BLOG} />);
|
||||
|
||||
expect(screen.getByText("نویسنده:")).toBeInTheDocument();
|
||||
expect(screen.getByText("تاریخ انتشار:")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("هر تگ به لیست مقالههای همان تگ لینک میشود", () => {
|
||||
render(<Head data={BLOG} />);
|
||||
|
||||
const links = screen.getAllByRole("link", { name: "قلب و عروق" });
|
||||
const url = new URL(links[0].getAttribute("href"), "https://yasuj-nobat.ir");
|
||||
|
||||
expect(links).toHaveLength(1);
|
||||
expect(url.pathname).toBe("/blogs");
|
||||
// قرارداد واقعی همان چیزی است که `searchParams.get("tag")` میخواند،
|
||||
// نه شکل دقیق encode — شکل شیئیِ href فاصله را `+` میکند و بردکرامب `%20`.
|
||||
expect(url.searchParams.get("tag")).toBe("قلب و عروق");
|
||||
});
|
||||
|
||||
it("تگ حاوی & با پارامتر دوم اشتباه گرفته نمیشود", () => {
|
||||
render(<Head data={{ ...BLOG, tag: [{ name: "قلب & عروق" }] }} />);
|
||||
|
||||
const links = screen.getAllByRole("link", { name: "قلب & عروق" });
|
||||
const url = new URL(links[0].getAttribute("href"), "https://yasuj-nobat.ir");
|
||||
|
||||
expect(url.searchParams.get("tag")).toBe("قلب & عروق");
|
||||
expect(Array.from(url.searchParams.keys())).toEqual(["tag"]);
|
||||
});
|
||||
|
||||
it("مقالهٔ بدون تگ هیچ فهرست تگی رندر نمیکند", () => {
|
||||
const { container } = render(<Head data={{ ...BLOG, tag: [] }} />);
|
||||
|
||||
expect(container.querySelector("ul")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import { convertTimestampToJalali } from "@/helper";
|
||||
const crumbClass =
|
||||
"text-[#9B9B9B] text-[11px] sm:text-[13px] md:text-[15px] lg:text-[16px] font-normal";
|
||||
|
||||
function Head({ data, cityName }) {
|
||||
function Head({ data }) {
|
||||
const firstTag = data?.tag?.[0];
|
||||
const createdDate = data?.created ? convertTimestampToJalali(data.created) : "";
|
||||
// normalizeBlog تگها را به [{name}] تبدیل میکند؛ نامِ خالی حذف میشود.
|
||||
@@ -44,18 +44,6 @@ function Head({ data, cityName }) {
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex mt-[16px] sm:mt-[18px] md:mt-[21px] lg:mt-[24px] items-center justify-start gap-[35px]">
|
||||
{/* شهر پست در HTML قابلمشاهده — نه فقط در متادیتا. برچسب با همان
|
||||
تایپوگرافی «نویسنده/تاریخ» تا با بقیهٔ سربرگ همخانواده بماند. */}
|
||||
{cityName && (
|
||||
<div className="flex items-center justify-start gap-1">
|
||||
<p className="text-[#9B9B9B] text-[11px] md:text-[13px] lg:text-[14px] font-normal">
|
||||
مخصوص شهر:
|
||||
</p>
|
||||
<p className="text-[#9B9B9B] text-[11px] md:text-[14px] lg:text-[16px] font-medium">
|
||||
{cityName}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{data?.author && (
|
||||
<div className="flex items-center justify-start gap-1">
|
||||
<p className="text-[#9B9B9B] text-[11px] md:text-[13px] lg:text-[14px] font-normal">
|
||||
@@ -88,15 +76,18 @@ function Head({ data, cityName }) {
|
||||
)}
|
||||
</div>
|
||||
{/* فهرست کامل تگها — تا این تغییر فقط تگ اول در بردکرامب دیده میشد.
|
||||
صفحهٔ فیلتر تگ وجود ندارد، پس چیپها بدون لینک رندر میشوند. */}
|
||||
مقصد هر چیپ همان لیست مقالههاست که `?tag=` را میخواند؛ شکل شیئیِ href
|
||||
تگِ حاوی فاصله یا & را هم درست encode میکند. */}
|
||||
{tags.length > 0 && (
|
||||
<ul className="flex flex-wrap items-center justify-start gap-2 mt-[12px] md:mt-[16px]">
|
||||
{tags.map((tag, idx) => (
|
||||
<li
|
||||
key={`${tag}-${idx}`}
|
||||
className="bg-[#F5F5F5] text-[#525252] rounded-full px-[12px] py-[4px] text-[11px] md:text-[13px] font-medium"
|
||||
>
|
||||
{tag}
|
||||
<li key={`${tag}-${idx}`}>
|
||||
<Link
|
||||
href={{ pathname: "/blogs", query: { tag } }}
|
||||
className="block bg-[#F5F5F5] text-[#525252] rounded-full px-[12px] py-[4px] text-[11px] md:text-[13px] font-medium hover:bg-[#E8E8E8] hover:text-[#3B3B3B] transition-colors"
|
||||
>
|
||||
{tag}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -2,10 +2,10 @@ import Detail from "./detail";
|
||||
import Head from "./head";
|
||||
import RelatedContent from "./relatedContent";
|
||||
|
||||
function BlogPage({ blog, blogs, cityName }) {
|
||||
function BlogPage({ blog, blogs }) {
|
||||
return (
|
||||
<div className="padding-responsive pt-[84px] sm:pt-[110px] md:pt-[140px] lg:pt-[168px]">
|
||||
<Head data={blog} cityName={cityName} />
|
||||
<Head data={blog} />
|
||||
<div className="flex flex-col gap-y-[32px] lg:flex-row items-start justify-center gap-[24px] mt-[16px] sm:mt-[19px] md:mt-[21px] lg:mt-[24px]">
|
||||
<Detail data={blog} />
|
||||
<RelatedContent data={blogs} />
|
||||
|
||||
@@ -10,8 +10,10 @@ function Item({ data, idx, loading }) {
|
||||
|
||||
return (
|
||||
<li className="flex w-full relative items-start justify-start gap-[8px] py-[12px]">
|
||||
{/* CustomLoading ظرف نمیسازد، پس فرزند مستقیمِ فلکس همین لینک است؛
|
||||
بدون shrink-0 تیتر بلند عرض عکس را میبلعد و ارتفاعش ثابت میماند. */}
|
||||
<CustomLoading width={80} height={72} loading={loading}>
|
||||
<Link href={`/blog/${data.uuid}`}>
|
||||
<Link href={`/blog/${data.uuid}`} className="block shrink-0">
|
||||
<img
|
||||
className=" w-[64px] sm:w-[73px] md:w-[85px] lg:w-[97px] h-[56px] sm:h-[64px] md:h-[75px] lg:h-[88px] rounded-[8px] overflow-hidden object-cover "
|
||||
src={cover}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import Item from "@/components/blog/relatedContent/Item";
|
||||
|
||||
const DATA = {
|
||||
uuid: "8f1c0f2e-0000-4000-8000-000000000001",
|
||||
title: "تیتری که بسیار بلند است و باید ستون متن را پر کند بدون آنکه عکس را باریک کند",
|
||||
created: 1750000000,
|
||||
};
|
||||
|
||||
describe("آیتم مطالب مرتبط", () => {
|
||||
it("ظرف عکس در فلکس فشرده نمیشود", () => {
|
||||
render(<Item data={DATA} idx={0} loading={false} />);
|
||||
|
||||
const image = screen.getByRole("img", { name: DATA.title });
|
||||
|
||||
// jsdom چیدمان را اندازه نمیگیرد؛ آنچه قابل تست است همین قرارداد کلاس است.
|
||||
expect(image.closest("a")).toHaveClass("shrink-0");
|
||||
});
|
||||
|
||||
it("ستون متن اجازهٔ کوچک شدن دارد تا سرریز نکند", () => {
|
||||
const { container } = render(<Item data={DATA} idx={0} loading={false} />);
|
||||
|
||||
expect(container.querySelector("li > div")).toHaveClass("min-w-0");
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import DoctorAvatar from "@/app/component/DoctorAvatar";
|
||||
import QrImg from "./QrImg";
|
||||
import SiteLogo from "./SiteLogo";
|
||||
import SiteLogo from "@/components/common/SiteLogo";
|
||||
import { getStateInfoClient } from "@/lib/getStateInfoClient";
|
||||
import { posterSpecialtyLine, splitSpecialties } from "@/lib/specialtyDisplay";
|
||||
import { doctorTitle } from "@/helper";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import DoctorAvatar from "@/app/component/DoctorAvatar";
|
||||
import QrImg from "./QrImg";
|
||||
import SiteLogoBase from "./SiteLogo";
|
||||
import SiteLogoBase from "@/components/common/SiteLogo";
|
||||
import { getStateInfoClient } from "@/lib/getStateInfoClient";
|
||||
import { posterSpecialtyLine, splitSpecialties } from "@/lib/specialtyDisplay";
|
||||
import { doctorTitle } from "@/helper";
|
||||
|
||||
@@ -3,7 +3,6 @@ import Image from "next/image";
|
||||
import SearchBar from "./search";
|
||||
import TextHeader from "./TextHeader";
|
||||
import FrequentSearches from "./FrequentSearches";
|
||||
import CityHighlights from "./CityHighlights";
|
||||
|
||||
function HomePage() {
|
||||
return (
|
||||
@@ -19,7 +18,6 @@ function HomePage() {
|
||||
<TextHeader />
|
||||
<SearchBar />
|
||||
<FrequentSearches />
|
||||
<CityHighlights />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user