Compare commits

..

3 Commits

Author SHA1 Message Date
04c48ccfb4 fix: contact service lint error 2025-02-28 19:36:54 +07:00
72638cab43 fix: footer linkedin data, integrated from payload 2025-02-28 19:35:54 +07:00
3a08a76b3f fix: layout and footer,
- restructure layout and footer
- footer (contact) data from payload
2025-02-28 19:07:37 +07:00
14 changed files with 355 additions and 67 deletions

View File

@ -1,59 +1,29 @@
"use client";
import { usePathname } from "next/navigation";
import { useEffect } from "react";
import { init_wow } from "@/utils/initWow";
import { parallaxMouseMovement, parallaxScroll } from "@/utils/parallax";
import { headerChangeOnScroll } from "@/utils/changeHeaderOnScroll";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { navMenuData } from "@/data/menu";
import "@/app/globals.css"; import "@/app/globals.css";
import "swiper/css"; import Footer, { FooterSkeleton } from "@/components/Footer";
import "jarallax/dist/jarallax.min.css"; import Header from "@/components/Header";
import "swiper/css/effect-fade"; import InitialScript from "@/components/InitialScript";
import "react-modal-video/css/modal-video.css"; import { navMenuData } from "@/data/menu";
import "photoswipe/dist/photoswipe.css";
import "tippy.js/dist/tippy.css";
import "@public/assets/css/styles.css"; import "@public/assets/css/styles.css";
import "jarallax/dist/jarallax.min.css";
import { Roboto } from "next/font/google"; import { Roboto } from "next/font/google";
import "photoswipe/dist/photoswipe.css";
import { Suspense } from "react";
import "react-modal-video/css/modal-video.css";
import "swiper/css";
import "swiper/css/effect-fade";
import "tippy.js/dist/tippy.css";
const roboto = Roboto({ subsets: ["latin"] }); const roboto = Roboto({ subsets: ["latin"] });
export default function MainLayout({ export default function MainLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
const path = usePathname();
useEffect(() => {
init_wow();
parallaxMouseMovement();
const mainNav = document.querySelector(".main-nav");
if (mainNav?.classList.contains("transparent")) {
mainNav.classList.add("js-transparent");
} else if (!mainNav?.classList?.contains("dark")) {
mainNav?.classList.add("js-no-transparent-white");
}
window.addEventListener("scroll", headerChangeOnScroll);
parallaxScroll();
return () => {
window.removeEventListener("scroll", headerChangeOnScroll);
};
}, [path]);
useEffect(() => {
if (typeof window !== "undefined") {
// Import the script only on the client side
// @ts-ignore
import("bootstrap/dist/js/bootstrap.esm").then(() => {
// Module is imported, you can access any exported functionality if
});
}
}, []);
return ( return (
<html lang="en" className={`no-mobile no-touch ${roboto.className}`}> <html lang="en" className={`no-mobile no-touch ${roboto.className}`}>
<InitialScript />
<body className="appear-animate body"> <body className="appear-animate body">
<div className="theme-slick"> <div className="theme-slick">
<div className="page" id="top"> <div className="page" id="top">
@ -61,7 +31,9 @@ export default function MainLayout({
<Header links={navMenuData} /> <Header links={navMenuData} />
</nav> </nav>
<main id="main">{children}</main> <main id="main">{children}</main>
<Suspense fallback={<FooterSkeleton />}>
<Footer /> <Footer />
</Suspense>
</div> </div>
</div> </div>
</body> </body>

View File

@ -15,5 +15,6 @@ export const Media: CollectionConfig = {
upload: true, upload: true,
admin: { admin: {
hideAPIURL: true, hideAPIURL: true,
group: "General",
}, },
}; };

View File

@ -72,4 +72,8 @@ export const Pages: CollectionConfig = {
], ],
}, },
], ],
admin: {
hideAPIURL: true,
group: "General",
},
}; };

View File

@ -30,5 +30,6 @@ export const Teams: CollectionConfig = {
admin: { admin: {
hideAPIURL: true, hideAPIURL: true,
useAsTitle: "name", useAsTitle: "name",
group: "General",
}, },
}; };

View File

@ -5,6 +5,7 @@ export const Users: CollectionConfig = {
admin: { admin: {
useAsTitle: "email", useAsTitle: "email",
hideAPIURL: true, hideAPIURL: true,
group: "Users",
}, },
auth: true, auth: true,
fields: [ fields: [

View File

@ -1,13 +1,10 @@
"use client";
import React from "react";
import Image from "next/image"; import Image from "next/image";
import { FaPhone, FaFax, FaFacebook, FaMapMarkerAlt, FaClock } from "react-icons/fa"; import { FaClock, FaFacebook, FaFax, FaLinkedin, FaMapMarkerAlt, FaPhone } from "react-icons/fa";
import ScrollToTop from "./ScrollToTop";
import { fetchContact } from "@/services/payload/contact";
export default function Footer() { export default async function Footer() {
const scrollToTop = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const contact = await fetchContact();
event.preventDefault();
window.scrollTo({ top: 0, behavior: "smooth" });
};
return ( return (
<div <div
@ -33,29 +30,43 @@ export default function Footer() {
<FaMapMarkerAlt className="text-2xl text-gray-300" /> <FaMapMarkerAlt className="text-2xl text-gray-300" />
<div className="leading-tight"> <div className="leading-tight">
<a <a
href="https://www.google.com/maps/place/5151+AZ-90,+Sierra+Vista,+AZ+85635" href={contact?.location?.href ?? ""}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-lg text-white" className="text-lg text-white"
> >
5151 E HIGHWAY 90 {contact?.location?.street ?? ""}
</a> </a>
<br /> <br />
<span className="text-sm text-gray-300">Sierra Vista, Arizona 85635</span> <span className="text-sm text-gray-300">{contact?.fullLocation ?? ""}</span>
</div> </div>
</li> </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 border-gray-500 pb-2">
<FaPhone className="text-2xl text-gray-300" /> <FaPhone className="text-2xl text-gray-300" />
<span className="text-lg">(520) 803-6644</span> <a
href={`tel:${contact?.phone ?? ""}`}
target="_blank"
rel="noopener noreferrer"
className="text-lg text-white"
>
{contact?.phone ?? ""}
</a>
</li> </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 border-gray-500 pb-2">
<FaFax className="text-2xl text-gray-300" /> <FaFax className="text-2xl text-gray-300" />
<span className="text-lg">Fax: (520) 459-3193</span> <a
href={`tel:${contact?.fax ?? ""}`}
target="_blank"
rel="noopener noreferrer"
className="text-lg text-white"
>
Fax: {contact?.fax ?? ""}
</a>
</li> </li>
<li className="flex items-center space-x-4"> <li className="flex items-center space-x-4 border-b border-gray-500 pb-2">
<FaFacebook className="text-2xl text-gray-300" /> <FaFacebook className="text-2xl text-gray-300" />
<a <a
href="https://www.facebook.com/p/Cochise-Oncology-61556262839823" href={contact?.facebook ?? ""}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-lg text-white" className="text-lg text-white"
@ -63,21 +74,91 @@ export default function Footer() {
Facebook Facebook
</a> </a>
</li> </li>
<li className="flex items-center space-x-4">
<FaLinkedin className="text-2xl text-gray-300" />
<a
href={contact?.linkedin ?? ""}
target="_blank"
rel="noopener noreferrer"
className="text-lg text-white"
>
Linkedin
</a>
</li>
</ul> </ul>
</div> </div>
<div className="w-full md:w-1/4"> <div className="w-full md:w-1/4">
<h3 className="text-lg font-semibold mb-4">Business Hours</h3> <h3 className="text-lg font-semibold mb-4">Business Hours</h3>
<div className="flex items-center space-x-2"> {contact?.hours?.map?.((hour) => (
<div key={hour.id} className="flex items-center space-x-2">
<FaClock className="text-xl" /> <FaClock className="text-xl" />
<span className="text-base font-medium">Monday - Friday: 8am - 5pm</span> <span className="text-base font-medium">{hour?.hour ?? ""}</span>
</div> </div>
)) ?? []}
</div> </div>
</div> </div>
<div className="text-center mt-6"> <div className="text-center mt-6">
<div onClick={scrollToTop} className="cursor-pointer text-white font-semibold" aria-label="Scroll to top"> <ScrollToTop />
Back to Top </div>
</div>
);
}
export function FooterSkeleton() {
return (
<div
className="relative text-white py-10"
style={{
backgroundColor: "transparent",
backgroundImage: "linear-gradient(172deg, #798D90 0%, #C48853 100%)",
}}
>
<div className="container mx-auto flex flex-wrap justify-between 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>
</div>
<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">
<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" />
<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">
<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" />
<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" />
<div className="h-2 bg-gray-300 rounded flex-1"></div>
<div className="h-2 bg-gray-300 rounded flex-1"></div>
</li>
</ul>
</div>
<div className="w-full md:w-1/4 animate-pulse">
<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>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,3 +1,5 @@
"use client";
import HeaderNav from "@/components/HeaderNav"; import HeaderNav from "@/components/HeaderNav";
import { navMenuData } from "@/data/menu"; import { navMenuData } from "@/data/menu";
import { toggleMobileMenu } from "@/utils/toggleMobileMenu"; import { toggleMobileMenu } from "@/utils/toggleMobileMenu";

View File

@ -0,0 +1,40 @@
"use client";
import { headerChangeOnScroll } from "@/utils/changeHeaderOnScroll";
import { init_wow } from "@/utils/initWow";
import { parallaxMouseMovement, parallaxScroll } from "@/utils/parallax";
import { usePathname } from "next/navigation";
import { useEffect } from "react";
export default function InitialScript() {
const path = usePathname();
useEffect(() => {
init_wow();
parallaxMouseMovement();
const mainNav = document.querySelector(".main-nav");
if (mainNav?.classList.contains("transparent")) {
mainNav.classList.add("js-transparent");
} else if (!mainNav?.classList?.contains("dark")) {
mainNav?.classList.add("js-no-transparent-white");
}
window.addEventListener("scroll", headerChangeOnScroll);
parallaxScroll();
return () => {
window.removeEventListener("scroll", headerChangeOnScroll);
};
}, [path]);
useEffect(() => {
if (typeof window !== "undefined") {
// Import the script only on the client side
// @ts-ignore
import("bootstrap/dist/js/bootstrap.esm").then(() => {
// Module is imported, you can access any exported functionality if
});
}
}, []);
return <></>;
}

View File

@ -0,0 +1,14 @@
"use client";
export default function ScrollToTop() {
const scrollToTop = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
event.preventDefault();
window.scrollTo({ top: 0, behavior: "smooth" });
};
return (
<div onClick={scrollToTop} className="cursor-pointer text-white font-semibold" aria-label="Scroll to top">
Back to Top
</div>
);
}

79
src/globals/Contacts.ts Normal file
View File

@ -0,0 +1,79 @@
import type { GlobalConfig } from "payload";
export const Contacts: GlobalConfig = {
slug: "contacts",
fields: [
{
name: "location",
type: "group",
fields: [
{
name: "street",
label: "Street",
type: "text",
},
{
name: "city",
label: "City",
type: "text",
},
{
name: "state",
label: "State",
type: "text",
},
{
name: "postcode",
label: "Postcode",
type: "text",
},
{
name: "href",
label: "Google Map Link",
type: "text",
},
{
name: "iframeSrc",
label: "Google Map Iframe Source",
type: "text",
},
],
},
{
name: "phone",
label: "Phone Number",
type: "text",
},
{
name: "fax",
label: "Fax",
type: "text",
},
{
name: "facebook",
label: "Facebook Link",
type: "text",
},
{
name: "linkedin",
label: "Linkedin Link",
type: "text",
},
{
name: "hours",
label: "Business Hours",
type: "array",
minRows: 1,
fields: [
{
name: "hour",
type: "text",
},
],
},
],
admin: {
hideAPIURL: true,
group: "General",
},
};

View File

@ -38,5 +38,6 @@ export const GoogleReviews: GlobalConfig = {
], ],
admin: { admin: {
hideAPIURL: true, hideAPIURL: true,
group: "General",
}, },
}; };

View File

@ -44,9 +44,11 @@ export interface Config {
}; };
globals: { globals: {
'google-reviews': GoogleReview; 'google-reviews': GoogleReview;
contacts: Contact;
}; };
globalsSelect: { globalsSelect: {
'google-reviews': GoogleReviewsSelect<false> | GoogleReviewsSelect<true>; 'google-reviews': GoogleReviewsSelect<false> | GoogleReviewsSelect<true>;
contacts: ContactsSelect<false> | ContactsSelect<true>;
}; };
locale: null; locale: null;
user: User & { user: User & {
@ -926,6 +928,33 @@ export interface GoogleReview {
updatedAt?: string | null; updatedAt?: string | null;
createdAt?: string | null; createdAt?: string | null;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "contacts".
*/
export interface Contact {
id: number;
location?: {
street?: string | null;
city?: string | null;
state?: string | null;
postcode?: string | null;
href?: string | null;
iframeSrc?: string | null;
};
phone?: string | null;
fax?: string | null;
facebook?: string | null;
linkedin?: string | null;
hours?:
| {
hour?: string | null;
id?: string | null;
}[]
| null;
updatedAt?: string | null;
createdAt?: string | null;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "google-reviews_select". * via the `definition` "google-reviews_select".
@ -944,6 +973,35 @@ export interface GoogleReviewsSelect<T extends boolean = true> {
createdAt?: T; createdAt?: T;
globalType?: T; globalType?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "contacts_select".
*/
export interface ContactsSelect<T extends boolean = true> {
location?:
| T
| {
street?: T;
city?: T;
state?: T;
postcode?: T;
href?: T;
iframeSrc?: T;
};
phone?: T;
fax?: T;
facebook?: T;
linkedin?: T;
hours?:
| T
| {
hour?: T;
id?: T;
};
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth". * via the `definition` "auth".

View File

@ -25,6 +25,7 @@ import {
LinkFeature, LinkFeature,
} from "@payloadcms/richtext-lexical"; } from "@payloadcms/richtext-lexical";
import { GoogleReviews } from "@/globals/GoogleReviews"; import { GoogleReviews } from "@/globals/GoogleReviews";
import { Contacts } from "./globals/Contacts";
const filename = fileURLToPath(import.meta.url); const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename); const dirname = path.dirname(filename);
@ -51,7 +52,7 @@ export default buildConfig({
theme: "dark", theme: "dark",
}, },
collections: [Users, Media, Blogs, Pages, Teams, BlogCategories, BlogTags], collections: [Users, Media, Blogs, Pages, Teams, BlogCategories, BlogTags],
globals: [GoogleReviews], globals: [GoogleReviews, Contacts],
secret: process.env.PAYLOAD_SECRET || "", secret: process.env.PAYLOAD_SECRET || "",
typescript: { typescript: {
outputFile: path.resolve(dirname, "payload-types.ts"), outputFile: path.resolve(dirname, "payload-types.ts"),
@ -111,6 +112,14 @@ export default buildConfig({
}, },
formOverrides: { formOverrides: {
fields: undefined, fields: undefined,
admin: {
group: "General",
},
},
formSubmissionOverrides: {
admin: {
group: "General",
},
}, },
}), }),
], ],

View File

@ -0,0 +1,25 @@
import payloadConfig from "@/payload.config";
import { getPayload } from "payload";
export async function fetchContact() {
const payload = await getPayload({ config: payloadConfig });
const dataQuery = await payload.findGlobal({ slug: "contacts" });
const fullLocationArr: string[] = [];
if (!!dataQuery?.location?.city) {
fullLocationArr.push(dataQuery.location.city);
}
if (!!dataQuery?.location?.state) {
fullLocationArr.push(dataQuery.location.state);
}
if (!!dataQuery?.location?.postcode) {
fullLocationArr.push(dataQuery.location.postcode);
}
return !!dataQuery
? {
...dataQuery,
fullLocation: fullLocationArr.join(","),
}
: null;
}