Compare commits

..

20 Commits

Author SHA1 Message Date
c7dcb7e7b4 Merge pull request 'dev' (#14) from dev into main
Reviewed-on: #14
2025-03-10 17:17:37 +00:00
76c39f214c fix: header dropdown bg color 2025-03-11 00:16:50 +07:00
dbf89a93e5 fix: some header, footer, hero element's style 2025-03-10 23:59:36 +07:00
9bfe2fc700 fix: google review text styling 2025-03-10 23:25:27 +07:00
ad39804e06 Merge branch 'main' into dev 2025-03-10 23:09:36 +07:00
ba72910f98 Merge branch 'dev' of https://giteadev3.link/rankrunners-dev/next-cochise into dev 2025-03-10 22:28:05 +07:00
8ef5545f93 fix: category page and blog card styling 2025-03-10 22:27:56 +07:00
d010f6a965 update icon contact us 2025-03-10 21:08:44 +07:00
Val
b5931a5612 fix: section title color 2025-03-10 19:14:51 +07:00
fa11bb928e Merge branch 'dev' of https://giteadev3.link/rankrunners-dev/next-cochise into dev 2025-03-10 16:54:26 +07:00
e8cb280326 fix: meta and jsonld tags 2025-03-10 16:54:22 +07:00
18e90ecbf2 fix: change pages cannonical url to canonical url 2025-03-10 11:30:59 +07:00
879f156f27 update space 2025-03-10 10:22:51 +07:00
312c891dda update size font font 2025-03-10 10:15:00 +07:00
ae877c44a1 upadate footer 2025-03-10 09:58:59 +07:00
e1e328f79a update footer 2025-03-10 09:26:36 +07:00
86c8e2a929 fix: home page jsonLd and move bg video 2025-03-08 17:48:10 +07:00
b064367fd7 fix: meta tags and jsonld 2025-03-07 23:17:40 +07:00
f0c7dc58e7 Merge branch 'dev' of https://giteadev3.link/rankrunners-dev/next-cochise into dev 2025-03-06 14:19:53 +07:00
06b682bee6 fix: remove env 2025-03-06 14:19:48 +07:00
30 changed files with 586 additions and 131 deletions

17
env
View File

@ -1,17 +0,0 @@
SERVICE_SUPABASESERVICE_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTczODQ3MzA2MCwiZXhwIjo0ODk0MTQ2NjYwLCJyb2xlIjoic2VydmljZV9yb2xlIn0.nRpZfD6Uur9yzCT_BTgLH9DLWsCcCiAgWKenJ_qYu5M\
SERVICE_SUPABASEANON_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTczODQ3MzA2MCwiZXhwIjo0ODk0MTQ2NjYwLCJyb2xlIjoiYW5vbiJ9.WXJvhzD1gD6Kxlq8PLFAcpaVFmh9h3xbJHh9oow2IFQ
SUPABASE_URL=https://supabasekong-n00g8kwoos4skc0gw44k8sks.dev3vds1.link
# payload
DATABASE_URI=postgresql://postgres.jswmbraeandqttpcdfmj:qDY4C35XaRsmW6dW@aws-0-ap-southeast-1.pooler.supabase.com:5432/postgres
PAYLOAD_SECRET=1b7204f9ed5f7ab09706fc51
S3_BUCKET=cochise-bucket
S3_ACCESS_KEY_ID=97394cd503ae9973efa25168ed980e51
S3_SECRET_ACCESS_KEY=9b4e412850582aab32ffdadbe23bb7ea972b39aaf79c34da0c19d3fcafe11dd0
S3_REGION=ap-southeast-1
S3_ENDPOINT=https://jswmbraeandqttpcdfmj.supabase.co/storage/v1/s3
NEXT_PUBLIC_PAYLOAD_URL=http://localhost:3000
SITE_URL=http://localhost:3000

View File

@ -11,7 +11,7 @@
--color-gray-1: #697582;
--color-gray-2: #747f8c;
--color-gray-3: #8a95a2;
--color-primary-1: #1872e8;
--color-primary-1: #00898b;
--color-primary-1-a: #2b87ff;
--color-primary-light-1: #e3effe;
--color-primary-light-1-a: #bcd1f1;

View File

@ -191,7 +191,7 @@ Primary use: Multipurpose Template
--color-gray-light-6: #bbb;
--color-dark-mode-gray-1: rgba(255, 255, 255, 0.7);
--color-dark-mode-gray-2: rgba(255, 255, 255, 0.1275);
--color-primary-1: #4567ed;
--color-primary-1: #00898b;
--color-primary-1-a: #375ae3;
--color-primary-light-1: #e3effe;
--color-primary-light-1-a: #bcd1f1;

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -16,6 +16,7 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
let updatedAt = "";
let imgUrl = "";
let createdByName = "";
let canonicalUrl = "";
const slug = (await params).slug;
const blog = await fetchBlogDetail(slug);
@ -27,6 +28,9 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
imgUrl = blog.imgUrl;
publishedAt = blog.data.createdAt;
updatedAt = blog.data.updatedAt;
if (!!blog.data?.meta?.canonical_url) {
canonicalUrl = blog.data.meta.canonical_url;
}
if (!!blog?.data?.createdBy && typeof blog.data.createdBy !== "number") {
createdByName = blog.data.createdBy?.name ?? "";
}
@ -39,7 +43,9 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
imgUrl = page.heroImg?.url;
publishedAt = page.createdAt;
updatedAt = page.updatedAt;
if (!!page.meta?.canonical_url) {
canonicalUrl = page.meta.canonical_url;
}
if (!!page?.createdBy && typeof page.createdBy !== "number") {
createdByName = page?.createdBy?.name ?? "";
}
@ -55,6 +61,9 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
defaultMetadata.openGraph.description = description;
defaultMetadata.openGraph.images = !!imgUrl ? [imgUrl] : undefined;
}
if (!!defaultMetadata.alternates && !!canonicalUrl) {
defaultMetadata.alternates.canonical = canonicalUrl;
}
defaultMetadata.twitter = {
card: "summary_large_image",
title: title,

View File

@ -0,0 +1,74 @@
import { BeforeFooterBlock } from "@/components/Blocks/BeforeFooter";
import { BlogCardItemSkeleton } from "@/components/Blogs/BlogCardItem";
import Blogs from "@/components/Blogs/Blogs";
import HeroOther from "@/components/HeroOther";
import { getDefaultMetadata } from "@/utils/metadata";
import { sanitizePageNumber } from "@/utils/sanitize";
import { Metadata } from "next";
// import { headers } from "next/headers";
import { Suspense } from "react";
export async function generateMetadata(): Promise<Metadata> {
const metadata = await getDefaultMetadata();
return metadata;
}
export default async function CategoryPage({
params,
searchParams,
}: {
params?: Promise<{ path: string[] }>;
searchParams?: Promise<{ page?: string; s?: string }>;
}) {
const path = (await params)?.path;
// const headersList = await headers();
// const paramsCategory = path?.[0] ?? "";
const paramsPage = path?.[2] ?? "";
const paramsSearch = (await searchParams)?.s;
const page = sanitizePageNumber(paramsPage);
return (
<>
<HeroOther title="Blog" />
<section className="page-section" id="blog">
<div className="w-full max-w-7xl mx-auto px-4 md:px-8">
<form action={`category/chemotherapy/page/1`} method="GET">
<div className="input-group">
<input
type="text"
name="s"
defaultValue={paramsSearch ?? ""}
placeholder="Search blog..."
className="input-lg input-circle form-control h-12"
/>
<div className="input-group-append px-2">
<button className="btn btn-info text-white h-12 px-5" type="submit">
Search
</button>
</div>
</div>
</form>
</div>
<Suspense fallback={<Loading />}>
<Blogs preset="categories" page={page} search={paramsSearch} />
</Suspense>
</section>
<BeforeFooterBlock />
</>
);
}
function Loading() {
return (
<div className="container position-relative">
<div className="row">
<BlogCardItemSkeleton />
<BlogCardItemSkeleton />
<BlogCardItemSkeleton />
</div>
</div>
);
}

View File

@ -1,38 +1,139 @@
import Parallax from "./home-bg-video/page";
import Homepage from "@/components/Homepage";
import { BeforeFooterBlock } from "@/components/Blocks/BeforeFooter";
import { Metadata, Viewport } from "next";
import { getDefaultMetadata } from "@/utils/metadata";
import HomeBgVideo from "@/components/HomeBgVideo";
import { headers } from "next/headers";
export const metadata = {
title: "HomePage - Cochise Oncology",
description: "Cochise Oncology",
keywords: "Cochise Oncology, Oncology, Healthcare, Medical Services, Cancer Treatment",
author: "Cochise Oncology",
robots: "index, follow",
og: {
title: "HomePage - Cochise Oncology",
description: "Cochise Oncology",
type: "website",
url: "https://www.cochiseoncology.com",
image: "https://www.cochiseoncology.com/og-image.jpg",
},
twitter: {
card: "summary_large_image",
site: "@CochiseOncology",
title: "HomePage - Cochise Oncology",
description: "Cochise Oncology",
image: "https://www.cochiseoncology.com/twitter-image.jpg",
},
};
export async function generateMetadata(): Promise<Metadata> {
const defaultMetadata = await getDefaultMetadata();
defaultMetadata.title = `Homepage - ${defaultMetadata.openGraph?.siteName}`;
defaultMetadata.keywords = "Cochise Oncology, Oncology, Healthcare, Medical Services, Cancer Treatment";
export const viewport = {
if (!!defaultMetadata?.openGraph) {
defaultMetadata.openGraph.images = ["/assets/images/cochise-welcome.png"];
}
return defaultMetadata;
}
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
};
export default function Home() {
export default async function Home() {
const headersList = await headers();
const fullUrl = headersList.get("x-full-url");
const siteName = headersList.get("x-site-name");
const metaDesc = headersList.get("x-meta-desc");
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebPage",
"@id": fullUrl,
url: fullUrl,
name: `Homepage - ${siteName}`,
isPartOf: {
"@id": `${fullUrl}#website`,
},
about: {
"@id": `${fullUrl}#organization`,
},
primaryImageOfPage: {
"@id": `${fullUrl}#primaryimage`,
},
image: {
"@id": `${fullUrl}#primaryimage`,
},
thumbnailUrl: [`${fullUrl}assets/images/cochise-welcome.png`],
datePublished: "2024-07-18T16:29:29+00:00",
dateModified: "2025-02-24T21:01:50+00:00",
description: metaDesc,
breadcrumb: {
"@id": `${fullUrl}#breadcrumb`,
},
inLanguage: "en-US",
potentialAction: [
{
"@type": "ReadAction",
target: [fullUrl],
},
],
},
{
"@type": "ImageObject",
inLanguage: "en-US",
"@id": `${fullUrl}#primaryimage`,
url: `${fullUrl}assets/images/cochise-welcome.png`,
contentUrl: `${fullUrl}assets/images/cochise-welcome.png`,
width: 940,
height: 788,
},
{
"@type": "BreadcrumbList",
"@id": `${fullUrl}#breadcrumb`,
itemListElement: [
{
"@type": "ListItem",
position: 1,
name: "Home",
},
],
},
{
"@type": "WebSite",
"@id": `${fullUrl}#website`,
url: fullUrl,
name: siteName,
description: "",
publisher: {
"@id": `${fullUrl}#organization`,
},
potentialAction: [
{
"@type": "SearchAction",
target: {
"@type": "EntryPoint",
urlTemplate: `${fullUrl}?s={search_term_string}`,
},
"query-input": {
"@type": "PropertyValueSpecification",
valueRequired: true,
valueName: "search_term_string",
},
},
],
inLanguage: "en-US",
},
{
"@type": "Organization",
"@id": `${fullUrl}#organization`,
name: "Cochise Oncology",
url: fullUrl,
logo: {
"@type": "ImageObject",
inLanguage: "en-US",
"@id": `${fullUrl}#/schema/logo/image/`,
url: `${fullUrl}assets/images/cochise-welcome.png`,
contentUrl: `${fullUrl}assets/images/cochise-welcome.png`,
width: 500,
height: 195,
caption: siteName,
},
image: {
"@id": `${fullUrl}#/schema/logo/image/`,
},
},
],
};
return (
<>
<Parallax />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<HomeBgVideo />
<Homepage />
<BeforeFooterBlock />
</>

View File

@ -3,7 +3,9 @@ import HeroOther from "@/components/HeroOther";
import { fetchTeamDetail } from "@/services/payload/team";
import { getDefaultMetadata } from "@/utils/metadata";
import { RichText } from "@payloadcms/richtext-lexical/react";
import { headers } from "next/headers";
import Image from "next/image";
import { notFound } from "next/navigation";
import { Metadata } from "next/types";
import { Suspense } from "react";
@ -45,17 +47,94 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
export default async function BiographySinglePage({ params }: { params: Promise<{ slug: string }> }) {
const slug = (await params).slug;
const data = await fetchTeamDetail(decodeURIComponent(slug));
if (!data?.data)
return (
<>
<BeforeFooterBlock />
</>
);
if (!data?.data) {
notFound();
}
const headersList = await headers();
const mainUrl = headersList.get("x-main-url");
const fullUrl = headersList.get("x-full-url");
const siteName = headersList.get("x-site-name");
const metaDesc = headersList.get("x-meta-desc");
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebPage",
"@id": fullUrl,
url: fullUrl,
name: `${data?.data?.name ?? ""} - ${siteName}`,
isPartOf: {
"@id": `${mainUrl}/#website`,
},
datePublished: data?.data?.createdAt,
dateModified: data?.data?.updatedAt,
description: metaDesc,
breadcrumb: {
"@id": `${fullUrl}#breadcrumb`,
},
inLanguage: "en-US",
potentialAction: [
{
"@type": "ReadAction",
target: [fullUrl],
},
],
},
{
"@type": "WebSite",
"@id": `${mainUrl}/#website`,
url: `${mainUrl}/`,
name: siteName,
description: "",
publisher: {
"@id": `${mainUrl}/#organization`,
},
potentialAction: [
{
"@type": "SearchAction",
target: {
"@type": "EntryPoint",
urlTemplate: `${mainUrl}/?s={search_term_string}`,
},
"query-input": {
"@type": "PropertyValueSpecification",
valueRequired: true,
valueName: "search_term_string",
},
},
],
inLanguage: "en-US",
},
{
"@type": "Organization",
"@id": `${mainUrl}/#organization`,
name: siteName,
url: `${mainUrl}/`,
logo: {
"@type": "ImageObject",
thumbnailUrl: data.imgUrl,
inLanguage: "en-US",
"@id": `${mainUrl}/#primaryimage`,
url: data.imgUrl,
contentUrl: data.imgUrl,
width: 500,
height: 195,
caption: siteName,
},
image: {
"@id": `${mainUrl}/#primaryimage`,
},
},
],
};
return (
<>
<Suspense fallback={<Loading />}>
<>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<HeroOther
img={"/assets/images/full-width-images/section-bg-13.jpeg"}
title={data.data.name}

View File

@ -59,6 +59,10 @@ body {
.ext-btn-shadow-sm-primary4 {
@apply bg-extColorPrimary4 text-white hover:text-white hover:bg-extColorPrimary6 transition-colors;
}
.ext-btn-shadow-sm-primary8 {
@apply bg-extColorPrimary8 text-white hover:text-white hover:bg-extColorPrimary6 transition-colors;
}
}
.bg-gradient {

View File

@ -72,8 +72,8 @@ export const Pages: CollectionConfig = {
type: "textarea",
},
{
name: "cannonical_url",
label: "Cannonical Url",
name: "canonical_url",
label: "Canonical Url",
type: "text",
},
],

View File

@ -52,7 +52,7 @@ export function BeforeFooterBlock({ title, description, buttonText, showLinier =
<div className="pt-5">
<Link
href="/contact"
className="inline-block bg-extColorPrimary6 hover:bg-extColorPrimary8 text-white py-3 px-6 rounded-full text-lg shadow-lg transition-all duration-500"
className="inline-block bg-extColorPrimary6 hover:bg-extColorPrimary8 text-white py-3 px-6 hover:scale-105 rounded-full text-lg shadow-sm transition-all duration-500"
>
{buttonText ?? placeholderButtonText}
</Link>

View File

@ -32,9 +32,9 @@ export function GoogleReviews({ data }: GoogleReviewProps) {
<div className="flex justify-center items-center space-x-2">
<h2 className="text-xl text-center font-bol mt-[15px]">{data.ratingValue}</h2>
<div className="mt-1">
<StarRating size={20} value={data.ratingValue} />
<StarRating size={30} value={data.ratingValue} />
</div>
<div className="text-xs font-medium">Over {data.totalRating} Reviews</div>
<div className="font-medium">Over {data.totalRating} Reviews</div>
</div>
<div className="mt-20 relative">

View File

@ -5,7 +5,7 @@ import { fetchBlog } from "@/services/payload/blog";
import { sanitizeBlogContentIntoStringPreview } from "@/utils/sanitize";
export default async function Blog() {
const data = await fetchBlog(undefined);
const data = await fetchBlog();
if (!data?.totalDocs) return <></>;
return (

View File

@ -31,7 +31,7 @@ export function BlogCardItem({ data }: BlogCardItemProps) {
<a href={`/${data.slug}/`}>{data.title}</a>
</h2>
<div className="flex justify-center mt-2">
<a href={`/${data.slug}/`} className="ext-btn-shadow-sm ext-btn-shadow-sm-primary4">
<a href={`/${data.slug}/`} className="ext-btn-shadow-sm ext-btn-shadow-sm-primary8">
Continue Reading
</a>
</div>

View File

@ -13,6 +13,7 @@ export interface BlogDetailProps {
export default async function BlogDetail({ slug }: BlogDetailProps) {
const data = await fetchBlogDetail(slug);
const headersList = await headers();
const mainUrl = headersList.get("x-main-url");
const fullUrl = headersList.get("x-full-url");
const shareUrl = {
@ -25,8 +26,73 @@ export default async function BlogDetail({ slug }: BlogDetailProps) {
notFound();
}
let author = {};
if (!!data?.data?.createdBy && typeof data?.data?.createdBy !== "number") {
author = {
author: {
name: data?.data?.createdBy?.name,
"@id": `${mainUrl}/#/schema/person/${data?.data?.createdBy?.id}`,
},
};
}
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "Article",
"@id": `${fullUrl}#article`,
isPartOf: {
"@id": fullUrl,
},
...author,
headline: data?.data?.title ?? "",
datePublished: data?.createdAt,
dateModified: data?.updatedAt,
mainEntityOfPage: {
"@id": fullUrl,
},
publisher: { "@id": `${mainUrl}/#organization` },
image: {
"@id": `${fullUrl}#primaryimage`,
},
thumbnailUrl: data?.imgUrl ?? "",
inLanguage: "en-US",
},
{
"@type": "WebPage",
"@id": fullUrl,
url: fullUrl,
name: `${data?.data?.title ?? ""} - Cochise Oncology`,
isPartOf: { "@id": `${mainUrl}/#website` },
primaryImageOfPage: {
"@id": `${fullUrl}#primaryimage`,
},
image: {
"@id": `${fullUrl}#primaryimage`,
},
thumbnailUrl: data?.imgUrl ?? "",
datePublished: data?.createdAt,
dateModified: data?.updatedAt,
description: data?.data?.meta?.description,
breadcrumb: {
"@id": `${fullUrl}#breadcrumb`,
},
inLanguage: "en-US",
potentialAction: [
{
"@type": "ReadAction",
target: [fullUrl],
},
],
},
],
};
return (
<>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<HeroOther title={data.data.title} createdAt={data?.createdAt} shareUrl={shareUrl} />
{/* Section */}

View File

@ -4,12 +4,13 @@ import { BlogCardItem } from "./BlogCardItem";
import { sanitizeBlogContentIntoStringPreview } from "@/utils/sanitize";
export interface BlogsProps {
preset?: "blogs" | "categories";
page: number;
search?: string;
}
export default async function Blogs({ page, search }: BlogsProps) {
const data = await fetchBlog(page, search);
export default async function Blogs({ page, search, preset = "blogs" }: BlogsProps) {
const data = await fetchBlog({ page, search });
if (!data?.totalDocs) return <></>;
@ -41,6 +42,7 @@ export default async function Blogs({ page, search }: BlogsProps) {
hasNextPage={data.hasNextPage}
hasPreviousPage={data.hasPrevPage}
totalPages={data.totalPages}
usePathParams={preset === "blogs" ? false : true}
/>
</div>
)}

View File

@ -1,5 +1,6 @@
import Image from "next/image";
import { FaClock, FaFacebook, FaFax, FaLinkedin, FaMapMarkerAlt, FaPhone } from "react-icons/fa";
import { FaClock, FaFacebookF, FaFax, FaLinkedinIn, FaMapMarkerAlt } from "react-icons/fa";
import { GiRotaryPhone } from "react-icons/gi";
import ScrollToTop from "./ScrollToTop";
import { fetchContact } from "@/services/payload/contact";
import { Suspense } from "react";
@ -25,11 +26,11 @@ async function FooterWithData() {
backgroundImage: "linear-gradient(172deg, #798D90 0%, #C48853 100%)",
}}
>
<div className="container mx-auto flex flex-wrap justify-between items-start px-6">
<div className="container mx-auto flex flex-wrap justify-around items-start px-6">
<div className="w-full md:w-1/4 mb-6 md:mb-0 flex flex-col items-start">
<Image src="/assets/images/demo-slick/logo-dark.webp" alt="Cochise Oncology Logo" width={363} height={138} />
<p className="text-sm mt-3">© {new Date().getFullYear()} All Rights Reserved</p>
<a href="/privacy-policy" className="text-sm text-orange-300 font-semibold">
<p className="mt-4 mb-2">© {new Date().getFullYear()} All Rights Reserved</p>
<a href="/privacy-policy" className="text text-extColorPrimary8 font-semibold">
Privacy Policy
</a>
</div>
@ -37,8 +38,8 @@ async function FooterWithData() {
<div className="w-full md:w-1/3 mb-6 md:mb-0">
<h3 className="text-lg font-semibold mb-4">Contact Us</h3>
<ul className="space-y-4 border-gray-400 pl-0">
<li className="flex items-center space-x-4 border-b border-gray-500 pb-2">
<FaMapMarkerAlt className="text-2xl text-gray-300" />
<li className="flex items-center space-x-4 border-b-[0.8px] border-[#b69a89] pb-2">
<FaMapMarkerAlt className="text-2xl text-white-300" />
<div className="leading-tight">
<a
href={contact?.location?.href ?? ""}
@ -49,49 +50,49 @@ async function FooterWithData() {
{contact?.location?.street ?? ""}
</a>
<br />
<span className="text-sm text-gray-300">{contact?.fullLocation ?? ""}</span>
<span className="text-sm text-white-300">{contact?.fullLocation ?? ""}</span>
</div>
</li>
<li className="flex items-center space-x-4 border-b border-gray-500 pb-2">
<FaPhone className="text-2xl text-gray-300" />
<li className="flex items-center space-x-4 border-b-[0.8px] border-[#b69a89] pb-2">
<GiRotaryPhone className="text-2xl text-white-300" />
<a
href={`tel:${contact?.phone ?? ""}`}
target="_blank"
rel="noopener noreferrer"
className="text-lg text-white"
className="text-lg text-white text-[18px]"
>
{contact?.phone ?? ""}
</a>
</li>
<li className="flex items-center space-x-4 border-b border-gray-500 pb-2">
<FaFax className="text-2xl text-gray-300" />
<li className="flex items-center space-x-4 border-b-[0.8px] border-[#b69a89] pb-2">
<FaFax className="text-2xl text-white-300" />
<a
href={`tel:${contact?.fax ?? ""}`}
target="_blank"
rel="noopener noreferrer"
className="text-lg text-white"
className="text-lg text-white text-[18px]"
>
Fax: {contact?.fax ?? ""}
</a>
</li>
<li className="flex items-center space-x-4 border-b border-gray-500 pb-2">
<FaFacebook className="text-2xl text-gray-300" />
<li className="flex items-center space-x-4 border-b-[0.8px] border-[#b69a89] pb-2">
<FaFacebookF className="text-2xl text-white-300" />
<a
href={contact?.facebook ?? ""}
target="_blank"
rel="noopener noreferrer"
className="text-lg text-white"
className="text-lg text-white text-[18px]"
>
Facebook
</a>
</li>
<li className="flex items-center space-x-4">
<FaLinkedin className="text-2xl text-gray-300" />
<FaLinkedinIn className="text-2xl text-white-300" />
<a
href={contact?.linkedin ?? ""}
target="_blank"
rel="noopener noreferrer"
className="text-lg text-white"
className="text-lg text-white text-[18px]"
>
Linkedin
</a>
@ -126,7 +127,7 @@ function FooterSkeleton() {
backgroundImage: "linear-gradient(172deg, #798D90 0%, #C48853 100%)",
}}
>
<div className="container mx-auto flex flex-wrap justify-between items-start px-6">
<div className="container mx-auto flex flex-wrap justify-around items-start px-6">
<div className="w-full md:w-1/4 mb-6 md:mb-0 flex flex-col items-start">
<Image src="/assets/images/demo-slick/logo-dark.webp" alt="Cochise Oncology Logo" width={363} height={138} />
<p className="text-sm mt-3">© {new Date().getFullYear()} All Rights Reserved</p>
@ -135,28 +136,28 @@ function FooterSkeleton() {
<div className="w-full md:w-1/3 mb-6 md:mb-0 animate-pulse">
<h3 className="text-lg font-semibold mb-4">Contact Us</h3>
<ul className="space-y-4 border-gray-400 pl-0">
<li className="flex items-center space-x-4 border-b border-gray-500 pb-2">
<li className="flex items-center space-x-4 border-b-[0.8px] border-[#b69a89] pb-2">
<FaMapMarkerAlt className="text-2xl text-gray-300" />
<div className="h-2 bg-gray-300 rounded flex-1"></div>
<div className="h-2 bg-gray-300 rounded flex-1"></div>
</li>
<li className="flex items-center space-x-4 border-b border-gray-500 pb-2">
<FaPhone className="text-2xl text-gray-300" />
<li className="flex items-center space-x-4 border-b-[0.8px] border-[#b69a89] pb-2">
<GiRotaryPhone className="text-2xl text-gray-300" />
<div className="h-2 bg-gray-300 rounded flex-1"></div>
<div className="h-2 bg-gray-300 rounded flex-1"></div>
</li>
<li className="flex items-center space-x-4 border-b border-gray-500 pb-2">
<li className="flex items-center space-x-4 border-b-[0.8px] border-[#b69a89] pb-2">
<FaFax className="text-2xl text-gray-300" />
<div className="h-2 bg-gray-300 rounded flex-1"></div>
<div className="h-2 bg-gray-300 rounded flex-1"></div>
</li>
<li className="flex items-center space-x-4">
<FaFacebook className="text-2xl text-gray-300" />
<li className="flex items-center space-x-4 border-b-[0.8px] border-[#b69a89]">
<FaFacebookF className="text-2xl text-gray-300" />
<div className="h-2 bg-gray-300 rounded flex-1"></div>
<div className="h-2 bg-gray-300 rounded flex-1"></div>
</li>
<li className="flex items-center space-x-4">
<FaLinkedin className="text-2xl text-gray-300" />
<FaLinkedinIn className="text-2xl text-gray-300" />
<div className="h-2 bg-gray-300 rounded flex-1"></div>
<div className="h-2 bg-gray-300 rounded flex-1"></div>
</li>
@ -167,8 +168,8 @@ function FooterSkeleton() {
<h3 className="text-lg font-semibold mb-4">Business Hours</h3>
<div className="flex items-center space-x-2">
<FaClock className="text-xl" />
<div className="h-2 bg-gray-300 rounded flex-1"></div>
<div className="h-2 bg-gray-300 rounded flex-1"></div>
<div className="h-2 bg-white-300 rounded flex-1"></div>
<div className="h-2 bg-white-300 rounded flex-1"></div>
</div>
</div>
</div>

View File

@ -11,27 +11,27 @@ const shareIcons: Record<string, any> = {
phone: {
link: "tel:+15208036644",
dom: (
<span className="social-nav flex gap-1 text-2xl lg:text-[#00898b]">
<Image src="/assets/icons/phone.png" alt="Image Description" width={25} height={25} />
<b className="text-sm">(520) 803-6644</b>
<span className="social-nav flex gap-1 text-2xl lg:text-white">
<Image src="/assets/icons/phone.png" alt="Image Description" width={25} height={22} />
<b className="text-[17px]">(520) 803-6644</b>
</span>
),
},
facebook: {
link: "https://www.facebook.com/p/Cochise-Oncology-61556262839823",
dom: (
<span className="social-nav flex gap-3 text-2xl lg:text-[#00898b]">
<FaFacebook />
<b className="text-sm lg:hidden">Facebook</b>
<span className="social-nav flex gap-3 text-2xl lg:text-white">
<FaFacebook size={25} />
<b className="text-[17px] lg:hidden">Facebook</b>
</span>
),
},
linkedin: {
link: "https://linkedin.com/company/cochise-oncology",
dom: (
<span className="social-nav flex gap-3 text-2xl lg:text-[#00898b]">
<FaLinkedin />
<b className="text-sm lg:hidden">Linkedin</b>
<span className="social-nav flex gap-3 text-2xl lg:text-white">
<FaLinkedin size={25} />
<b className="text-[17px] lg:hidden">Linkedin</b>
</span>
),
},
@ -78,7 +78,7 @@ export default function Header({ links }: { links: typeof navMenuData }) {
<div className="flex flex-col items-center h-full w-full content-center justify-center gap-2">
<a
href="/contact"
className="bg-[#00898b] px-3 py-2 rounded-full text-white font-bold hover:cursor-pointer hover:bg-[#00abad] scale-[1.02] mt-3 lg:mt-0"
className="bg-extColorPrimary8 px-3 py-2 rounded-full text-white font-bold hover:cursor-pointer hover:scale-[1.15] mt-3 lg:mt-0 transition-transform duration-300"
>
REQUEST CONSULTATION
</a>
@ -86,7 +86,7 @@ export default function Header({ links }: { links: typeof navMenuData }) {
{Object.keys(shareIcons).map((k, idx) => {
return (
<li key={idx} className="!p-0 !m-0">
<a className="cursor-pointer" href={shareIcons[k].link} target="_blank">
<a className="cursor-pointer text-white" href={shareIcons[k].link} target="_blank">
{shareIcons[k].dom}
</a>
</li>

View File

@ -60,7 +60,10 @@ export default function HeaderNav({ links, animateY = false }: { links: typeof n
{link.text} <i className="mi-chevron-down" onClick={() => toggleDropdown([link.text])} />
</Link>
<ul className={`mn-sub to-right ${isDropdownOpen.includes(link.text) && "open"}`} ref={dropdownRef}>
<ul
className={`mn-sub to-right ${isDropdownOpen.includes(link.text) && "open"} !bg-extColorPrimary8`}
ref={dropdownRef}
>
{link.child.map((subLink: any, subLinkIdx: number) => (
<li key={subLinkIdx}>
{!Array.isArray(subLink?.child) && (
@ -75,7 +78,9 @@ export default function HeaderNav({ links, animateY = false }: { links: typeof n
<i className="mi-chevron-down" onClick={() => toggleDropdown([link.text, subLink.text])} />
</Link>
<ul className={`mn-sub to-right ${isDropdownOpen.includes(subLink.text) && "open"}`}>
<ul
className={`mn-sub to-right ${isDropdownOpen.includes(subLink.text) && "open"} !bg-extColorPrimary8`}
>
{subLink.child.map((subLink2: any, subLinkIdx2: number) => (
<li key={subLinkIdx2}>
<Link href={subLink2?.href} onClick={() => toggleMobileMenu()}>

View File

@ -24,7 +24,8 @@ export default function Hero() {
<p className="text-xl">Southern Arizonas Only Complete Cancer Treatment Center in Sierra Vista.</p>
<a
href="/contact"
className="btn btn-mod btn-border-w btn-large btn-round align-middle w-full md:w-1/2 lg:w-1/4 hover:opacity-[0.5]"
// className="btn btn-mod btn-border-w btn-large btn-round align-middle w-full md:w-1/2 lg:w-1/4 hover:opacity-[0.5]"
className="inline-block bg-extColorPrimary6 hover:bg-extColorPrimary8 text-white py-1 px-6 hover:scale-105 rounded-full text-lg shadow-lg transition-all duration-500"
data-btn-animate="y"
>
Request Consultation

View File

@ -1,10 +1,6 @@
// "use client";
"use client";
import Hero from "@/components/Hero";
// const ParallaxContainer = dynamic(() => import("@/components/ParallaxContainer"), {
// ssr: false,
// });
export default function HomeBgVideo() {
return (
<>

View File

@ -123,15 +123,15 @@ export default function homepage({ dark = false }) {
<div className="row wow fadeInUp">
<div className="col-12 col-lg-6 offset-lg-6 col-xl-6 offset-xl-6">
<h2 className="section-title mb-40 mb-sm-30">A Cancer Treatment Center of Excellence</h2>
<p className="text-sm">
<p className="">
Get compassionate care and excellent medical services from COCHISE ONCOLOGY in Sierra Vista, Arizona. We
are the largest full-service cancer treatment center between Albuquerque, New Mexico and Tucson,
Arizona. Our services cover radiation treatments as well as chemotherapy, and we also have an infusion
center. We offer support groups as well as free hospitality housing for patients undergoing treatment.
Read on to learn more about our establishment, or reach out to us via phone or email to make an inquiry.
</p>
<h4>The Mission of Cochise Oncology</h4>
<p className="text-sm">
<h2 className="section-title mb-40 mb-sm-30">The Mission of Cochise Oncology</h2>
<p className="">
To provide excellence in cancer and patient care in a comprehensive center for the local community.
Optimal cancer treatment places the patient firstfrom the patients first greeting with the front
office staff to the trust they develop with the physicians and the whole treatment team. We believe our

View File

@ -3,6 +3,7 @@ import { RenderBlocks } from "@/components/Blocks/RenderBlocks";
import BlogDetail from "@/components/Blogs/BlogDetail";
import { fetchPageBySlug } from "@/services/payload/page";
import HeroOther from "@/components/HeroOther";
import { headers } from "next/headers";
export interface PageProps {
slug: string | undefined;
@ -11,6 +12,7 @@ export interface PageProps {
export default async function Page({ slug }: PageProps) {
const page = await fetchPageBySlug({ slug });
// if page is a blog post
if (!page) {
return (
<>
@ -20,8 +22,88 @@ export default async function Page({ slug }: PageProps) {
);
}
// if page is not a blog post
const headersList = await headers();
const mainUrl = headersList.get("x-main-url");
const fullUrl = headersList.get("x-full-url");
const siteName = headersList.get("x-site-name");
const metaDesc = !!page?.meta?.description ? page.meta?.description : headersList.get("x-meta-desc");
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebPage",
"@id": fullUrl,
url: fullUrl,
name: `${page.meta?.title ?? ""} - ${siteName}`,
isPartOf: {
"@id": `${mainUrl}/#website`,
},
datePublished: page?.createdAt,
dateModified: page?.updatedAt,
description: metaDesc,
breadcrumb: {
"@id": `${fullUrl}#breadcrumb`,
},
inLanguage: "en-US",
potentialAction: [
{
"@type": "ReadAction",
target: [fullUrl],
},
],
},
{
"@type": "WebSite",
"@id": `${mainUrl}/#website`,
url: `${mainUrl}/`,
name: siteName,
description: "",
publisher: {
"@id": `${mainUrl}/#organization`,
},
potentialAction: [
{
"@type": "SearchAction",
target: {
"@type": "EntryPoint",
urlTemplate: `${mainUrl}/?s={search_term_string}`,
},
"query-input": {
"@type": "PropertyValueSpecification",
valueRequired: true,
valueName: "search_term_string",
},
},
],
inLanguage: "en-US",
},
{
"@type": "Organization",
"@id": `${mainUrl}/#organization`,
name: siteName,
url: `${mainUrl}/`,
logo: {
"@type": "ImageObject",
inLanguage: "en-US",
"@id": `${mainUrl}/#/schema/logo/image/`,
url: `${mainUrl}/assets/images/logo-dark.webp`,
contentUrl: `${mainUrl}/assets/images/logo-dark.webp`,
width: 500,
height: 195,
caption: siteName,
},
image: {
"@id": `${mainUrl}/#/schema/logo/image/`,
},
},
],
};
return (
<>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<HeroOther title={page.title} img={page?.heroImg?.url ?? ""} />
<RenderBlocks blocks={page.layout} />
</>

View File

@ -7,9 +7,16 @@ interface PaginationProps {
hasPreviousPage: boolean;
hasNextPage: boolean;
totalPages: number;
usePathParams?: boolean;
}
export default function Pagination({ page, hasPreviousPage, hasNextPage, totalPages }: PaginationProps) {
export default function Pagination({
page,
hasPreviousPage,
hasNextPage,
totalPages,
usePathParams = false,
}: PaginationProps) {
const activePage = page;
const pathName = usePathname();
@ -17,11 +24,16 @@ export default function Pagination({ page, hasPreviousPage, hasNextPage, totalPa
const handlePageChange = (page: string | number) => {
if (typeof page === "string") return;
if (typeof window === "undefined") return;
const url = new URL(window.location.href);
const searchParams = new URLSearchParams(url.search);
if (usePathParams) {
searchParams.set("page", `${page}`);
window.location.href = `${pathName}/page/${page}/?${searchParams}`;
} else {
searchParams.set("page", `${page}`);
window.location.href = `${pathName}/?${searchParams}`;
}
};
const getPageNumbers = () => {

View File

@ -1,5 +1,6 @@
import Image from "next/image";
import StarRating from "./StarRating";
import { limitString } from "@/utils/general";
export interface CardStarRatingProps {
data: {
@ -12,10 +13,10 @@ export function CardStarRating({ data }: CardStarRatingProps) {
return (
<div className="bg-white p-2 rounded-lg shadow-md">
<div className="flex justify-between">
<StarRating size={20} value={data.star} />
<Image src="/assets/images/google-provider.svg" width={20} height={20} alt="" />
<StarRating size={30} value={data.star} />
<Image src="/assets/images/google-provider.svg" width={30} height={30} alt="" />
</div>
<p className="text-xs pl-1 mt-4">{data.description}</p>
<p className="p-1 mt-4 text-[22px]">{limitString(data.description)}</p>
</div>
);
}

View File

@ -7,12 +7,17 @@ export function middleware(request: NextRequest) {
const protocol = request.headers.get("x-forwarded-proto") || "http"; // Default to 'http' if not provided
const host = request.headers.get("x-forwarded-host") || request.nextUrl.hostname;
const path = request.nextUrl.pathname + request.nextUrl.search;
const siteName = "Cochise Oncology";
const metaDesc =
"Get compassionate care and excellent medical services from COCHISE ONCOLOGY in Sierra Vista, Arizona. We are the largest full-service cancer treatment center between Albuquerque, New Mexico and Tucson, Arizona.";
// Construct the full URL
const mainUrl = `${protocol}://${host}`;
const fullUrl = `${mainUrl}${path}`;
request.headers.set("x-main-url", mainUrl);
request.headers.set("x-full-url", fullUrl);
request.headers.set("x-site-name", siteName);
request.headers.set("x-meta-desc", metaDesc);
return NextResponse.next({
request: {

View File

@ -304,7 +304,7 @@ export interface Page {
meta?: {
title?: string | null;
description?: string | null;
cannonical_url?: string | null;
canonical_url?: string | null;
};
createdBy?: (number | null) | User;
updatedBy?: (number | null) | User;
@ -749,7 +749,7 @@ export interface PagesSelect<T extends boolean = true> {
| {
title?: T;
description?: T;
cannonical_url?: T;
canonical_url?: T;
};
createdBy?: T;
updatedBy?: T;

View File

@ -1,21 +1,34 @@
import payloadConfig from "@/payload.config";
import { formatDate } from "@/utils/datetime";
import { getPayload } from "payload";
import { getPayload, Where } from "payload";
export async function fetchBlog(page: number | undefined, search: string = "") {
type FetchBlogParams = {
page?: number;
search?: string;
categorySlug?: string;
};
export async function fetchBlog({ page, search = "", categorySlug = "" }: FetchBlogParams = {}) {
const payload = await getPayload({ config: payloadConfig });
const queryCondition: Where = {};
if (!!search) {
queryCondition["title"] = {
contains: search,
};
}
if (!!categorySlug) {
queryCondition["categories"] = {
equals: 9,
};
}
const blogDataQuery = await payload.find({
collection: "blogs",
page,
pagination: true,
limit: 6,
where: !search
? undefined
: {
title: {
contains: search,
},
},
limit: 9,
where: queryCondition,
});
const formattedData = blogDataQuery.docs.map((item) => {
@ -47,11 +60,13 @@ export async function fetchBlogDetail(slug: string | undefined) {
const data = blogDataQuery?.docs?.[0];
const createdAt = formatDate(data.createdAt);
const updatedAt = formatDate(data.updatedAt);
const imgUrl = typeof data.img !== "number" ? (data?.img?.url ?? "") : "";
return {
data,
createdAt,
updatedAt,
imgUrl,
};
}

View File

@ -0,0 +1,3 @@
export function limitString(text: string) {
return `${text.length > 100 ? `${text.slice(0, 100)}...` : text}`;
}

View File

@ -5,14 +5,30 @@ export async function getDefaultMetadata(): Promise<Metadata> {
const headersList = await headers();
const mainUrl = headersList.get("x-main-url") ?? "";
const fullUrl = headersList.get("x-full-url") ?? "";
const siteName = headersList.get("x-site-name") ?? "";
const metaDesc = headersList.get("x-meta-desc") ?? "";
return {
metadataBase: new URL(mainUrl),
title: siteName,
description: metaDesc,
robots: {
index: true,
follow: true,
"max-image-preview": "large",
"max-snippet": -1,
"max-video-preview": -1,
},
openGraph: {
siteName,
title: siteName,
description: metaDesc,
type: "website",
locale: "en_US",
url: fullUrl,
siteName: "Cochise Oncology",
},
twitter: {
card: "summary_large_image",
},
alternates: {
canonical: "./",