From 08e1186f1834ea6de8ec3d81692cac300b8457dd Mon Sep 17 00:00:00 2001 From: davenpos Date: Sun, 16 Mar 2025 11:17:28 -0400 Subject: [PATCH 01/16] Made it so the loading spinner icon appears when the blogs are loading --- app/research/page.js | 21 ++++++++------------- components/BlogClientList.jsx | 34 +++++++++++++++------------------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/app/research/page.js b/app/research/page.js index fbf01b5..9aa7b39 100644 --- a/app/research/page.js +++ b/app/research/page.js @@ -1,25 +1,20 @@ import Hero from "@/components/Hero"; import CallToAction from "@/components/CallToAction"; import BlogClientList from "@/components/BlogClientList"; -import { fetchBlogs } from "@/services/blogService"; - - import "./ResearchPage.css"; import AOSWrapper from "@/components/AOSWrapper"; export default async function ResearchPage() { - const blogs = await fetchBlogs(); - return (
- - - - + + + +
); } diff --git a/components/BlogClientList.jsx b/components/BlogClientList.jsx index 4204c2d..91a67f7 100644 --- a/components/BlogClientList.jsx +++ b/components/BlogClientList.jsx @@ -4,32 +4,28 @@ import { useState, useEffect } from "react"; import { Container, Button } from "react-bootstrap"; import BlogCard from "./BlogCard"; import LoadingSpinner from "./LoadingSpinner"; // Import LoadingSpinner +import { fetchBlogs } from "@/services/blogService"; -const BlogClientList = ({ initialBlogs }) => { - const [blogs, setBlogs] = useState(initialBlogs || []); - const [isLoading, setIsLoading] = useState(false); // Use isLoading for managing loading state +const BlogClientList = () => { + const [blogs, setBlogs] = useState([]); + const [isLoading, setIsLoading] = useState(true); // Use isLoading for managing loading state const [visibleBlogs, setVisibleBlogs] = useState(6); const handleLoadMore = () => setVisibleBlogs((prev) => prev + 6); // Optional: To fetch blogs dynamically if needed (for client-side fetching) useEffect(() => { - if (!initialBlogs || initialBlogs.length === 0) { - setIsLoading(true); - // Fetch blogs from an API or service if the initialBlogs prop is empty - const fetchBlogs = async () => { - try { - const response = await fetchBlogs(); // Fetching data from the service - setBlogs(response); - } catch (error) { - console.log("Error fetching blogs:", error); - } finally { - setIsLoading(false); - } - }; - fetchBlogs(); - } - }, [initialBlogs]); + (async () => { + try { + const response = await fetchBlogs(); // Fetching data from the service + setBlogs(response); + } catch (error) { + console.log("Error fetching blogs:", error); + } finally { + setIsLoading(false); + } + })() + }, []); return (
From 86c02f76d6ca5969484fa9fb22a3057becd2913e Mon Sep 17 00:00:00 2001 From: davenpos Date: Mon, 17 Mar 2025 11:46:40 -0400 Subject: [PATCH 02/16] Separated services into its own component, DRYed its code and removed unused imports on app/page.js --- app/page.js | 54 +++----------------------- components/ServiceCards.jsx | 77 +++++++++++++------------------------ components/Services.jsx | 54 ++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 99 deletions(-) create mode 100644 components/Services.jsx diff --git a/app/page.js b/app/page.js index fea3ac3..17b36f0 100644 --- a/app/page.js +++ b/app/page.js @@ -1,22 +1,14 @@ import "./HomePage.css"; -import { Container, Row, Col, Card } from "react-bootstrap"; +import { Container, Row, Col } from "react-bootstrap"; import Carousel from "react-bootstrap/Carousel"; import CarouselItem from "react-bootstrap/CarouselItem"; import CarouselCaption from "react-bootstrap/CarouselCaption"; -import { - FaQuoteLeft, - FaMobile, - FaCogs, - FaProjectDiagram, - FaShoppingCart, - FaLaptopCode, -} from "react-icons/fa"; +import { FaQuoteLeft } from "react-icons/fa"; import { LuTrendingUp, LuUsers, LuLightbulb } from "react-icons/lu"; import HomeHero from "@/components/HomeHero"; import BlogCard from "@/components/BlogCard"; -import BlackCard from "@/components/BlackCard"; import CustomButton from "@/components/CustomButton"; import CallToAction from "@/components/CallToAction"; @@ -24,7 +16,7 @@ import { fetchBlogs } from "@/services/blogService"; import { fetchClients } from "@/services/clientService"; import defaultClientImage from "@/public/assets/homepage/default-pic.jpg"; -import ServiceCards from "@/components/ServiceCards"; +import Services from "@/components/Services"; import BlackCardsSection from "@/components/BlackCardsSection"; import AOSWrapper from "@/components/AOSWrapper"; import Image from "next/image"; @@ -33,42 +25,6 @@ export default async function HomePage() { const blogs = await fetchBlogs(); const clients = await fetchClients(); - const services = [ - { - image: "/assets/homepage/services-img/market.webp", - icon: , - title: "Marketing + Social Media", - description: - "Amplify your brand's impact with expert marketing and social media strategies. Engage your audience, accelerate growth, and redefine your digital presence.", - }, - { - image: "/assets/homepage/services-img/ecom.webp", - icon: , - title: "E-commerce Solutions", - description: - "Transform your online business with tailored e-commerce solutions. Optimize performance, boost sales, and enhance customer satisfaction.", - }, - { - image: "/assets/homepage/services-img/business.webp", - icon: , - title: "Business Process Optimization", - description: "Streamline your operations and enhance efficiency with our business optimization services. Achieve greater productivity and profitability.", - }, - { - image: "/assets/homepage/services-img/manage.webp", - icon: , - title: "Project Management", - description: - "Ensure timely, budget-friendly project delivery with our expert management services. We focus on seamless project process management with an emphasis on continuity to improve efficiency and achieve your goals.", - }, - { - image: "/assets/homepage/services-img/it.webp", - icon: , - title: "IT + Tech Integrations", - description: "Boost your business with our innovative IT solutions. We design custom IT and tech packages to increase efficiencies and maximize output.", - }, - ]; - const reasons = [ { icon: , @@ -189,7 +145,7 @@ export default async function HomePage() { - + @@ -217,7 +173,7 @@ export default async function HomePage() { alt={client.name} width={100} height={100} - // style={{ borderRadius: "50%" }} + // style={{ borderRadius: "50%" }} />

diff --git a/components/ServiceCards.jsx b/components/ServiceCards.jsx index 61d6d3f..cf3e52c 100644 --- a/components/ServiceCards.jsx +++ b/components/ServiceCards.jsx @@ -1,56 +1,33 @@ "use client"; -import { Card, Col, Row } from "react-bootstrap"; +import { Card, Col } from "react-bootstrap"; +import { createElement } from "react"; -const ServiceCards = ({ services }) => { - return ( - - -

- {services.slice(0, 3).map((service, index) => ( - - -
{service.icon}
-

{service.title}

-

{service.description}

-
-
- ))} -
- - -
- {services.slice(3, 5).map((service, index) => ( - - -
{service.icon}
-

{service.title}

-

{service.description}

-
-
- ))} -
- - - ); +const ServiceCards = ({ services, divClasses }) => { + return ( +
+ {services.map((service, index) => ( + + +
+ {createElement(service.icon, { size: 40 })} +
+

{service.title}

+

{service.description}

+
+
+ ))} +
+ ); }; export default ServiceCards; diff --git a/components/Services.jsx b/components/Services.jsx new file mode 100644 index 0000000..7202449 --- /dev/null +++ b/components/Services.jsx @@ -0,0 +1,54 @@ +"use client" + +import { + FaMobile, + FaCogs, + FaProjectDiagram, + FaShoppingCart, + FaLaptopCode, +} from "react-icons/fa"; +import { Row } from "react-bootstrap"; +import ServiceCards from "@/components/ServiceCards"; + +export default function Services() { + const services = [ + { + image: "/assets/homepage/services-img/market.webp", + icon: FaMobile, + title: "Marketing + Social Media", + description: + "Amplify your brand's impact with expert marketing and social media strategies. Engage your audience, accelerate growth, and redefine your digital presence.", + }, + { + image: "/assets/homepage/services-img/ecom.webp", + icon: FaShoppingCart, + title: "E-commerce Solutions", + description: + "Transform your online business with tailored e-commerce solutions. Optimize performance, boost sales, and enhance customer satisfaction.", + }, + { + image: "/assets/homepage/services-img/business.webp", + icon: FaCogs, + title: "Business Process Optimization", + description: "Streamline your operations and enhance efficiency with our business optimization services. Achieve greater productivity and profitability.", + }, + { + image: "/assets/homepage/services-img/manage.webp", + icon: FaProjectDiagram, + title: "Project Management", + description: + "Ensure timely, budget-friendly project delivery with our expert management services. We focus on seamless project process management with an emphasis on continuity to improve efficiency and achieve your goals.", + }, + { + image: "/assets/homepage/services-img/it.webp", + icon: FaLaptopCode, + title: "IT + Tech Integrations", + description: "Boost your business with our innovative IT solutions. We design custom IT and tech packages to increase efficiencies and maximize output.", + }, + ]; + + return ( + + + ) +} \ No newline at end of file From b73aa2f255c8ffe7566ff880fff41196174f0f25 Mon Sep 17 00:00:00 2001 From: davenpos Date: Mon, 17 Mar 2025 13:18:17 -0400 Subject: [PATCH 03/16] Swapped out JSX for React icons with createElement on home and about pages --- app/about/page.js | 241 +------------------------------ app/page.js | 41 +----- components/BlackCard.js | 12 +- components/BlackCardsSection.jsx | 16 -- components/ChooseUs.jsx | 49 +++++++ components/Leads.jsx | 132 +++++++++++++++++ components/LeadsGroup.jsx | 46 ++++++ components/OurValues.jsx | 49 +++++++ 8 files changed, 293 insertions(+), 293 deletions(-) delete mode 100644 components/BlackCardsSection.jsx create mode 100644 components/ChooseUs.jsx create mode 100644 components/Leads.jsx create mode 100644 components/LeadsGroup.jsx create mode 100644 components/OurValues.jsx diff --git a/app/about/page.js b/app/about/page.js index 9b4ee4e..b2ee257 100644 --- a/app/about/page.js +++ b/app/about/page.js @@ -1,160 +1,17 @@ import "./AboutPage.css"; import { Container, Row, Col } from "react-bootstrap"; -import { - FaLightbulb, - FaBalanceScale, - FaUsers, - FaLinkedinIn, - FaGlobe, -} from "react-icons/fa"; import AOSWrapper from "@/components/AOSWrapper"; - import Hero from "@/components/Hero"; import CustomButton from "@/components/CustomButton"; import CallToAction from "@/components/CallToAction"; -import BlackCard from "@/components/BlackCard"; +import Leads from "@/components/Leads"; +import OurValues from "@/components/OurValues"; import Image from "next/image"; const AboutPage = () => { - const values = [ - { - icon: , - title: "Innovation", - description: - "We constantly seek out new ideas and approaches to stay ahead of industry trends and provide our clients with cutting-edge solutions.", - }, - { - icon: , - title: "Integrity", - description: - "We believe in doing business with honesty and transparency, building trust with our clients and partners through ethical practices.", - }, - { - icon: , - title: "Collaboration", - description: - "We work closely with our clients and partners, fostering a collaborative environment that drives success for everyone involved.", - }, - ]; - - const consultingLeads = [ - { - image: "/assets/about/team/phelan.webp", - name: "Mark Phelan", - title: "Senior Consultant,", - specialty: "E-Commerce Solutions", - socials: [ - { - icon: , - href: "https://www.linkedin.com/in/phelanmarkw", - }, - { - icon: , - href: "https://www.thephelanfocus.com/", - }, - ], - }, - { - image: "/assets/about/team/kathy.webp", - name: "Kathy Seaton", - title: "Senior Consultant,", - specialty: "Non Profit Development", - socials: [ - { - icon: , - href: "https://www.linkedin.com/in/klseaton", - }, - { - icon: , - href: "https://www.klseatonconsulting.com/", - }, - ], - }, - { - image: "/assets/about/team/donavon.webp", - name: "Donavon Roberson", - title: "Senior Consultant,", - specialty: "Tech + Software Solutions", - socials: [ - { - icon: , - href: "https://www.linkedin.com/in/donavonroberson", - }, - { - icon: , - href: "https://medium.com/@thejourneyofthedreamer", - }, - ], - }, - { - image: "/assets/about/team/melissa.webp", - name: "Melissa Eboli", - title: "Senior Consultant,", - specialty: "Health + Wellness Solutions", - socials: [ - { - icon: , - href: "https://www.linkedin.com/in/viamelissa", - }, - { icon: , href: "https://www.viaskitchen.com/" }, - ], - }, - ]; - - const teamLeads = [ - { - image: "/assets/about/team/bast.webp", - name: "Bast Herrera", - title: "Team Lead,", - specialty: "Program Development", - socials: [ - { - icon: , - href: "https://www.linkedin.com/in/vincentcherrera", - }, - ], - }, - { - image: "/assets/about/team/dania.webp", - name: "Dania Ali", - title: "Team Lead,", - specialty: "Operations + Strategy", - socials: [ - { - icon: , - href: "https://www.linkedin.com/in/syeda-dania-ali", - }, - ], - }, - { - image: "/assets/about/team/megha.webp", - name: "Megha Vinjamuru", - title: "Team Lead,", - specialty: "Project Management", - socials: [ - { - icon: , - href: "https://www.linkedin.com/in/meghanethra", - }, - ], - }, - { - image: "/assets/about/team/alexandria.webp", - name: "Alexandria Boreman", - title: "Team Lead,", - specialty: "Marketing + Creative", - socials: [ - { - icon: , - href: "https://www.linkedin.com/in/alexandriaboreman", - }, - ], - }, - ]; - return (
@@ -176,7 +33,7 @@ const AboutPage = () => { width={800} height={400} className="overflow-hidden rounded-3 object-fit-cover w-100" - // placeholder="blur" + // placeholder="blur" /> @@ -265,22 +122,7 @@ const AboutPage = () => {
{/* Our Values Section */} -
- -

- Our Core Values -

- - {values.map((value, index) => ( - - ))} - -
-
+ {/* Our Team Section */}
@@ -295,79 +137,8 @@ const AboutPage = () => { -

Consulting Leads

- - {consultingLeads.map((member, i) => ( - -
- {member.name} -
-

{member.name}

-
{member.title}
-
{member.specialty}
-
- {member.socials.map((link, index) => ( - - {link.icon} - - ))} -
-
-
- - ))} -
- -

Delivery Team Leads

- - {teamLeads.map((member, i) => ( - -
- {member.name} -
-

{member.name}

-
{member.title}
-
{member.specialty}
-
- {member.socials.map((link, index) => ( - - {link.icon} - - ))} -
-
-
- - ))} -
+ {/* Consulting Leads and Team Leads Sections */} +
diff --git a/app/page.js b/app/page.js index 17b36f0..6e919d2 100644 --- a/app/page.js +++ b/app/page.js @@ -5,7 +5,6 @@ import Carousel from "react-bootstrap/Carousel"; import CarouselItem from "react-bootstrap/CarouselItem"; import CarouselCaption from "react-bootstrap/CarouselCaption"; import { FaQuoteLeft } from "react-icons/fa"; -import { LuTrendingUp, LuUsers, LuLightbulb } from "react-icons/lu"; import HomeHero from "@/components/HomeHero"; import BlogCard from "@/components/BlogCard"; @@ -17,7 +16,7 @@ import { fetchClients } from "@/services/clientService"; import defaultClientImage from "@/public/assets/homepage/default-pic.jpg"; import Services from "@/components/Services"; -import BlackCardsSection from "@/components/BlackCardsSection"; +import ChooseUs from "@/components/ChooseUs"; import AOSWrapper from "@/components/AOSWrapper"; import Image from "next/image"; @@ -25,35 +24,6 @@ export default async function HomePage() { const blogs = await fetchBlogs(); const clients = await fetchClients(); - const reasons = [ - { - icon: , - title: "Expert Team", - description: - "Our team consists of industry experts with years of experience in their respective fields. With diverse backgrounds and deep knowledge, we bring unparalleled expertise to every project.", - stat: "10", - statSuffix: "+", - statDescription: "years combined experience", - }, - { - icon: , - title: "Proven Results", - description: "We have a track record of delivering successful projects and measurable improvements for our clients. Our results speak for themselves, with consistent client satisfaction and tangible outcomes.", - stat: "95", - statSuffix: "%", - statDescription: "customer satisfaction rate", - }, - { - icon: , - title: "Innovative Solutions", - description: - "We leverage the latest technologies and methodologies to provide innovative solutions to our clients. Our forward-thinking approach ensures that you stay ahead of the curve in your industry.", - stat: "20", - statSuffix: "+", - statDescription: "innovative projects delivered", - }, - ]; - return ( <> @@ -151,14 +121,7 @@ export default async function HomePage() { -
- -

- Why Choose Us? -

- -
-
+
diff --git a/components/BlackCard.js b/components/BlackCard.js index 4f5a80e..40f2d5c 100644 --- a/components/BlackCard.js +++ b/components/BlackCard.js @@ -1,10 +1,11 @@ "use client"; -import AnimatedNumber from "../components/AnimatedNumber"; +import AnimatedNumber from "@/components/AnimatedNumber"; import "./BlackCard.css"; import { Col } from "react-bootstrap"; +import { createElement } from "react"; -const BlackCard = ({ item, isStat }) => { +const BlackCard = ({ item, iconSize, isStat }) => { return ( { data-aos-once="true" >
-
{item.icon}
+
+ {createElement(item.icon, { + className: "text-yellow", + size: iconSize + })} +

{item.title}

diff --git a/components/BlackCardsSection.jsx b/components/BlackCardsSection.jsx deleted file mode 100644 index 8c49ef0..0000000 --- a/components/BlackCardsSection.jsx +++ /dev/null @@ -1,16 +0,0 @@ -"use client"; - -import { Row } from "react-bootstrap"; -import BlackCard from "./BlackCard"; - -const BlackCardsSection = ({ reasons }) => { - return ( - - {reasons.map((reason, index) => ( - - ))} - - ); -}; - -export default BlackCardsSection; diff --git a/components/ChooseUs.jsx b/components/ChooseUs.jsx new file mode 100644 index 0000000..41fc060 --- /dev/null +++ b/components/ChooseUs.jsx @@ -0,0 +1,49 @@ +"use client" + +import { LuTrendingUp, LuUsers, LuLightbulb } from "react-icons/lu"; +import { Container, Row } from "react-bootstrap"; +import BlackCard from "@/components/BlackCard"; + +export default function ChooseUs() { + const reasons = [ + { + icon: LuUsers, + title: "Expert Team", + description: + "Our team consists of industry experts with years of experience in their respective fields. With diverse backgrounds and deep knowledge, we bring unparalleled expertise to every project.", + stat: "10", + statSuffix: "+", + statDescription: "years combined experience", + }, + { + icon: LuTrendingUp, + title: "Proven Results", + description: "We have a track record of delivering successful projects and measurable improvements for our clients. Our results speak for themselves, with consistent client satisfaction and tangible outcomes.", + stat: "95", + statSuffix: "%", + statDescription: "customer satisfaction rate", + }, + { + icon: LuLightbulb, + title: "Innovative Solutions", + description: + "We leverage the latest technologies and methodologies to provide innovative solutions to our clients. Our forward-thinking approach ensures that you stay ahead of the curve in your industry.", + stat: "20", + statSuffix: "+", + statDescription: "innovative projects delivered", + }, + ]; + + return (
+ +

+ Why Choose Us? +

+ + {reasons.map((reason, index) => ( + + ))} + +
+
) +} \ No newline at end of file diff --git a/components/Leads.jsx b/components/Leads.jsx new file mode 100644 index 0000000..eade833 --- /dev/null +++ b/components/Leads.jsx @@ -0,0 +1,132 @@ +"use client" + +import { + FaLinkedin, + FaGlobe, +} from "react-icons/fa"; +import LeadsGroup from "@/components/LeadsGroup"; + +export default function Leads() { + const consultingLeads = [ + { + image: "/assets/about/team/phelan.webp", + name: "Mark Phelan", + title: "Senior Consultant,", + specialty: "E-Commerce Solutions", + socials: [ + { + icon: FaLinkedin, + href: "https://www.linkedin.com/in/phelanmarkw", + }, + { + icon: FaGlobe, + href: "https://www.thephelanfocus.com/", + }, + ], + }, + { + image: "/assets/about/team/kathy.webp", + name: "Kathy Seaton", + title: "Senior Consultant,", + specialty: "Non Profit Development", + socials: [ + { + icon: FaLinkedin, + href: "https://www.linkedin.com/in/klseaton", + }, + { + icon: FaGlobe, + href: "https://www.klseatonconsulting.com/", + }, + ], + }, + { + image: "/assets/about/team/donavon.webp", + name: "Donavon Roberson", + title: "Senior Consultant,", + specialty: "Tech + Software Solutions", + socials: [ + { + icon: FaLinkedin, + href: "https://www.linkedin.com/in/donavonroberson", + }, + { + icon: FaGlobe, + href: "https://medium.com/@thejourneyofthedreamer", + }, + ], + }, + { + image: "/assets/about/team/melissa.webp", + name: "Melissa Eboli", + title: "Senior Consultant,", + specialty: "Health + Wellness Solutions", + socials: [ + { + icon: FaLinkedin, + href: "https://www.linkedin.com/in/viamelissa", + }, + { + icon: FaGlobe, + href: "https://www.viaskitchen.com/" + }, + ], + }, + ]; + + const teamLeads = [ + { + image: "/assets/about/team/bast.webp", + name: "Bast Herrera", + title: "Team Lead,", + specialty: "Program Development", + socials: [ + { + icon: FaLinkedin, + href: "https://www.linkedin.com/in/vincentcherrera", + }, + ], + }, + { + image: "/assets/about/team/dania.webp", + name: "Dania Ali", + title: "Team Lead,", + specialty: "Operations + Strategy", + socials: [ + { + icon: FaLinkedin, + href: "https://www.linkedin.com/in/syeda-dania-ali", + }, + ], + }, + { + image: "/assets/about/team/megha.webp", + name: "Megha Vinjamuru", + title: "Team Lead,", + specialty: "Project Management", + socials: [ + { + icon: FaLinkedin, + href: "https://www.linkedin.com/in/meghanethra", + }, + ], + }, + { + image: "/assets/about/team/alexandria.webp", + name: "Alexandria Boreman", + title: "Team Lead,", + specialty: "Marketing + Creative", + socials: [ + { + icon: FaLinkedin, + href: "https://www.linkedin.com/in/alexandriaboreman", + }, + ], + }, + ]; + + return (<> + + + ) +} \ No newline at end of file diff --git a/components/LeadsGroup.jsx b/components/LeadsGroup.jsx new file mode 100644 index 0000000..d23d762 --- /dev/null +++ b/components/LeadsGroup.jsx @@ -0,0 +1,46 @@ +"use client" + +import { Row, Col } from "react-bootstrap"; +import Image from "next/image"; +import { createElement } from "react"; + +export default function LeadsGroup({ heading, leads }) { + return (<> +

{heading}

+ + {leads.map((member, i) => ( + +
+ {member.name} +
+

{member.name}

+
{member.title}
+
{member.specialty}
+
+ {member.socials.map((link, index) => ( + + {createElement(link.icon, { size: 20 })} + + ))} +
+
+
+ + ))} +
+ ) +} \ No newline at end of file diff --git a/components/OurValues.jsx b/components/OurValues.jsx new file mode 100644 index 0000000..923c560 --- /dev/null +++ b/components/OurValues.jsx @@ -0,0 +1,49 @@ +"use client" + +import { Container, Row } from "react-bootstrap"; +import BlackCard from "@/components/BlackCard"; +import { + FaLightbulb, + FaBalanceScale, + FaUsers +} from "react-icons/fa"; + +export default function OurValues() { + const values = [ + { + icon: FaLightbulb, + title: "Innovation", + description: + "We constantly seek out new ideas and approaches to stay ahead of industry trends and provide our clients with cutting-edge solutions.", + }, + { + icon: FaBalanceScale, + title: "Integrity", + description: + "We believe in doing business with honesty and transparency, building trust with our clients and partners through ethical practices.", + }, + { + icon: FaUsers, + title: "Collaboration", + description: + "We work closely with our clients and partners, fostering a collaborative environment that drives success for everyone involved.", + }, + ]; + + return (
+ +

+ Our Core Values +

+ + {values.map((value, index) => ( + + ))} + +
+
) +} \ No newline at end of file From ce7e6863b08098ac0e6ef5b6a62e75f8245ece34 Mon Sep 17 00:00:00 2001 From: davenpos Date: Mon, 17 Mar 2025 13:30:43 -0400 Subject: [PATCH 04/16] Updated footer to be more DRY and use arrays of objects like I had it in another branch --- components/Footer.css | 10 +- components/Footer.js | 401 ++++++++++++++++++++---------------------- 2 files changed, 199 insertions(+), 212 deletions(-) diff --git a/components/Footer.css b/components/Footer.css index 479ac7b..ecbff35 100644 --- a/components/Footer.css +++ b/components/Footer.css @@ -9,6 +9,8 @@ footer { .footer-logo { letter-spacing: 0.1rem; + text-decoration: none; + color: inherit; } footer:hover { @@ -50,8 +52,10 @@ footer:hover { color: #ffd700; transform: translateY(-3px); text-shadow: 0 0 5px #ffd700, 0 0 10px #ffd700, 0 0 15px #ffd700, - 0 0 20px #ffd700, 0 0 25px #ffd700; /* Enhanced glowing effect */ - transition: transform 0.2s ease, text-shadow 0.2s ease; /* Smooth transition */ + 0 0 20px #ffd700, 0 0 25px #ffd700; + /* Enhanced glowing effect */ + transition: transform 0.2s ease, text-shadow 0.2s ease; + /* Smooth transition */ } .footer-icon { @@ -95,4 +99,4 @@ footer:hover { .footer .text-center p { font-size: 0.85rem; } -} +} \ No newline at end of file diff --git a/components/Footer.js b/components/Footer.js index d3a09b6..573c03d 100644 --- a/components/Footer.js +++ b/components/Footer.js @@ -1,226 +1,209 @@ import { FaEnvelope, FaTwitter, FaLinkedin } from "react-icons/fa"; import { Container, Row, Col } from "react-bootstrap"; +import { createElement } from "react"; import Link from "next/link"; import "./Footer.css"; const Footer = () => { - return ( -
- - - - -

PROJXON

- - - -
- -
- -
- - -
- -
- - -
- -
- -
- -
- -
- - {/* Services Column */} - -

Services

-
    -
  • - - Marketing + Social - -
  • -
  • - - E-commerce Solutions - -
  • -
  • - - Business Optimization - -
  • -
  • - - Project Management - -
  • -
  • - - IT Interactions - -
  • -
- + const socialIcons = [ + { + href: "mailto:info@projxon.com", + ariaLabel: "Email", + icon: FaEnvelope, + newTab: false + }, + { + href: "https://twitter.com/projxon", + ariaLabel: "Twitter", + icon: FaTwitter, + newTab: true + }, + { + href: "https://linkedin.com/company/projxon", + ariaLabel: "LinkedIn", + icon: FaLinkedin, + newTab: true + } + ] - {/* About Column */} - -

About

-
    -
  • - - Vision - -
  • -
  • - - Mission - -
  • -
  • - - Core Value - -
  • -
  • - - Meet the Team - -
  • -
  • - - Our Journey - -
  • -
- + //Note: I assume that all of these href values are supposed to be different and go to different pages/subpages, or different sections of the same page (like /services#marketing-social) and the fact that each subsection of the footer gave all of them the same href was just a placeholder. But when copying over the code, I kept the href links exactly how they originally were. + const footerLinks = [ + { + heading: "Services", + links: [ + { + href: "/services", + text: "Marketing + Social" + }, + { + href: "/services", + text: "E-commerce Solutions" + }, + { + href: "/services", + text: "Business Optimization" + }, + { + href: "/services", + text: "Project Management" + }, + { + href: "/services", + text: "IT Interactions" + } + ] + }, + { + heading: "About", + links: [ + { + href: "/about", + text: "Vision" + }, + { + href: "/about", + text: "Mission" + }, + { + href: "/about", + text: "Core Value" + }, + { + href: "/about", + text: "Meet the Team" + }, + { + href: "/about", + text: "Our Journey" + } + ] + }, + { + heading: "Partnerships", + links: [ + { + href: "/servicepartners", + text: "Service Partners" + }, + { + href: "/referralpartners", + text: "Referral Partners" + } + ] + }, + { + heading: "Connect", + links: [ + { + href: "/contact", + text: "Contact" + }, + { + href: "/contact", + text: "Social" + }, + { + href: "/contact", + text: "Appoint" + }, + { + href: "/contact", + text: "Inquiries" + }, + { + href: "/contact", + text: "Address (Google)" + } + ] + }, + { + heading: "Research", + links: [ + { + href: "/research", + text: "Blog" + }, + { + href: "/research", + text: "Events" + }, + { + href: "/research", + text: "Articles" + }, + { + href: "/research", + text: "Newsletter" + } + ] + }, + { + heading: "Careers", + links: [ + { + href: "/career", + text: "Join Our Team" + }, + { + href: "/career", + text: "Internship Program" + } + ] + } + ] - {/* Partnerships Column */} - -

Partnerships

-
    -
  • - - Service Partners - -
  • -
  • - - Referral Partners - -
  • -
- - - {/* Connect Column */} - -

Connect

-
    -
  • - - Contact - -
  • -
  • - - Social - -
  • -
  • - - Appoint - -
  • -
  • - - Inquiries - -
  • -
  • - - Address (Google) - -
  • -
- - - {/* Blog & Research Column */} - -

Research

-
    -
  • - - Blog - -
  • -
  • - - Events - -
  • -
  • - - Articles - -
  • -
  • - - Newsletter - -
  • -
- + return (
+ + + + +

PROJXON

+ + + +
+ {socialIcons.map((icon, index) => ( + + {createElement(icon.icon, { size: 40 })} + + ))} +
+ +
- {/* Careers Column */} - -

Careers

+
+ + {footerLinks.map((section, index) => ( + +

{section.heading}

    -
  • - - Join Our Team + {section.links.map((link, index) => (
  • + + {link.text} -
  • -
  • - - Internship Program - -
  • + ))}
-
+ ))} + - - -

- © {new Date().getFullYear()} PROJXON. All rights reserved. -

- -
-
-
- ); + + +

© {new Date().getFullYear()} PROJXON. All rights reserved.

+ +
+
+
) }; export default Footer; From 667588f36a9cb11a5b2d6fcc1208f778dce3f5c6 Mon Sep 17 00:00:00 2001 From: davenpos Date: Mon, 17 Mar 2025 15:01:17 -0400 Subject: [PATCH 05/16] Created renderIcon function to fix slight performance overhead issue --- components/BlackCard.js | 7 ++----- components/Footer.js | 4 ++-- components/LeadsGroup.jsx | 4 ++-- components/ServiceCards.jsx | 4 ++-- lib/renderIcon.js | 3 +++ 5 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 lib/renderIcon.js diff --git a/components/BlackCard.js b/components/BlackCard.js index 40f2d5c..9b759a0 100644 --- a/components/BlackCard.js +++ b/components/BlackCard.js @@ -1,9 +1,9 @@ "use client"; import AnimatedNumber from "@/components/AnimatedNumber"; +import renderIcon from "@/lib/renderIcon"; import "./BlackCard.css"; import { Col } from "react-bootstrap"; -import { createElement } from "react"; const BlackCard = ({ item, iconSize, isStat }) => { return ( @@ -17,10 +17,7 @@ const BlackCard = ({ item, iconSize, isStat }) => { >
- {createElement(item.icon, { - className: "text-yellow", - size: iconSize - })} + {renderIcon(item.icon, iconSize, "text-yellow")}

{item.title}

diff --git a/components/Footer.js b/components/Footer.js index 573c03d..de61c37 100644 --- a/components/Footer.js +++ b/components/Footer.js @@ -1,6 +1,6 @@ import { FaEnvelope, FaTwitter, FaLinkedin } from "react-icons/fa"; import { Container, Row, Col } from "react-bootstrap"; -import { createElement } from "react"; +import renderIcon from "@/lib/renderIcon"; import Link from "next/link"; import "./Footer.css"; @@ -174,7 +174,7 @@ const Footer = () => { target={icon.newTab ? "_blank" : undefined} rel={icon.newTab ? "noopener noreferrer" : undefined} > - {createElement(icon.icon, { size: 40 })} + {renderIcon(icon.icon, 40)} ))}
diff --git a/components/LeadsGroup.jsx b/components/LeadsGroup.jsx index d23d762..81147c2 100644 --- a/components/LeadsGroup.jsx +++ b/components/LeadsGroup.jsx @@ -2,7 +2,7 @@ import { Row, Col } from "react-bootstrap"; import Image from "next/image"; -import { createElement } from "react"; +import renderIcon from "@/lib/renderIcon"; export default function LeadsGroup({ heading, leads }) { return (<> @@ -33,7 +33,7 @@ export default function LeadsGroup({ heading, leads }) { target="_blank" rel="noopener noreferrer" > - {createElement(link.icon, { size: 20 })} + {renderIcon(link.icon, 20)} ))}
diff --git a/components/ServiceCards.jsx b/components/ServiceCards.jsx index cf3e52c..c35da2e 100644 --- a/components/ServiceCards.jsx +++ b/components/ServiceCards.jsx @@ -1,7 +1,7 @@ "use client"; import { Card, Col } from "react-bootstrap"; -import { createElement } from "react"; +import renderIcon from "@/lib/renderIcon"; const ServiceCards = ({ services, divClasses }) => { return ( @@ -19,7 +19,7 @@ const ServiceCards = ({ services, divClasses }) => { >
- {createElement(service.icon, { size: 40 })} + {renderIcon(service.icon, 40)}

{service.title}

{service.description}

diff --git a/lib/renderIcon.js b/lib/renderIcon.js new file mode 100644 index 0000000..cbeddf4 --- /dev/null +++ b/lib/renderIcon.js @@ -0,0 +1,3 @@ +export default function renderIcon(Icon, size, className = null) { + return +} \ No newline at end of file From 7caca7719e4c79de427894b439434a0da35a4639 Mon Sep 17 00:00:00 2001 From: davenpos Date: Mon, 17 Mar 2025 17:40:30 -0400 Subject: [PATCH 06/16] Fully reverted removal of use client commit and updated footer to fix hydration issue and match previous footer link structure --- components/Footer.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/components/Footer.js b/components/Footer.js index de61c37..f7f3e73 100644 --- a/components/Footer.js +++ b/components/Footer.js @@ -7,7 +7,7 @@ import "./Footer.css"; const Footer = () => { const socialIcons = [ { - href: "mailto:info@projxon.com", + href: "https://mail.google.com/mail/u/0/?fs=1&tf=cm&to=info@projxon.com", ariaLabel: "Email", icon: FaEnvelope, newTab: false @@ -166,16 +166,20 @@ const Footer = () => {
{socialIcons.map((icon, index) => ( - - {renderIcon(icon.icon, 40)} - +
+ {renderIcon(icon.icon, 40)} +
+ ))}
From 88540a6f491100f486a81dfe1f9106be9353a235 Mon Sep 17 00:00:00 2001 From: davenpos Date: Thu, 27 Mar 2025 19:21:56 -0400 Subject: [PATCH 07/16] Copied code from api/blog to newly-created lib and had blogServices call it --- app/api/blog/route.js | 33 --------------------------------- app/page.js | 5 ++--- app/research/page.js | 4 +++- components/BlogClientList.jsx | 32 ++++++++++++++++++-------------- lib/getBlogs.js | 30 ++++++++++++++++++++++++++++++ services/blogService.js | 8 ++++---- 6 files changed, 57 insertions(+), 55 deletions(-) delete mode 100644 app/api/blog/route.js create mode 100644 lib/getBlogs.js diff --git a/app/api/blog/route.js b/app/api/blog/route.js deleted file mode 100644 index 6f88eb1..0000000 --- a/app/api/blog/route.js +++ /dev/null @@ -1,33 +0,0 @@ -export async function GET(req) { - const { searchParams } = new URL(req.url); - const slug = searchParams.get("slug"); - - try { - const endpoint = slug ? `/posts?_embed&slug=${slug}` : `/posts?_embed`; - - console.log(`Fetching from: ${process.env.WORDPRESS_API_URL}${endpoint}`); - - const res = await fetch(`${process.env.WORDPRESS_API_URL}${endpoint}`, { - headers: { - Authorization: - "Basic " + - btoa( - `${process.env.WORDPRESS_API_USERNAME}:${process.env.WORDPRESS_API_PASSWORD}` - ), - }, - }); - - if (!res.ok) { - throw new Error(`Failed to fetch data: ${res.statusText}`); - } - - const data = await res.json(); - return new Response(JSON.stringify(data), { status: 200 }); - } catch (error) { - console.error("Error fetching blogs:", error); - return new Response( - JSON.stringify({ message: "Error fetching blogs", error: error.message }), - { status: 500 } - ); - } -} \ No newline at end of file diff --git a/app/page.js b/app/page.js index 974aa80..6e919d2 100644 --- a/app/page.js +++ b/app/page.js @@ -11,7 +11,7 @@ import BlogCard from "@/components/BlogCard"; import CustomButton from "@/components/CustomButton"; import CallToAction from "@/components/CallToAction"; -// import { fetchBlogs } from "../api/blog/route"; +import { fetchBlogs } from "@/services/blogService"; import { fetchClients } from "@/services/clientService"; import defaultClientImage from "@/public/assets/homepage/default-pic.jpg"; @@ -21,8 +21,7 @@ import AOSWrapper from "@/components/AOSWrapper"; import Image from "next/image"; export default async function HomePage() { - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/blog`); - const blogs = await res.json(); + const blogs = await fetchBlogs(); const clients = await fetchClients(); return ( diff --git a/app/research/page.js b/app/research/page.js index 4478d83..07b6077 100644 --- a/app/research/page.js +++ b/app/research/page.js @@ -6,6 +6,8 @@ import AOSWrapper from "@/components/AOSWrapper"; import { fetchBlogs } from "@/services/blogService"; export default async function ResearchPage() { + const blogs = await fetchBlogs(); + return (
@@ -14,7 +16,7 @@ export default async function ResearchPage() { subtitle="Stay Updated with the Latest News, Events, and Insight" backgroundClass="research-hero" /> - +
); diff --git a/components/BlogClientList.jsx b/components/BlogClientList.jsx index 91a67f7..253e6ce 100644 --- a/components/BlogClientList.jsx +++ b/components/BlogClientList.jsx @@ -6,26 +6,30 @@ import BlogCard from "./BlogCard"; import LoadingSpinner from "./LoadingSpinner"; // Import LoadingSpinner import { fetchBlogs } from "@/services/blogService"; -const BlogClientList = () => { - const [blogs, setBlogs] = useState([]); - const [isLoading, setIsLoading] = useState(true); // Use isLoading for managing loading state +const BlogClientList = ({ initialBlogs }) => { + const [blogs, setBlogs] = useState(initialBlogs || []); + const [isLoading, setIsLoading] = useState(false); // Use isLoading for managing loading state const [visibleBlogs, setVisibleBlogs] = useState(6); const handleLoadMore = () => setVisibleBlogs((prev) => prev + 6); // Optional: To fetch blogs dynamically if needed (for client-side fetching) useEffect(() => { - (async () => { - try { - const response = await fetchBlogs(); // Fetching data from the service - setBlogs(response); - } catch (error) { - console.log("Error fetching blogs:", error); - } finally { - setIsLoading(false); - } - })() - }, []); + if (!initialBlogs || initialBlogs.length === 0) { + setIsLoading(true) + + (async () => { + try { + const response = await fetchBlogs(); // Fetching data from the service + setBlogs(response); + } catch (error) { + console.log("Error fetching blogs:", error); + } finally { + setIsLoading(false); + } + })() + } + }, [initialBlogs]); return (
diff --git a/lib/getBlogs.js b/lib/getBlogs.js new file mode 100644 index 0000000..3095971 --- /dev/null +++ b/lib/getBlogs.js @@ -0,0 +1,30 @@ +export default async function getBlogs(slug = null) { + try { + const endpoint = slug ? `/posts?_embed&slug=${slug}` : `/posts?_embed`; + + console.log(`Fetching from: ${process.env.WORDPRESS_API_URL}${endpoint}`); + + const res = await fetch(`${process.env.WORDPRESS_API_URL}${endpoint}`, { + headers: { + Authorization: + "Basic " + + btoa( + `${process.env.WORDPRESS_API_USERNAME}:${process.env.WORDPRESS_API_PASSWORD}` + ), + }, + }); + + if (!res.ok) { + throw new Error(`Failed to fetch data: ${res.statusText}`); + } + + const data = await res.json(); + return new Response(JSON.stringify(data), { status: 200 }); + } catch (error) { + console.error("Error fetching blogs:", error); + return new Response( + JSON.stringify({ message: "Error fetching blogs", error: error.message }), + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/services/blogService.js b/services/blogService.js index 8693faa..c20db02 100644 --- a/services/blogService.js +++ b/services/blogService.js @@ -1,8 +1,8 @@ -// services/blogService.js +import getBlogs from '@/lib/getBlogs' export const fetchBlogs = async () => { try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/blog`); // Full URL + const response = await getBlogs() if (!response.ok) { throw new Error("Failed to fetch blogs"); @@ -15,9 +15,9 @@ export const fetchBlogs = async () => { } }; -export const fetchBlog = async (id) => { +export const fetchBlog = async id => { try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/blog?slug=${id}`); // Full URL + const response = await getBlogs(id) if (!response.ok) { throw new Error(`Failed to fetch blog with id: ${id}`); From 65ce5b5fe0276fc39009bb52e39015807ab784f2 Mon Sep 17 00:00:00 2001 From: davenpos Date: Thu, 27 Mar 2025 20:37:24 -0400 Subject: [PATCH 08/16] Got rid of client api call, although it doesn't fetch the clients on the editor page, likely because it's client-side and can't access the environment variables --- app/api/client/[id]/route.js | 51 ---------------- app/api/client/route.js | 48 --------------- app/editor/page.js | 53 ++++++++-------- lib/getClients.js | 25 ++++++++ services/clientService.js | 115 +++++++++++++++++++---------------- 5 files changed, 115 insertions(+), 177 deletions(-) delete mode 100644 app/api/client/[id]/route.js delete mode 100644 app/api/client/route.js create mode 100644 lib/getClients.js diff --git a/app/api/client/[id]/route.js b/app/api/client/[id]/route.js deleted file mode 100644 index 441c0ff..0000000 --- a/app/api/client/[id]/route.js +++ /dev/null @@ -1,51 +0,0 @@ -import { authenticateUser } from "@/lib/authenticateUser"; - -export async function GET(req, { params }) { - try { - const res = await fetch(`${process.env.WORDPRESS_CUSTOM_API_URL}/clients`); - const data = await res.json(); - const client = data.find((c) => c.id === params.id); - - return client - ? Response.json(client) - : new Response(JSON.stringify({ message: "Client not found" }), { - status: 404, - }); - } catch (error) { - return new Response( - JSON.stringify({ - message: "Error fetching client", - error: error.message, - }), - { status: 500 } - ); - } -} - -export async function DELETE(req, { params }) { - try { - authenticateUser(req); - - const { id } = await params - - const res = await fetch( - `${process.env.WORDPRESS_CUSTOM_API_URL}/clients/${id}`, - { - method: "DELETE", - headers: { - Authorization: req.headers.get("authorization"), - }, - } - ); - - return Response.json(await res.json()); - } catch (error) { - return new Response( - JSON.stringify({ - message: "Error deleting client", - error: error.message, - }), - { status: 500 } - ); - } -} diff --git a/app/api/client/route.js b/app/api/client/route.js deleted file mode 100644 index f66f7e1..0000000 --- a/app/api/client/route.js +++ /dev/null @@ -1,48 +0,0 @@ -import { authenticateUser } from "@/lib/authenticateUser"; - -export async function GET() { - try { - const res = await fetch(`${process.env.WORDPRESS_CUSTOM_API_URL}/clients`); - const data = await res.json(); - return Response.json(data); - } catch (error) { - return new Response( - JSON.stringify({ - message: "Error fetching clients", - error: error.message, - }), - { status: 500 } - ); - } -} - -export async function POST(req) { - try { - authenticateUser(req); // Protect this route - - const body = await req.json(); - console.log("Received client data:", body); - const res = await fetch(`${process.env.WORDPRESS_CUSTOM_API_URL}/clients`, { - method: "POST", - headers: { - Authorization: req.headers.get("authorization"), - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); - - if (!res.ok) { - const errorResponse = await res.json(); - console.error("Error from WordPress API:", errorResponse); // Log error response from WordPress - throw new Error(`Failed to add client. Status code: ${res.status}`); - } - - const responseData = await res.json(); - return Response.json(responseData, { status: 201 }); - } catch (error) { - return new Response( - JSON.stringify({ message: "Error adding client", error: error.message }), - { status: 500 } - ); - } -} diff --git a/app/editor/page.js b/app/editor/page.js index ff8cfc4..f5247dc 100644 --- a/app/editor/page.js +++ b/app/editor/page.js @@ -12,7 +12,6 @@ import "./TestimonialEditorPage.css"; import { useRouter } from "next/navigation"; import { logout } from "../../services/loginService"; import ImageUpload from "../../components/ImageUpload"; -import axiosInstance from "../../utils/axiosInstance"; export default function EditorPage() { const router = useRouter(); @@ -100,37 +99,37 @@ export default function EditorPage() { const token = localStorage.getItem('authToken'); // Or use context if needed if (!token) { - return; + return; } try { - // Upload file if there is one - let fileUrl = ''; - if (file) { - fileUrl = await uploadFile(file); - } - - const newClient = { - name: newTestimonial.name, - quote: newTestimonial.quote, - title: newTestimonial.title, - image: fileUrl, - }; - const addedClient = await addClient(newClient, token); - if (addedClient) { - setClients((prevClients) => { - const updatedClients = [...prevClients, newClient]; - setCurrentTestIndex(updatedClients.length - 1); - return updatedClients; - }); - } - - console.log('Added client:', addedClient); - // Handle success (e.g., clear form, show success message) + // Upload file if there is one + let fileUrl = ''; + if (file) { + fileUrl = await uploadFile(file); + } + + const newClient = { + name: newTestimonial.name, + quote: newTestimonial.quote, + title: newTestimonial.title, + image: fileUrl, + }; + const addedClient = await addClient(newClient, token); + if (addedClient) { + setClients((prevClients) => { + const updatedClients = [...prevClients, newClient]; + setCurrentTestIndex(updatedClients.length - 1); + return updatedClients; + }); + } + + console.log('Added client:', addedClient); + // Handle success (e.g., clear form, show success message) } catch (error) { - console.error('Error adding client:', error); + console.error('Error adding client:', error); } -}; + }; return ( diff --git a/lib/getClients.js b/lib/getClients.js new file mode 100644 index 0000000..ec7c652 --- /dev/null +++ b/lib/getClients.js @@ -0,0 +1,25 @@ +export default async function getClients(id = null) { + try { + console.log(process.env.WORDPRESS_CUSTOM_API_URL) + const res = await fetch(`${process.env.WORDPRESS_CUSTOM_API_URL}/clients`); + const data = await res.json(); + + if (id) { + const client = data.find((c) => c.id === id); + + return client + ? Response.json(client) + : new Response(JSON.stringify({ message: "Client not found" }), { + status: 404, + }); + } else return Response.json(data) + } catch (error) { + return new Response( + JSON.stringify({ + message: "Error fetching client", + error: error.message, + }), + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/services/clientService.js b/services/clientService.js index 36af768..18d9b90 100644 --- a/services/clientService.js +++ b/services/clientService.js @@ -1,32 +1,37 @@ -// services/clientService.js - -// We will modify the clientService.js to fetch data internally instead of externally +import getClients from '@/lib/getClients' import axios from 'axios'; -const api = axios.create({ - baseURL: process.env.WORDPRESS_CUSTOM_API_URL, - headers: { - 'Content-Type': 'application/json', - }, -}); - export const fetchClients = async () => { - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/client`); // Internal API route - if (!res.ok) { - throw new Error("Error fetching clients"); + try { + const response = await getClients() + + if (!response.ok) { + throw new Error("Failed to fetch clients"); + } + + return await response.json(); + } catch (error) { + console.error("❌ Error fetching clients:", error); + return []; // Return empty array if error occurs } - return res.json(); // Parse the JSON response }; -export const fetchClient = async (id) => { - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/client/${id}`); // Internal API route with dynamic id - if (!res.ok) { - throw new Error("Error fetching client"); +export const fetchClient = async id => { + try { + const response = await getClients(id) + + if (!response.ok) { + throw new Error(`Failed to fetch client with id: ${id}`); + } + const data = await response.json(); + return data[0]; + } catch (error) { + console.error("❌ Error fetching client:", error); + return null; } - return res.json(); // Parse the JSON response }; -export const addClient = async (clientData) => { +export const addClient = async clientData => { try { const token = localStorage.getItem("authToken"); @@ -34,7 +39,7 @@ export const addClient = async (clientData) => { throw new Error("Unauthorized - No token found"); } - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/client`, { + const res = await fetch(`${process.env.WORDPRESS_CUSTOM_API_URL}/clients`, { method: "POST", headers: { Authorization: `Bearer ${token}`, @@ -44,20 +49,24 @@ export const addClient = async (clientData) => { }); if (!res.ok) { - const errorResponse = await res.json(); - throw new Error(`Error adding client: ${errorResponse.message || res.statusText}`); + const errorResponse = await res.json(); + console.error("Error from WordPress API:", errorResponse); // Log error response from WordPress + throw new Error(`Failed to add client. Status code: ${res.status}`); } - return res.json(); + const responseData = await res.json(); + return Response.json(responseData, { status: 201 }); } catch (error) { - console.error("❌ Error adding client:", error.message); - return null; + return new Response( + JSON.stringify({ message: "Error adding client", error: error.message }), + { status: 500 } + ); } }; export const uploadFile = async (file) => { const token = localStorage.getItem("authToken"); - + if (!token) { throw new Error("No token found"); } @@ -66,19 +75,19 @@ export const uploadFile = async (file) => { formData.append('file', file); try { - const response = await axios.post('/api/upload', formData, { - headers: { - 'Authorization': `Bearer ${token}`, - }, - }); - return response.data.url; + const response = await axios.post('/api/upload', formData, { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + return response.data.url; } catch (error) { - console.error('Error uploading file:', error); - throw error; + console.error('Error uploading file:', error); + throw error; } }; -export const deleteClient = async (id) => { +export const deleteClient = async id => { try { const token = localStorage.getItem("authToken"); @@ -86,21 +95,25 @@ export const deleteClient = async (id) => { throw new Error("Unauthorized - No token found"); } - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/client/${id}`, { - method: "DELETE", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }); - - if (!res.ok) { - throw new Error("Error deleting client"); - } - - return res.status === 200 || res.status === 204; + const res = await fetch( + `${process.env.WORDPRESS_CUSTOM_API_URL}/clients/${id}`, + { + method: "DELETE", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + } + ); + + return Response.json(await res.json()); } catch (error) { - console.error("❌ Error deleting client:", error); - return false; + return new Response( + JSON.stringify({ + message: "Error deleting client", + error: error.message, + }), + { status: 500 } + ); } }; From d3991afd5c51535e370da4f423832757c87f92ac Mon Sep 17 00:00:00 2001 From: davenpos Date: Mon, 31 Mar 2025 10:03:10 -0400 Subject: [PATCH 09/16] Fixed npm run build error --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 065df0e..8c37a54 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "dev": "next dev", - "build": "node --max-old-space-size=4096 node_modules/.bin/next build", + "build": "next build", "start": "next start", "lint": "next lint", "generate-sitemap": "node generate-sitemap.js", @@ -32,4 +32,4 @@ "react-icons": "^5.5.0", "react-intersection-observer": "^9.16.0" } -} +} \ No newline at end of file From e311a24a2ab2364cc0c9ae9a10d16ab1f97b9611 Mon Sep 17 00:00:00 2001 From: davenpos Date: Mon, 31 Mar 2025 11:00:23 -0400 Subject: [PATCH 10/16] Got the client testimonials to finally show up in the editor page --- app/api/client/route.js | 37 +++++++++++++++++++++++++++++++++++++ app/editor/page.js | 31 ++++++++++++------------------- lib/getClients.js | 3 +-- 3 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 app/api/client/route.js diff --git a/app/api/client/route.js b/app/api/client/route.js new file mode 100644 index 0000000..acffe5e --- /dev/null +++ b/app/api/client/route.js @@ -0,0 +1,37 @@ +import { authenticateUser } from "@/lib/authenticateUser"; +import getClients from "@/lib/getClients"; + +export async function GET() { + return await getClients() +} + +export async function POST(req) { + try { + authenticateUser(req); // Protect this route + + const body = await req.json(); + console.log("Received client data:", body); + const res = await fetch(`${process.env.WORDPRESS_CUSTOM_API_URL}/clients`, { + method: "POST", + headers: { + Authorization: req.headers.get("authorization"), + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + const errorResponse = await res.json(); + console.error("Error from WordPress API:", errorResponse); // Log error response from WordPress + throw new Error(`Failed to add client. Status code: ${res.status}`); + } + + const responseData = await res.json(); + return Response.json(responseData, { status: 201 }); + } catch (error) { + return new Response( + JSON.stringify({ message: "Error adding client", error: error.message }), + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/editor/page.js b/app/editor/page.js index f5247dc..0aa4fbf 100644 --- a/app/editor/page.js +++ b/app/editor/page.js @@ -2,16 +2,12 @@ import AuthGuard from "@/components/AuthGuard"; import React, { useState, useEffect, useRef } from "react"; -import { - fetchClients, - addClient, - deleteClient, - uploadFile -} from "../../services/clientService"; +import { deleteClient, uploadFile } from "../../services/clientService"; import "./TestimonialEditorPage.css"; import { useRouter } from "next/navigation"; import { logout } from "../../services/loginService"; import ImageUpload from "../../components/ImageUpload"; +import axios from 'axios'; export default function EditorPage() { const router = useRouter(); @@ -38,11 +34,10 @@ export default function EditorPage() { }; useEffect(() => { - const loadClients = async () => { - const response = await fetchClients(); - setClients(response); - }; - loadClients(); + (async () => { + const response = await axios.get("/api/client") + setClients(response.data) + })() }, []); const getCurrentClient = () => { @@ -59,15 +54,13 @@ export default function EditorPage() { const handlePrev = () => { if (clients.length > 0) { - setCurrentTestIndex( - (prevIndex) => (prevIndex - 1 + clients.length) % clients.length - ); + setCurrentTestIndex(prevIndex => (prevIndex - 1 + clients.length) % clients.length) } }; const handleNext = () => { if (clients.length > 0) { - setCurrentTestIndex((prevIndex) => (prevIndex + 1) % clients.length); + setCurrentTestIndex(prevIndex => (prevIndex + 1) % clients.length); } }; @@ -88,11 +81,11 @@ export default function EditorPage() { } }; - const handleInputChange = (e) => { + const handleInputChange = e => { setNewTestimonial({ ...newTestimonial, [e.target.name]: e.target.value }); }; - const handleAdd = async (e) => { + const handleAdd = async e => { e.preventDefault(); // Make sure you handle the JWT token (can be stored in cookies or context) @@ -115,7 +108,7 @@ export default function EditorPage() { title: newTestimonial.title, image: fileUrl, }; - const addedClient = await addClient(newClient, token); + const addedClient = await axios.post("/api/client", newClient, token) if (addedClient) { setClients((prevClients) => { const updatedClients = [...prevClients, newClient]; @@ -161,7 +154,7 @@ export default function EditorPage() { setFile(file)} + onFileSelect={file => setFile(file)} />