This commit is contained in:
2026-03-26 08:09:01 +03:30
parent 5b10807004
commit 333bc0c69e
37 changed files with 940 additions and 575 deletions

View File

@@ -2,6 +2,16 @@ import type {NextConfig} from "next";
const nextConfig: NextConfig = {
/* config options here */
images: {
remotePatterns: [
{
protocol: "http",
hostname: "localhost",
port: "4000",
pathname: "/uploads/**",
},
],
},
};
export default nextConfig;

141
package-lock.json generated
View File

@@ -8,6 +8,8 @@
"name": "frontend",
"version": "0.1.0",
"dependencies": {
"@tanstack/react-query": "^5.90.21",
"axios": "^1.13.5",
"next": "15.5.4",
"react": "19.1.0",
"react-dom": "19.1.0",
@@ -1433,6 +1435,32 @@
"tailwindcss": "4.1.14"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.90.20",
"resolved": "https://mirror-npm.runflare.com/@tanstack/query-core/-/query-core-5.90.20.tgz",
"integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.90.21",
"resolved": "https://mirror-npm.runflare.com/@tanstack/react-query/-/react-query-5.90.21.tgz",
"integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.90.20"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18 || ^19"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@@ -2266,6 +2294,12 @@
"node": ">= 0.4"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://mirror-npm.runflare.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -2290,6 +2324,17 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.13.5",
"resolved": "https://mirror-npm.runflare.com/axios/-/axios-1.13.5.tgz",
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -2363,7 +2408,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
@@ -2477,6 +2521,18 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://mirror-npm.runflare.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2674,6 +2730,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://mirror-npm.runflare.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -2708,7 +2773,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
@@ -2817,7 +2881,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -2826,7 +2889,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -2862,7 +2924,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"dependencies": {
"es-errors": "^1.3.0"
},
@@ -2874,7 +2935,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
@@ -3457,6 +3517,26 @@
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://mirror-npm.runflare.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -3472,6 +3552,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://mirror-npm.runflare.com/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -3531,7 +3627,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
@@ -3555,7 +3650,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
@@ -3637,7 +3731,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -3709,7 +3802,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -3721,7 +3813,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"dependencies": {
"has-symbols": "^1.0.3"
},
@@ -4621,7 +4712,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -4653,6 +4743,27 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://mirror-npm.runflare.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://mirror-npm.runflare.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -5126,6 +5237,12 @@
"react-is": "^16.13.1"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://mirror-npm.runflare.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

View File

@@ -11,6 +11,8 @@
"app": "npm run json-server & npm run dev"
},
"dependencies": {
"@tanstack/react-query": "^5.90.21",
"axios": "^1.13.5",
"next": "15.5.4",
"react": "19.1.0",
"react-dom": "19.1.0",

BIN
public/dr-emami.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
public/dr-galin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
public/dr-heydarnejad.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
public/dr-majd.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
public/dr-rojaei.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
public/dr-t.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
public/dr-tahmasbi.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
public/dr-uknown.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
public/khayer.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
public/metron-ghanbari.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
public/rajabtabar.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -5,20 +5,74 @@ import Image from "next/image";
import React from "react";
import { getDictionary } from "../dictionaries";
import PatientAcceptForm from "@/ui/forms/PatientAcceptForm";
import {default_info, pages_titles, PHONE_NUMBERS} from "@/constants";
import { pages_titles } from "@/constants";
import { Metadata } from "next";
export const metadata: Metadata = {
title: pages_titles.contact_us["fa"] + " | " + "بیمارستان شمال",
description: "Shomal Hospital IPD contact us page",
};
interface contactUsResponseType {
status: number;
data: [
{
email: string;
hospitalPhone: string;
mapAddress: string;
instagramLink: string | null;
ipdNumber: string | null;
translations: {
address: string;
language: {
slug: string;
title: string;
}
}[];
} | null,
{
email: string | null;
translations: {
position: string | null;
lang: {
slug: string;
} | null;
displayName: string | null;
}[];
} | null,
];
message: string;
}
async function getData(): Promise<contactUsResponseType> {
const res = await fetch(
`http://localhost:3500/api/v1/public-apis/get/contact-us`,
{
cache: "no-cache",
},
);
if (!res.ok) {
throw new Error("Error");
}
const data = await res.json();
return data;
}
export default async function ContactUs({
params,
}: {
params: Promise<{ lang: languages_types }>;
}) {
const { lang } = await params;
const {contact_us_page, about_page, footer, accept_request_form} =
const { contact_us_page, about_page, accept_request_form } =
await getDictionary(lang);
const getdata = await getData();
const contactInfo = getdata.data.length > 0 ? getdata.data[0] : null;
const filteredAddress = getdata.data[0]?.translations.find((item) => item?.language.slug===lang);
return (
<>
<PageHeaderSlider
@@ -63,9 +117,9 @@ export default async function ContactUs({
<span className="text-lg text-white font-semibold">
<a
dir="ltr"
href={`tel:${PHONE_NUMBERS.ipd_technician.href}`}
href={`tel:${contactInfo?.ipdNumber}`}
>
{PHONE_NUMBERS.ipd_technician.href}
{contactInfo?.ipdNumber}
</a>
</span>
</div>
@@ -85,10 +139,8 @@ export default async function ContactUs({
</svg>
</span>
<span className="text-lg text-white font-semibold">
<a
href={`mailto:${default_info.email}`}
>
{default_info.email}
<a href={`mailto:${contactInfo?.email}`}>
{contactInfo?.email}
</a>
</span>
</div>
@@ -128,7 +180,7 @@ export default async function ContactUs({
</svg>
</span>
</div>
<p className="text-xl">{footer?.contact_us?.address}</p>
<p className="text-xl">{filteredAddress?.address}</p>
</div>
<div className="flex items-center gap-x-4">
<div className="w-[70px] h-[70px] flex items-center justify-center rounded-2xl bg-secondary">
@@ -147,8 +199,8 @@ export default async function ContactUs({
</svg>
</span>
</div>
<a href={`mailto:${default_info.email}`} className="text-xl">
{default_info.email}
<a href={`mailto:${contactInfo?.email}`} className="text-xl">
{contactInfo?.email}
</a>
</div>
<div className="flex items-center gap-x-4">
@@ -160,25 +212,25 @@ export default async function ContactUs({
<div className="flex items-center gap-x-2">
<a
dir="ltr"
href={`tel:${PHONE_NUMBERS.ipd_technician.href}`}
href={`tel:${contactInfo?.ipdNumber}`}
className="text-xl"
>
{PHONE_NUMBERS.ipd_technician.href}
{contactInfo?.ipdNumber}
</a>
<span>|</span>
<a
dir="ltr"
href={`tel:${PHONE_NUMBERS.hospital.href}`}
href={`tel:${contactInfo?.hospitalPhone}`}
className="text-xl"
>
{PHONE_NUMBERS.hospital.label}
{contactInfo?.hospitalPhone}
</a>
</div>
</div>
<span className="w-full inline-block h-[2px] bg-secondary/20"></span>
<div className="flex items-center gap-x-3">
<a
href="https://instagram.com/shomalhospital"
href={contactInfo?.instagramLink ?? "#"}
className="w-[70px] h-[70px] flex items-center justify-center rounded-2xl bg-secondary"
>
<span className="text-white">
@@ -196,12 +248,11 @@ export default async function ContactUs({
</svg>
</span>
</a>
</div>
</div>
<div className="md:col-span-6 col-span-12 md:mt-0 mt-8">
<iframe
src="https://www.google.com/maps/embed?pb=!1m14!1m8!1m3!1d6419.524849772638!2d52.347594!3d36.439127!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3f8fbd6849bf386b%3A0x26cbc9441b00f373!2sShomal%20Hospital!5e0!3m2!1sen!2sus!4v1762836982418!5m2!1sen!2sus"
src={contactInfo?.mapAddress}
width="100%"
height="450"
style={{ border: "0" }}

View File

@@ -1,4 +1,4 @@
import {DoctorDataType, languages_types} from "@/types";
import {languages_types} from "@/types";
import DoctorsFilterBox from "@/ui/forms/DoctorsFilterBox";
import PageHeaderSlider from "@/ui/page-header-slider/PageHeaderSlider";
import {toPersianNumber} from "@/utils/functions";

View File

@@ -1,22 +1,77 @@
import type {Metadata} from "next";
import "../globals.css";
import { FontVazir } from "@/config/font.config";
import TopMenu from "@/ui/components/top-menu/TopMenu";
import TopNavbar from "@/ui/top-navbar";
import { languages_types } from "@/types";
import { redirect } from "next/navigation";
import { Metadata } from "next";
import LanguageProvider from "@/ui/components/LanguageProvider";
import Image from "next/image";
import { FooterMenuLinks1 } from "@/constants/menu/menu.const";
import CustomLink from "@/ui/components/global/CustomLink";
import { getHref } from "@/utils/functions";
import {languages_types} from "@/types";
import { getDictionary } from "./dictionaries";
import { PHONE_NUMBERS } from "@/constants";
export const metadata: Metadata = {
title: "بیماران بین الملل | بیمارستان شمال آمل",
description: "صفحه ی بیماران بین الملل بیمارستان شمال آمل",
};
export async function generateStaticParams() {
return [{lang: "en"}, {lang: "fa"}, {lang: "ar"}];
interface footerResponseType {
status: number;
data: {
email: string;
hospitalPhone: string;
mapAddress: string;
instagramLink: string | null;
ipdNumber: string | null;
translations: {
address: string;
underLogoText: string | null;
language: {
title: string;
slug: string;
};
}[];
};
message: string;
}
interface getLanguagesTypes {
id: number;
title: string;
slug: string;
}
async function getLanguages() : Promise<{data:getLanguagesTypes[]}> {
const res = await fetch(`http://localhost:3500/api/v1/language/get/all`, {
cache: "no-cache",
});
if (!res.ok) {
return { data: [{ id:0,title: "فارسی", slug: "fa" }] };
}
return await res.json();
}
async function getData(): Promise<footerResponseType> {
const res = await fetch(
`http://localhost:3500/api/v1/public-apis/get/footer`,
{
cache: "no-cache",
},
);
if (!res.ok) {
throw new Error("Error");
}
const data = await res.json();
return data;
}
export default async function RootLayout({
children,
@@ -26,14 +81,31 @@ export default async function RootLayout({
params: Promise<{ lang: string }>;
}>) {
const { lang } = await params;
const dict = await getDictionary(lang as languages_types);
const {footer} = dict || {};
const { footer } = dict;
const languages = await getLanguages();
const currentLang = languages?.data.find((l) => l.slug === lang);
if (!currentLang) {
const defaultLang = languages?.data[0]?.slug;
redirect(`/${defaultLang}`);
}
const getdata = await getData();
const filteredData = getdata.data.translations.find(
(item) => item.language.slug === lang,
);
return (
<html lang={(await params).lang} dir={`${lang === "en" ? "ltr" : "rtl"}`}>
<body
className={`${FontVazir.variable} antialiased`}
style={{ fontFamily: FontVazir.style.fontFamily }}
>
<LanguageProvider languages={languages?.data}>
<TopNavbar lang={lang as languages_types} />
<TopMenu lang={lang as languages_types} />
{children}
@@ -59,7 +131,7 @@ export default async function RootLayout({
lang === "en" ? "text-left" : "text-right"
}`}
>
{footer?.under_logo_text}
{filteredData?.underLogoText}
</p>
</div>
<div className="lg:col-span-5 col-span-12 flex justify-center">
@@ -92,7 +164,7 @@ export default async function RootLayout({
<strong className=" text-blue-primary">
{footer?.our_address}
</strong>{" "}
:{footer?.contact_us?.address}
:{filteredData?.address}
</p>
<p className="mt-4 rtl text-sm">
<strong className=" text-blue-primary">
@@ -100,7 +172,13 @@ export default async function RootLayout({
</strong>{" "}
: &nbsp;
<span style={{ direction: "ltr" }}>
<a dir="ltr" style={{textAlign:"left"}} href={`tel:${PHONE_NUMBERS.ipd_technician.href}`}>{PHONE_NUMBERS.ipd_technician.label}</a>
<a
dir="ltr"
style={{ textAlign: "left" }}
href={`tel:${getdata.data.ipdNumber}`}
>
{getdata.data.ipdNumber}
</a>
</span>
</p>
<p className="my-4 rtl text-sm">
@@ -109,12 +187,18 @@ export default async function RootLayout({
</strong>{" "}
: &nbsp;
<span style={{ direction: "ltr" }}>
<a dir="ltr" style={{textAlign:"left"}} href={`tel:${PHONE_NUMBERS.hospital.href}`}>{PHONE_NUMBERS.hospital.label}</a>
<a
dir="ltr"
style={{ textAlign: "left" }}
href={`tel:${getdata.data.hospitalPhone}`}
>
{getdata.data.hospitalPhone}
</a>
</span>
</p>
<div className="relative w-full pb-[56.25%] rounded-xl overflow-hidden">
<iframe
src="https://www.google.com/maps/embed?pb=!1m14!1m8!1m3!1d6419.524849772638!2d52.347594!3d36.439127!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3f8fbd6849bf386b%3A0x26cbc9441b00f373!2sShomal%20Hospital!5e0!3m2!1sen!2sus!4v1762836982418!5m2!1sen!2sus"
src={getdata.data.mapAddress}
// width="100%"
style={{ border: "0" }}
allowFullScreen={false}
@@ -128,6 +212,7 @@ export default async function RootLayout({
</div>
</footer>
</div>
</LanguageProvider>
</body>
</html>
);

View File

@@ -1,7 +1,6 @@
import PageHeaderSlider from "@/ui/page-header-slider/PageHeaderSlider";
import Link from "next/link";
import React, {lazy, Suspense} from "react";
import {packages_types} from "@/types";
import {getDictionary} from "../dictionaries";
import {Metadata} from "next";
import {pages_titles} from "@/constants";

View File

@@ -3,14 +3,87 @@ import PageHeaderSlider from "@/ui/page-header-slider/PageHeaderSlider";
import Image from "next/image";
import PatientConsultantForm from "@/ui/forms/PatientConsultantForm";
import { getDictionary } from "./dictionaries";
import { languages_types } from "@/types";
interface aboutUsTextResponse {
status: number;
data: {
translations: { aboutUsText: string; language: { slug: string } }[];
};
message: string;
}
interface departmentdataResponse {
status: number;
data: {
translations: {
firstName: string | null;
lastName: string | null;
position: string | null;
excerpt: string | null;
lang: {
slug: string;
} | null;
}[];
image: {
fileUrl: string;
};
}[];
message: string;
}
async function getAboutUsText(): Promise<aboutUsTextResponse> {
const res = await fetch(
`${process.env.API_URL}/public-apis/get/about-us-text`,
// { next: { revalidate: 60 } },
{ cache: "no-cache" },
);
if (!res.ok) {
return { status: 200, data: { translations: [] }, message: "Ok" };
}
const data = await res.json();
return data;
}
async function getDepartmentMembers(): Promise<departmentdataResponse> {
const res = await fetch(
`${process.env.API_URL}/public-apis/get/department-members`,
// { next: { revalidate: 60 } },
{ cache: "no-cache" },
);
if (!res.ok) {
return {
status: 200,
data: [{ translations: [], image: { fileUrl: "#" } }],
message: "Ok",
};
}
const data = await res.json();
return data;
}
export default async function Page({
params,
}: {
params: Promise<{lang: "en" | "fa" | "ar"}>;
params: Promise<{ lang: string }>;
}) {
const { lang } = await params;
const {about_page} = await getDictionary(lang);
const { about_page } = await getDictionary(lang as languages_types);
const getdata = await getAboutUsText();
const getmembers = await getDepartmentMembers();
const filteredTranslations = getdata?.data?.translations.find(
(item) => item.language.slug === lang,
);
// const filteredDepartmentMembersTranslations = getmembers?.data?.flatMap(
// (item) => item.translations.filter((item) => item.lang?.slug === lang),
// );
return (
<>
@@ -37,66 +110,20 @@ export default async function Page({
{about_page?.introduction}
</h3>
<div className="mt-16">
<p
{getdata.data.translations?.length > 0 ? (
<div
className={`${
lang === "en" ? "text-left" : "text-right"
} lg:text-[1.2rem] leading-relaxed text-[#454547]`}
>
{about_page?.introduction_description_headText}
</p>
<br />
<br />
<ul className=" space-y-2 lg:text-[1.2rem] text-[#454547]">
<div
className="introduction_description_subText"
} lg:text-[1.2rem] leading-relaxed text-[#454547] textResult`}
dangerouslySetInnerHTML={{
__html: about_page?.introduction_description_subText,
__html: filteredTranslations?.aboutUsText ?? "",
}}
/>
</ul>
) : (
""
)}
</div>
</div>
{/* <div className="grid grid-cols-12 gap-5 mt-5">
<div className="col-span-5">
<div className="custom-shadow p-10 rounded-lg">
<form action="" className="space-y-4">
<div className="space-y-2 flex flex-col">
<label htmlFor="">نام شما</label>
<input
type="text"
placeholder=""
className="border-[1px] border-neutral-400 rounded-lg py-2 px-4"
/>
</div>
<div className="space-y-2 flex flex-col">
<label htmlFor="">ایمیل شما</label>
<input
type="text"
placeholder=""
className="border-[1px] border-neutral-400 rounded-lg py-2 px-4"
/>
</div>
<div className="space-y-2 flex flex-col">
<label htmlFor="">پیام شما</label>
<textarea
rows={4}
placeholder=""
className="border-[1px] border-neutral-400 rounded-lg py-2 px-4"
></textarea>
</div>
<div className="space-y-2 flex flex-col">
<button className="flex items-center justify-center py-4 rounded-lg text-center text-sm font-semibold bg-blue-primary text-white">
ارسال
</button>
</div>
</form>
</div>
</div>
<div className="col-span-7">
<div className="custom-shadow p-10 rounded-lg"></div>
</div>
</div> */}
</section>
<section className="lg:mb-24 mb-10 lg:mt-10 mt-4 container ">
<div className="rounded-4xl py-16">
@@ -114,79 +141,43 @@ export default async function Page({
</p>
</div>
<div className="grid grid-cols-3 gap-x-10 lg:gap-y-0 gap-y-4 mt-10 lg:h-[600px]">
<div className="lg:col-span-1 col-span-3 rounded-2xl border-[1px] border-neutral-200 overflow-hidden relative">
<div className="lg:h-[430px] h-[300px] relative before:absolute before:bg-gradient-to-t before:from-white before:to-transparent before:z-10 before:w-full before:h-[70%] before:bottom-0 before:right-0 overflow-hidden">
<Image src="/jafarian.jpg" fill alt=""
className="object-fill hover:scale-110 transition-all duration-300"
{getmembers?.data?.length > 0
? getmembers.data.map((item, index) => {
const filteredDepartmentMembersTranslations =
item.translations.find((item) => item.lang?.slug === lang);
return (
<div
key={index}
className="lg:col-span-1 col-span-3 rounded-2xl border-[1px] border-neutral-200 overflow-hidden relative"
>
<div className="lg:h-[430px] h-[300px] hover:scale-110 transition-all duration-300 relative before:absolute before:bg-gradient-to-t before:from-white before:to-transparent before:z-10 before:w-full before:h-[70%] before:bottom-0 before:right-0 overflow-hidden">
<Image
src={item.image?.fileUrl ?? "/"}
fill
alt=""
className="object-fill "
/>
</div>
<div className=" h-full px-6 py-10 space-y-2 ">
<h3 className="lg:text-2xl text-lg font-black text-black">
{about_page?.dr_jafarian?.name}
{filteredDepartmentMembersTranslations?.firstName}{" "}
{filteredDepartmentMembersTranslations?.lastName}
</h3>
<div>
<span className="text-secondary lg:text-xl font-medium">
{about_page?.dr_jafarian?.expertise}
{filteredDepartmentMembersTranslations?.excerpt}
</span>
</div>
<div>
<span className="text-black lg:text-base text-sm font-medium">
{about_page?.dr_jafarian?.position}
</span>
</div>
</div>
</div>
<div className="lg:col-span-1 col-span-3 rounded-2xl border-[1px] border-neutral-200 overflow-hidden relative">
<div className="lg:h-[430px] h-[300px] relative before:absolute before:bg-gradient-to-t before:from-white before:to-transparent before:z-10 before:w-full before:h-[70%] before:bottom-0 before:right-0 overflow-hidden">
<Image
src="/motamedi.jpg"
fill
alt=""
className="object-fill hover:scale-110 transition-all duration-300"
/>
</div>
<div className=" h-full px-6 py-10 space-y-2 ">
<h3 className="text-2xl font-black text-black">
{about_page?.dr_motamedi?.name}
</h3>
<div>
<span className="text-secondary text-xl font-medium">
{about_page?.dr_motamedi?.expertise}
</span>
</div>
<div>
<span className="text-black font-medium">
{about_page?.dr_motamedi?.position}
</span>
</div>
</div>
</div>
<div className="lg:col-span-1 col-span-3 rounded-2xl border-[1px] border-neutral-200 overflow-hidden relative">
<div className="lg:h-[430px] h-[300px] relative before:absolute before:bg-gradient-to-t before:from-white before:to-transparent before:z-10 before:w-full before:h-[70%] before:bottom-0 before:right-0 overflow-hidden">
<Image
src="/heidarnejad.jpg"
fill
alt=""
className="object-fill hover:scale-110 transition-all duration-300"
/>
</div>
<div className=" h-full px-6 py-10 space-y-2 ">
<h3 className="text-2xl font-black text-black">
{about_page?.heidarnejad?.name}
</h3>
<div>
<span className="text-secondary text-xl font-medium">
{about_page?.heidarnejad?.expertise}
</span>
</div>
<div>
<span className="text-black font-medium">
{about_page?.heidarnejad?.position}
{filteredDepartmentMembersTranslations?.position}
</span>
</div>
</div>
</div>
);
})
: ""}
</div>
</div>
</section>

View File

@@ -5,9 +5,41 @@ import {getDictionary} from "../dictionaries";
import { pages_titles } from "@/constants";
import { Metadata } from "next";
export const metadata: Metadata = {
title: pages_titles.patient_rights_charter['fa'] + ' | ' +'بیمارستان شمال',
title: pages_titles.patient_rights_charter["fa"] + " | " + "بیمارستان شمال",
description: "Shomal Hospital IPD patient rights charter page",
};
interface patientRightsResponseType {
status: number;
data: {
translations: {
patientsRights: string | null;
language: {
title: string;
slug: string;
};
}[];
};
message: string;
}
async function getData(): Promise<patientRightsResponseType> {
const res = await fetch(
`http://localhost:3500/api/v1/public-apis/get/human-rights`,
{
cache: "no-cache",
},
);
if (!res.ok) {
throw new Error("Error");
}
const data = await res.json();
return data;
}
export default async function PatientRights({
params,
}: {
@@ -15,6 +47,13 @@ export default async function PatientRights({
}) {
const { lang } = await params;
const { patient_rights_charter } = await getDictionary(lang);
const getdata = await getData();
const filteredData =
getdata.data.translations.length > 0
? getdata?.data?.translations.find((item) => item.language.slug === lang)
: null;
return (
<>
<PageHeaderSlider
@@ -28,46 +67,16 @@ export default async function PatientRights({
{patient_rights_charter?.page_title}
</h3>
<div className="mt-16">
<h4 className="text-justify text-[1.2rem] leading-relaxed text-secondary font-medium">
{patient_rights_charter?.section_one?.title}
</h4>
<br />
<br />
{filteredData ? (
<div
className="introduction_description_subText "
className="textResult"
dangerouslySetInnerHTML={{
__html: patient_rights_charter?.section_one?.content,
}}
/>
<h4 className="text-justify text-[1.2rem] leading-relaxed text-secondary font-medium my-10">
{patient_rights_charter?.section_two?.title}
</h4>
<div
className="introduction_description_subText"
dangerouslySetInnerHTML={{
__html: patient_rights_charter?.section_two?.content,
}}
/>
<h4 className="text-justify text-[1.2rem] leading-relaxed text-secondary font-medium my-10">
{patient_rights_charter?.section_three?.title}
</h4>
<div
className="introduction_description_subText"
dangerouslySetInnerHTML={{
__html: patient_rights_charter?.section_three?.content,
}}
/>
<h4 className="text-justify text-[1.2rem] leading-relaxed text-secondary font-medium my-10">
{patient_rights_charter?.section_four?.title}
</h4>
<div
className="introduction_description_subText"
dangerouslySetInnerHTML={{
__html: patient_rights_charter?.section_four?.content,
__html: filteredData.patientsRights ?? "",
}}
/>
) : (
""
)}
</div>
</div>
</section>

View File

@@ -4,17 +4,78 @@ import {getDictionary} from "../dictionaries";
import { languages_types } from "@/types";
import { Metadata } from "next";
import { pages_titles } from "@/constants";
import { toPersianNumber } from "@/utils/functions";
export const metadata: Metadata = {
title: pages_titles.pricing["fa"] + " | " + "بیمارستان شمال",
description: "Shomal Hospital IPD Pricing page",
};
interface medicalPackagesDataType {
id: number;
translations: (
| {
title: string;
lang: {
title: string;
slug: string;
} | null;
}
| {
title: string;
lang: {
title: string;
slug: string;
} | null;
}
)[];
icon: string | null;
priority: number | null;
price: string;
parent_id: number | null;
parent: {
translations: {
title: string;
lang: {
title: string;
slug: string;
} | null;
}[];
} | null;
children: medicalPackagesDataType[];
}
interface medicalPackagesResponseType {
status: number;
data: medicalPackagesDataType[];
message: string;
}
async function getData(lang: string): Promise<medicalPackagesResponseType> {
const res = await fetch(
`http://localhost:3500/api/v1/public-apis/get/pricing/${lang ?? ""}`,
{
cache: "no-cache",
},
);
if (!res.ok) {
throw new Error("Error");
}
const data = await res.json();
return data;
}
export default async function PricingPage({
params,
}: {
params: Promise<{ lang: languages_types }>;
}) {
const { lang } = await params;
const {pricing_page, medical_packages} = await getDictionary(lang);
const { pricing_page } = await getDictionary(lang);
const getdata = await getData(lang);
return (
<>
<PageHeaderSlider
@@ -43,224 +104,58 @@ export default async function PricingPage({
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
<tr>
<td rowSpan={3} className="px-4 py-2 font-medium text-center">
{medical_packages[0].package_name}
</td>
<td className="px-4 py-2">
{medical_packages[0].services[0].service_name}
</td>
<td className="px-4 py-2 text-center">350 600</td>
</tr>
<tr>
<td className="px-4 py-2 ">
{medical_packages[0].services[1].service_name}
</td>
<td className="px-4 py-2 text-center">450 800</td>
</tr>
<tr>
<td className="px-4 py-2 ">
{medical_packages[0].services[2].service_name}
</td>
<td className="px-4 py-2 text-center">750 1,200</td>
</tr>
<tr>
<td rowSpan={2} className="px-4 py-2 font-medium text-center">
{medical_packages[1].package_name}
</td>
<td className="px-4 py-2">
{medical_packages[1].services[0].service_name}
</td>
<td className="px-4 py-2 text-center">3,800 6,000</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[1].services[1].service_name}
</td>
<td className="px-4 py-2 text-center">4,500 7,000</td>
</tr>
<tr>
<td rowSpan={8} className="px-4 py-2 font-medium text-center">
{medical_packages[2].package_name}
</td>
<td className="px-4 py-2">
{medical_packages[2].services[0].service_name}
</td>
<td className="px-4 py-2 text-center">900 1,300</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[2].services[1].service_name}
</td>
<td className="px-4 py-2 text-center">3,200 4,200</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[2].services[2].service_name}
</td>
<td className="px-4 py-2 text-center">1,500 2,000</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[2].services[3].service_name}
</td>
<td className="px-4 py-2 text-center">3,000 6,000</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[2].services[4].service_name}
</td>
<td className="px-4 py-2 text-center">9,000 12,000</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[2].services[5].service_name}
</td>
<td className="px-4 py-2 text-center">7,000 10,000</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[2].services[6].service_name}
</td>
<td className="px-4 py-2 text-center">550 4,500</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[2].services[7].service_name}
</td>
<td className="px-4 py-2 text-center">5,500-9,000</td>
</tr>
<tr>
<td rowSpan={9} className="px-4 py-2 font-medium text-center">
{medical_packages[3].package_name}
</td>
<td className="px-4 py-2">
{medical_packages[3].services[0].service_name}
</td>
<td className="px-4 py-2 text-center">1,800 2,800</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[3].services[1].service_name}
</td>
<td className="px-4 py-2 text-center">2,500 3,500</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[3].services[2].service_name}
</td>
<td className="px-4 py-2 text-center">1,000 1,600</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[3].services[3].service_name}
</td>
<td className="px-4 py-2 text-center">3,500 5,000</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[3].services[4].service_name}
</td>
<td className="px-4 py-2 text-center">2,800 3,800</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[3].services[5].service_name}
</td>
<td className="px-4 py-2 text-center">2,500 3,200</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[3].services[6].service_name}
</td>
<td className="px-4 py-2 text-center">1,000 1,500</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[3].services[7].service_name}
</td>
<td className="px-4 py-2 text-center">4,500 - 7,000</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[3].services[8].service_name}
</td>
<td className="px-4 py-2 text-center">800 1,200</td>
</tr>
<tr>
{getdata.data.flatMap((item) =>
item.children.length > 0 ? (
item.children.map((item2, index) => (
<tr key={item2.id}>
{index + 1 === 1 && (
<td
rowSpan={2}
rowSpan={item.children.length}
className="px-4 py-2 font-medium text-center"
>
{medical_packages[4].package_name}
{
item2.parent?.translations.find(
(translate) => translate.lang?.slug === lang,
)?.title
}
</td>
)}
<td className="px-4 py-2">
{medical_packages[4].services[0].service_name}
{
item2.translations.find(
(p) => p.lang?.slug === lang,
)?.title
}
</td>
<td className="px-4 py-2 text-center">6,600 - 11,700</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[4].services[1].service_name}
</td>
<td className="px-4 py-2 text-center">5,100 - 9,000</td>
</tr>
<tr>
<td rowSpan={6} className="px-4 py-2 font-medium text-center">
{medical_packages[5].package_name}
<td className="px-4 py-2 text-center">
{lang === "fa" || lang === "ar"
? toPersianNumber(item2.price)
: item2.price}
</td>
</tr>
<tr></tr>
<tr>
<td className="px-4 py-2">
{medical_packages[5].services[2].service_name}
</td>
<td className="px-4 py-2 text-center">4,500 - 9,000</td>
</tr>
<tr></tr>
<tr></tr>
<tr>
<td className="px-4 py-2">
{medical_packages[5].services[5].service_name}
</td>
<td className="px-4 py-2 text-center">10,000 - 35,000</td>
</tr>
<tr>
<td rowSpan={4} className="px-4 py-2 font-medium text-center">
{medical_packages[6].package_name}
</td>
<td className="px-4 py-2">
{medical_packages[6].services[0].service_name}
))
) : (
<tr key={item.id}>
<td
rowSpan={1}
className="px-4 py-2 font-medium text-center"
>
{
item.translations.find(
(translate) => translate.lang?.slug === lang,
)?.title
}
</td>
<td className="px-4 py-2 text-center">1,200 1,800</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[6].services[1].service_name}
<td className="px-4 py-2"></td>
<td className="px-4 py-2 text-center">
{lang === "fa" || lang === "ar"
? toPersianNumber(item.price)
: item.price}
</td>
<td className="px-4 py-2 text-center">1,000 1,500</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[6].services[2].service_name}
</td>
<td className="px-4 py-2 text-center">1,000 1,400</td>
</tr>
<tr>
<td className="px-4 py-2">
{medical_packages[6].services[3].service_name}
</td>
<td className="px-4 py-2 text-center">1,500 2,500</td>
</tr>
),
)}
</tbody>
</table>
</div>

View File

@@ -129,3 +129,8 @@ td {
p {
@apply leading-relaxed my-4;
}
.textResult ul,.textResult ol{
@apply list-disc list-inside marker:text-secondary marker:text-2xl space-y-6 text-[#454547];
}

View File

@@ -1,5 +1,4 @@
import {contact_us_form_types} from "@/types";
export const site_languages = [
{
label: "English",

17
src/hooks/index.ts Normal file
View File

@@ -0,0 +1,17 @@
import { getDefaultInfo, getLanguages } from "@/services/requests";
import { useQuery } from "@tanstack/react-query";
export const useGetLanguages = () => useQuery({
queryKey:['get-languages'],
queryFn:getLanguages,
retry:false,
refetchOnWindowFocus:false
})
export const useGetDefaultInfo = () =>useQuery({
queryKey:['get-default-info'],
queryFn:getDefaultInfo,
retry:false,
refetchOnWindowFocus:false,
})

View File

@@ -1,32 +1,31 @@
import { NextResponse } from "next/server";
const locales = ["en", "fa","ar"];
const DEFAULT_LOCALE = "fa"; // fallback امن
export function middleware(request) {
const { pathname } = request.nextUrl;
if (
pathname.startsWith("/_next") ||
pathname.startsWith("/favicon.ico") ||
pathname.startsWith("/robots.txt") ||
pathname.match(/^\/.*\.(png|jpg|jpeg|gif|svg|webp|ico)$/)
pathname.startsWith("/api") ||
pathname.match(/\.(.*)$/)
) {
return NextResponse.next();
}
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
if (pathnameHasLocale) return NextResponse.next();
const segments = pathname.split("/");
const maybeLang = segments[1];
const locale = "fa";
const url = request.nextUrl.clone();
url.pathname = `/${locale}${pathname}`;
return NextResponse.redirect(url);
// فقط چک کن آیا prefix داره یا نه
if (!maybeLang) {
return NextResponse.redirect(
new URL(`/${DEFAULT_LOCALE}${pathname}`, request.url)
);
}
// export const config = {
// matcher: [
// "/((?!_next|favicon.ico|robots.txt|.*\\.(png|jpg|jpeg|gif|svg|webp|ico)).*)",
// ],
// };
return NextResponse.next();
}
export const config = {
matcher: ["/((?!_next|.*\\..*).*)"],
};

View File

@@ -0,0 +1,25 @@
import axios from "axios";
const publicApiInstance = axios.create({
baseURL: 'http://localhost:3500/api/v1',
withCredentials: true,
});
publicApiInstance.interceptors.request.use(
(res) => res,
(err) => Promise.reject(err)
);
publicApiInstance.interceptors.response.use(
(res) => res,
async (err) => Promise.reject(err)
);
const publicApi = {
post: publicApiInstance.post,
get: publicApiInstance.get,
put: publicApiInstance.put,
delete: publicApiInstance.delete,
};
export default publicApi;

View File

@@ -0,0 +1,28 @@
import axios from "axios";
const CDNCallAxios = axios.create({
baseURL: "http://localhost:4000",
withCredentials: true,
});
CDNCallAxios.interceptors.request.use(
(config) => {
return config;
},
(error) => Promise.reject(error),
);
CDNCallAxios.interceptors.response.use(
(res) => res,
(error) => Promise.reject(error),
);
const CDNCall = {
get: CDNCallAxios.get,
post: CDNCallAxios.post,
put: CDNCallAxios.put,
patch: CDNCallAxios.patch,
delete: CDNCallAxios.delete,
};
export default CDNCall;

View File

@@ -0,0 +1,8 @@
import publicApi from "../api/call-api";
export async function getLanguages() {
return await publicApi.get("/language/get/all").then((res) => res.data);
}
export async function getDefaultInfo() {
return await publicApi.get("/default-info/get/all").then((res) => res.data);
}

View File

@@ -0,0 +1,20 @@
"use client";
import { createContext, useContext } from "react";
type Language = {
title: string;
slug: string;
};
export const LanguageContext = createContext<Language[] | null>(null);
export const useLanguages = () => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error("useLanguages must be used inside LanguageProvider");
}
return context;
};

View File

@@ -0,0 +1,20 @@
"use client";
import { LanguageContext } from "./LanguageContext";
type Language = {
title: string;
slug: string;
};
export default function LanguageProvider({
children,
languages,
}: {
children: React.ReactNode;
languages: Language[];
}) {
return (
<LanguageContext.Provider value={languages}>
{children}
</LanguageContext.Provider>
);
}

View File

@@ -0,0 +1,54 @@
"use client";
import { usePathname, useRouter } from "next/navigation";
import { useLanguages } from "./LanguageContext";
import React from "react";
export default function LanguageSwitcher() {
const languages = useLanguages();
const pathname = usePathname();
const router = useRouter();
const removeLangPrefix = (path: string) => {
const segments = path.split("/");
segments.splice(1, 1);
return segments.join("/") || "/";
};
const handleChangeLanguage = (slug: string) => {
const cleanPath = removeLangPrefix(pathname);
document.cookie = `NEXT_LOCALE=${slug}; path=/`;
router.push(`/${slug}${cleanPath}`);
};
return (
<div className="md:text-white text-sm flex flex-wrap items-center md:justify-end justify-center gap-x-4 border-[1px] md:border-white md:px-4 py-1.5 rounded-full md:mt-0 mt-4">
{languages.map((lang) => (
<React.Fragment key={lang.slug}>
<span>
<button
key={lang.slug}
onClick={() => handleChangeLanguage(lang.slug)}
className="cursor-pointer"
>
{lang.slug.toUpperCase()}
</button>
</span>
<span> | </span>
</React.Fragment>
))}
<svg
width="15"
height="15"
viewBox="0 0 25 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.5 25C10.7917 25 9.17708 24.6719 7.65625 24.0156C6.13542 23.3594 4.80729 22.4635 3.67188 21.3281C2.53646 20.1927 1.64063 18.8646 0.984375 17.3438C0.328125 15.8229 0 14.2083 0 12.5C0 10.7708 0.328125 9.15104 0.984375 7.64063C1.64063 6.13021 2.53646 4.80729 3.67188 3.67188C4.80729 2.53646 6.13542 1.64063 7.65625 0.984375C9.17708 0.328125 10.7917 0 12.5 0C14.2292 0 15.849 0.328125 17.3594 0.984375C18.8698 1.64063 20.1927 2.53646 21.3281 3.67188C22.4635 4.80729 23.3594 6.13021 24.0156 7.64063C24.6719 9.15104 25 10.7708 25 12.5C25 14.2083 24.6719 15.8229 24.0156 17.3438C23.3594 18.8646 22.4635 20.1927 21.3281 21.3281C20.1927 22.4635 18.8698 23.3594 17.3594 24.0156C15.849 24.6719 14.2292 25 12.5 25ZM12.5 22.4375C13.0417 21.6875 13.5104 20.9063 13.9063 20.0938C14.3021 19.2813 14.625 18.4167 14.875 17.5H10.125C10.375 18.4167 10.6979 19.2813 11.0938 20.0938C11.4896 20.9063 11.9583 21.6875 12.5 22.4375ZM9.25 21.9375C8.875 21.25 8.54688 20.5365 8.26563 19.7969C7.98438 19.0573 7.75 18.2917 7.5625 17.5H3.875C4.47917 18.5417 5.23438 19.4479 6.14063 20.2188C7.04688 20.9896 8.08333 21.5625 9.25 21.9375ZM15.75 21.9375C16.9167 21.5625 17.9531 20.9896 18.8594 20.2188C19.7656 19.4479 20.5208 18.5417 21.125 17.5H17.4375C17.25 18.2917 17.0156 19.0573 16.7344 19.7969C16.4531 20.5365 16.125 21.25 15.75 21.9375ZM2.8125 15H7.0625C7 14.5833 6.95313 14.1719 6.92188 13.7656C6.89063 13.3594 6.875 12.9375 6.875 12.5C6.875 12.0625 6.89063 11.6406 6.92188 11.2344C6.95313 10.8281 7 10.4167 7.0625 10H2.8125C2.70833 10.4167 2.63021 10.8281 2.57813 11.2344C2.52604 11.6406 2.5 12.0625 2.5 12.5C2.5 12.9375 2.52604 13.3594 2.57813 13.7656C2.63021 14.1719 2.70833 14.5833 2.8125 15ZM9.5625 15H15.4375C15.5 14.5833 15.5469 14.1719 15.5781 13.7656C15.6094 13.3594 15.625 12.9375 15.625 12.5C15.625 12.0625 15.6094 11.6406 15.5781 11.2344C15.5469 10.8281 15.5 10.4167 15.4375 10H9.5625C9.5 10.4167 9.45313 10.8281 9.42188 11.2344C9.39063 11.6406 9.375 12.0625 9.375 12.5C9.375 12.9375 9.39063 13.3594 9.42188 13.7656C9.45313 14.1719 9.5 14.5833 9.5625 15ZM17.9375 15H22.1875C22.2917 14.5833 22.3698 14.1719 22.4219 13.7656C22.474 13.3594 22.5 12.9375 22.5 12.5C22.5 12.0625 22.474 11.6406 22.4219 11.2344C22.3698 10.8281 22.2917 10.4167 22.1875 10H17.9375C18 10.4167 18.0469 10.8281 18.0781 11.2344C18.1094 11.6406 18.125 12.0625 18.125 12.5C18.125 12.9375 18.1094 13.3594 18.0781 13.7656C18.0469 14.1719 18 14.5833 17.9375 15ZM17.4375 7.5H21.125C20.5208 6.45833 19.7656 5.55208 18.8594 4.78125C17.9531 4.01042 16.9167 3.4375 15.75 3.0625C16.125 3.75 16.4531 4.46354 16.7344 5.20313C17.0156 5.94271 17.25 6.70833 17.4375 7.5ZM10.125 7.5H14.875C14.625 6.58333 14.3021 5.71875 13.9063 4.90625C13.5104 4.09375 13.0417 3.3125 12.5 2.5625C11.9583 3.3125 11.4896 4.09375 11.0938 4.90625C10.6979 5.71875 10.375 6.58333 10.125 7.5ZM3.875 7.5H7.5625C7.75 6.70833 7.98438 5.94271 8.26563 5.20313C8.54688 4.46354 8.875 3.75 9.25 3.0625C8.08333 3.4375 7.04688 4.01042 6.14063 4.78125C5.23438 5.55208 4.47917 6.45833 3.875 7.5Z"
fill="#E8EAED"
/>
</svg>
</div>
);
}

View File

@@ -3,7 +3,7 @@ import {languages_types} from "@/types";
import {usePathname, useRouter} from "next/navigation";
import React from "react";
export default function LanguagesChanger({lang}: {lang: languages_types}) {
export default function LanguagesChanger() {
const pathname = usePathname();
const router = useRouter();
const removeLangPrefix = (path: string) => {

View File

@@ -0,0 +1,14 @@
import React from "react";
export default async function TopNavbarEmail({email}:{email:string}) {
return (
<>
<span className="text-[#e6e6e6] group-hover:text-white lg:text-base text-sm">
<a href={`mailto:${email}`}>{email}</a>
</span>
</>
);
}

View File

@@ -129,7 +129,7 @@ export default function MobileMenu({lang}: {lang: languages_types}) {
)}
</div>
))}
<LanguagesChanger lang={lang} />
<LanguagesChanger />
<div className="col-span-4 flex flex-col items-center justify-center gap-y-2 mt-3">

View File

@@ -20,7 +20,7 @@ export default function DoctorsFilterBox({
select_this: string;
};
}) {
const [name, setName] = useState(defaultName);
const [name] = useState(defaultName);
const [expertise, setExpertise] = useState(defaultExpertise);
const router = useRouter();

View File

@@ -1,9 +1,28 @@
import React from "react";
import LanguagesChanger from "../components/LanguagesChanger";
import { languages_types } from "@/types";
import {default_info} from "@/constants";
import TopNavbarEmail from "../components/TopNavbarEmail";
import LanguageSwitcher from "../components/LanguageSwitcher";
async function getDefaultInfoData() {
const res = await fetch(`${process.env.API_URL}/public-apis/top-navbar`, {
next: { revalidate: 60 },
});
export default function TopNavbar({lang}: {lang: languages_types}) {
if (!res.ok && res.status === 500) {
throw new Error("Fail to get data");
}
if (!res.ok && res.status === 404) {
return null;
}
const data = await res.json();
return data;
}
export default async function TopNavbar({ lang }: { lang: languages_types }) {
const getdata = await getDefaultInfoData();
const { data } = getdata;
return (
<div className="bg-blue-primary">
<div className="py-2 container flex items-center justify-between w-full">
@@ -22,9 +41,7 @@ export default function TopNavbar({lang}: {lang: languages_types}) {
/>
</svg>
</span>
<span className="text-[#e6e6e6] group-hover:text-white lg:text-base text-sm">
<a href={`mailto:${default_info.email}`}>{default_info.email}</a>
</span>
<TopNavbarEmail email={data?.email} />
</div>
<div className="lg:flex hidden items-center justify-end gap-x-4 ">
{/* <a
@@ -46,7 +63,7 @@ export default function TopNavbar({lang}: {lang: languages_types}) {
</a>
<span className="text-white"> | </span> */}
<a href="https://instagram.com/shomalhospital">
<a href={data?.instagram}>
<svg
width="21"
height="21"
@@ -62,7 +79,7 @@ export default function TopNavbar({lang}: {lang: languages_types}) {
</a>
<span className="text-white"> | </span>
<LanguagesChanger lang={lang} />
<LanguageSwitcher />
</div>
</div>
</div>