diff --git a/app/(event)/upcoming-event/[slug]/page.jsx b/app/(event)/upcoming-event/[slug]/page.jsx index 1e5cf6e..4d69be1 100644 --- a/app/(event)/upcoming-event/[slug]/page.jsx +++ b/app/(event)/upcoming-event/[slug]/page.jsx @@ -1,31 +1,60 @@ import UpcomingEventSinglePage from "@/components/events/UpcomingEventSinglePage"; -import { events } from "@/utility/constant.utils"; -const getEvent = (slug) => events.find((event) => event.slug === slug); +export const dynamic = 'force-dynamic'; +export const revalidate = 0; -export function generateStaticParams() { - return events - .filter((event) => event.slug) - .map((event) => ({ - slug: event.slug, - })); +export async function generateStaticParams() { + try { + const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3006/api'; + const res = await fetch(`${API_BASE_URL}/upcoming-events`); + const data = await res.json(); + if (data && data.success && data.data && data.data.length > 0) { + const paramsList = []; + data.data.forEach(event => { + if (event.slug && String(event.slug).trim()) { + paramsList.push({ slug: String(event.slug).trim() }); + } + if (event.id) { + paramsList.push({ slug: String(event.id) }); + } + const title = event.title || event.eventtitle || ''; + if (title) { + const titleSlug = String(title).toLowerCase().trim().replace(/\s+/g, '-').replace(/[^\w\-]+/g, ''); + if (titleSlug) { + paramsList.push({ slug: titleSlug }); + } + } + }); + return paramsList.length > 0 ? paramsList : [{ slug: 'default' }]; + } + } catch (e) { + console.error("Error in generateStaticParams:", e); + } + return [{ slug: 'default' }]; } export async function generateMetadata({ params }) { - const event = getEvent(params.slug); - - if (!event) { - return { - title: "Upcoming Event | Tamil Culture Waterloo", - }; + try { + const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3006/api'; + const res = await fetch(`${API_BASE_URL}/upcoming-events/slug/${params.slug}`); + const data = await res.json(); + if (data && data.success && data.data) { + const event = data.data; + return { + title: `${event.title || event.eventtitle} | Tamil Culture Waterloo`, + description: event.description || event.eventdescription || `Details for ${event.title || event.eventtitle}.`, + }; + } + } catch (e) { + console.error("Error in generateMetadata:", e); } - + return { - title: `${event.title} | Tamil Culture Waterloo`, - description: event.desc || `Details for ${event.title}.`, + title: "Upcoming Event | Tamil Culture Waterloo", }; } export default function Page({ params }) { return ; } + diff --git a/components/events/UpcomingEventData.jsx b/components/events/UpcomingEventData.jsx index c3b390e..f32d329 100644 --- a/components/events/UpcomingEventData.jsx +++ b/components/events/UpcomingEventData.jsx @@ -1,11 +1,37 @@ 'use client' import Link from 'next/link' import moment from "moment"; -import { useState } from "react" -import { events } from "@/utility/constant.utils"; // <-- import the events array here +import { useState, useEffect } from "react" +import axios from "axios"; export default function UpcomingEventData() { const [currentMonth, setCurrentMonth] = useState(moment()); + const [events, setEvents] = useState([]); + + useEffect(() => { + const fetchEvents = async () => { + try { + const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3006/api'; + const response = await axios.get(`${API_BASE_URL}/upcoming-events`); + if (response.data && response.data.success) { + const normalizedEvents = response.data.data.map(e => ({ + ...e, + title: e.title || e.eventtitle, + date: e.date || e.eventdate, + image: e.image || e.eventimageurl, + desc: e.description || e.eventdescription, + btnText: e.btn_text || e.btnText, + link: e.link || (e.slug ? `/upcoming-event/${e.slug}` : `/upcoming-event/${e.id}`), + location: e.location || e.venue, + })); + setEvents(normalizedEvents); + } + } catch (error) { + console.error("Error fetching upcoming events:", error); + } + }; + fetchEvents(); + }, []); // Get start and end of current month const startOfMonth = currentMonth.clone().startOf("month"); @@ -135,125 +161,125 @@ export default function UpcomingEventData() { return (
-
-
-
-

{String(idx + 1).padStart(2, "0")}

- - {!isEven ? ( - <> -
-
- {event.title} -
-
- -
- -
-
-
    -
  • - - {event.time{" "} - {event.time ? `${event.time} - ` : ""} - {formatEventDate(event.date)} - -
  • - {event.location && event.location.trim() && ( -
  • - - {" "} - {event.location} - -
  • - )} -
-
- {event.title} -
-

- {event.desc} -

- -
-
- - {event.btnText || "Online Tickets"} - +
+
+

{String(idx + 1).padStart(2, "0")}

+
+ {!isEven ? ( + <> +
+
+ {event.title}
-
- - ) : ( - <> -
-
-
    -
  • - - {event.time{" "} - {event.time ? `${event.time} - ` : ""} - {formatEventDate(event.date)} - -
  • - {event.location && event.location.trim() && ( + +
    + +
    +
    +
    • - {" "} - {event.location} + {event.time{" "} + {event.time ? `${event.time} - ` : ""} + {formatEventDate(event.date)}
    • - )} -
    -
    - {event.title} -
    -

    - {event.desc} -

    -
    -
    - + + {" "} + {event.location} + + + )} +
+
+ {event.title} +
+

- {event.btnText || "Online Tickets"} - + {event.desc} +

+ +
+
+ + {event.btnText || "Online Tickets"} + +
-
- -
- -
-
- {event.title} + + ) : ( + <> +
+
+
    +
  • + + {event.time{" "} + {event.time ? `${event.time} - ` : ""} + {formatEventDate(event.date)} + +
  • + {event.location && event.location.trim() && ( +
  • + + {" "} + {event.location} + +
  • + )} +
+
+ {event.title} +
+

+ {event.desc} +

+
+
+ + {event.btnText || "Online Tickets"} + +
+
-
- - )} + +
+ +
+
+ {event.title} +
+
+ + )} +
diff --git a/components/events/UpcomingEventSinglePage.jsx b/components/events/UpcomingEventSinglePage.jsx index 8ef0851..afe1f56 100644 --- a/components/events/UpcomingEventSinglePage.jsx +++ b/components/events/UpcomingEventSinglePage.jsx @@ -1,7 +1,9 @@ +'use client' import Layout from "@/components/layout/Layout"; import Link from "next/link"; import moment from "moment"; -import { events } from "@/utility/constant.utils"; +import { useState, useEffect } from "react"; +import axios from "axios"; const formatEventDate = (dateStr) => { if (!dateStr) return "Details will be announced"; @@ -12,11 +14,79 @@ const formatEventDate = (dateStr) => { }); }; -export default function UpcomingEventSinglePage({ slug }) { - const event = events.find((item) => item.slug === slug); +import { useSearchParams } from "next/navigation"; + +export default function UpcomingEventSinglePage({ slug: propSlug }) { + const searchParams = useSearchParams(); + const [event, setEvent] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let activeSlug = propSlug; + if (!activeSlug && searchParams) { + activeSlug = searchParams.get('slug') || searchParams.get('id'); + } + if (!activeSlug && typeof window !== 'undefined') { + const parts = window.location.pathname.split('/').filter(Boolean); + if (parts.length > 0) { + activeSlug = parts[parts.length - 1]; + } + } + + const fetchEvent = async () => { + try { + const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3006/api'; + const response = await axios.get(`${API_BASE_URL}/upcoming-events/slug/${activeSlug}`); + if (response.data && response.data.success) { + const e = response.data.data; + setEvent({ + ...e, + title: e.title || e.eventtitle, + date: e.date || e.eventdate, + image: e.image || e.eventimageurl, + desc: e.description || e.eventdescription, + btnText: e.btn_text || e.btnText, + link: e.link || `/upcoming-event/${e.slug}`, + location: e.location || e.venue, + }); + } + } catch (error) { + console.error("Error fetching event details:", error); + } finally { + setLoading(false); + } + }; + if (activeSlug) { + fetchEvent(); + } else { + setLoading(false); + } + }, [propSlug, searchParams]); + + if (loading) { + return ( + +
+
+ Loading... +
+
+
+ ); + } if (!event) { - return null; + return ( + +
+

Event not found

+
+ + Back to Events + +
+ + ); } const eventDate = formatEventDate(event.date); diff --git a/components/home/HomeUpcomingEvent.tsx b/components/home/HomeUpcomingEvent.tsx index fabfa2c..0d0ea16 100644 --- a/components/home/HomeUpcomingEvent.tsx +++ b/components/home/HomeUpcomingEvent.tsx @@ -1,16 +1,40 @@ 'use client' import Link from 'next/link' -import { useState } from "react" -import { events } from "@/utility/constant.utils" // import your events array +import { useState, useEffect } from "react" +import axios from "axios" +import { events as staticEvents } from "@/utility/constant.utils" export default function HomeUpcomingEvent() { const [isTab, setIsTab] = useState(1) - const handleTab = (i: number) => { - setIsTab(i) - } + const [liveEvents, setLiveEvents] = useState([]) - // Take only the first 3 events - const displayEvents = events.slice(0, 3) as any[]; + useEffect(() => { + const fetchEvents = async () => { + try { + const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3006/api'; + const response = await axios.get(`${API_BASE_URL}/upcoming-events`); + if (response.data && response.data.success && response.data.data.length > 0) { + const normalizedEvents = response.data.data.map((e: any) => ({ + ...e, + title: e.title || e.eventtitle, + date: e.date || e.eventdate, + image: e.image || e.eventimageurl, + desc: e.description || e.desc || e.eventdescription, + btnText: e.btn_text || e.btnText, + link: e.link || `/upcoming-event/${e.slug || e.id}`, + location: e.location || e.venue, + })); + setLiveEvents(normalizedEvents); + } + } catch (error) { + console.error("Error fetching homepage upcoming events:", error); + } + }; + fetchEvents(); + }, []); + + // Take only the first 3 events from API or fallback + const displayEvents = (liveEvents.length > 0 ? liveEvents : staticEvents).slice(0, 3) as any[]; return ( <> @@ -44,117 +68,118 @@ export default function HomeUpcomingEvent() {
{/* Alternating layout */} {idx % 2 === 0 ? ( - <> -
-
- {event.title} -
-
-
-
-
-
    -
  • - - - - {event.time ? event.time + " - " : ""}{event.date} | - - -
  • -
  • - - {event.location} - - - -
  • -
-
- {event.title} -
-

- {event.desc} -

-
-
- - {event.btnText || "purchase ticket"} - + <> +
+
+ {event.title}
-
-
- - ) : ( - <> -
-
-
    -
  • - - - - {event.time ? event.time + " - " : ""}{event.date} | - - -
  • -
  • - - {event.location} - +
    +
    +
    +
      +
    • + + + + {event.time ? event.time + " - " : ""}{event.date} | + + +
    • +
    • + + {event.location} + - -
    • -
    -
    - {event.title} -
    -

    - {event.desc} -

    -
    -
    - +
  • +
+
+ {event.title} +
+

- {event.btnText || "purchase ticket"} - -

+ {event.desc} +

+
+
+ + {event.btnText || "purchase ticket"} + +
+
-
-
-
-
- {event.title} + + ) : ( + <> +
+
+
    +
  • + + + + {event.time ? event.time + " - " : ""}{event.date} | + + +
  • +
  • + + {event.location} + + + +
  • +
+
+ {event.title} +
+

+ {event.desc} +

+
+
+ + {event.btnText || "purchase ticket"} + +
+ +
+
-
- - )} -
+
+
+
+ {event.title} +
+
+ + )} +
diff --git a/components/layout/MobileMenu.tsx b/components/layout/MobileMenu.tsx index b9034e5..734eb1f 100644 --- a/components/layout/MobileMenu.tsx +++ b/components/layout/MobileMenu.tsx @@ -1,11 +1,31 @@ 'use client' -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import Link from 'next/link' -import { events } from "@/utility/constant.utils"; +import axios from "axios"; export default function MobileMenu({ isMobileMenu, handleMobileMenu }: any) { const [isAccordion, setIsAccordion] = useState(null); - const upcomingEvents = events.filter((event: any) => event.link); + const [upcomingEvents, setUpcomingEvents] = useState([]); + + useEffect(() => { + const fetchEvents = async () => { + try { + const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3006/api'; + const response = await axios.get(`${API_BASE_URL}/upcoming-events`); + if (response.data && response.data.success) { + const evts = response.data.data.map((e: any) => ({ + id: e.id, + title: e.title || e.eventtitle, + link: e.link || (e.slug ? `/upcoming-event/${e.slug}` : `/upcoming-event/${e.id}`) + })); + setUpcomingEvents(evts); + } + } catch (error) { + console.error("Error fetching upcoming events for mobile menu:", error); + } + }; + fetchEvents(); + }, []); const handleAccordion = (key: any) => { setIsAccordion(prevState => prevState === key ? null : key) @@ -105,25 +125,15 @@ export default function MobileMenu({ isMobileMenu, handleMobileMenu }: any) {
  • handleAccordion(5)}> - Events + Upcoming Event
      -
    • - setSubAccordion(subAccordion === 1 ? 0 : 1)}> - Upcoming Event - -
        - {upcomingEvents.map((event: any) => ( -
      • - - {event.title} - -
      • - ))} -
      -
    • -
    • - Photos Gallery -
    • + {upcomingEvents.map((event: any) => ( +
    • + + {event.title} + +
    • + ))}
  • handleAccordion(2)}> diff --git a/components/layout/header/Header1.tsx b/components/layout/header/Header1.tsx index 001c8e0..a606d66 100644 --- a/components/layout/header/Header1.tsx +++ b/components/layout/header/Header1.tsx @@ -1,17 +1,37 @@ "use client"; import Link from "next/link"; -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Swiper, SwiperSlide } from "swiper/react"; import { Autoplay, Pagination } from "swiper/modules"; -import { events } from "@/utility/constant.utils"; +import axios from "axios"; import "swiper/css"; import "swiper/css/pagination"; export default function Header1({ scroll, isMobileMenu, handleMobileMenu, isSearch, handleSearch }: any) { const [mobileMenu, setMobileMenu] = useState(false); - const upcomingEvents = events.filter((event: any) => event.link); + const [upcomingEvents, setUpcomingEvents] = useState([]); + + useEffect(() => { + const fetchEvents = async () => { + try { + const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3006/api'; + const response = await axios.get(`${API_BASE_URL}/upcoming-events`); + if (response.data && response.data.success) { + const evts = response.data.data.map((e: any) => ({ + id: e.id, + title: e.title || e.eventtitle, + link: e.link || (e.slug ? `/upcoming-event/${e.slug}` : `/upcoming-event/${e.id}`) + })); + setUpcomingEvents(evts); + } + } catch (error) { + console.error("Error fetching upcoming events for header:", error); + } + }; + fetchEvents(); + }, []); return ( <> @@ -55,7 +75,7 @@ export default function Header1({ scroll, isMobileMenu, handleMobileMenu, isSear style={{ background: "#000000ff", borderTop: "1px solid #cbc5c557" }} > -
    +
    @@ -63,7 +83,7 @@ export default function Header1({ scroll, isMobileMenu, handleMobileMenu, isSear logo
    -
      +
      • Home {/*
        @@ -235,23 +255,15 @@ export default function Header1({ scroll, isMobileMenu, handleMobileMenu, isSear
    • - Events + Upcoming Event
        -
      • - - Upcoming Event - -
          - {upcomingEvents.map((event: any) => ( -
        • - - {event.title} - -
        • - ))} -
        -
      • - {/*
      • Photos Gallery
      • */} + {upcomingEvents.map((event: any) => ( +
      • + + {event.title} + +
      • + ))}
    • diff --git a/next.config.mjs b/next.config.mjs index 9030961..a3c5333 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,8 +1,7 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - output: "export", reactStrictMode: false, - trailingSlash: true, // if needed for static export + trailingSlash: true, }; export default nextConfig; diff --git a/public/assets/css/main.css b/public/assets/css/main.css index 35d5a90..c470de0 100644 --- a/public/assets/css/main.css +++ b/public/assets/css/main.css @@ -9762,7 +9762,7 @@ html { color: #000000cf; display: inline-block; transition: all 0.4s; - padding: 0 20px; + padding: 0 12px; } .homepage1-body .header-area.homepage1 .header-elements .main-menu ul li:hover>a { @@ -9795,7 +9795,9 @@ html { position: absolute; background: var(--ztc-text-text-1); top: 100px; - width: 225px; + min-width: 360px; + width: max-content; + max-width: 440px; z-index: 1; transition: all 0.4s; border-radius: 5px; @@ -9875,11 +9877,13 @@ html { font-weight: var(--ztc-weight-medium); transition: all 0.4s; padding: 8px; - display: inline-block; + display: block; position: relative; z-index: 1; border-radius: 4px; color: var(--ztc-text-text-2); + white-space: normal; + word-break: break-word; } .homepage1-body .header-area.homepage1 .header-elements .main-menu ul li ul.dropdown-padding li a::after { @@ -10252,7 +10256,9 @@ html { background: var(--ztc-text-text-1); top: 201.3%; transform: scale(1, 0); - width: 225px; + min-width: 360px; + width: max-content; + max-width: 440px; z-index: 0; transition: all 0.4s; border-radius: 5px; @@ -18048,7 +18054,8 @@ html { content: ""; height: 93%; width: 1px; - left: 45.7%; + left: 50%; + transform: translateX(-50%); top: 0; transition: all 0.4s; background: #E6E7E8; @@ -18082,7 +18089,8 @@ html { background: #FFBA00; border-radius: 50%; position: absolute; - left: 43%; + left: 50%; + transform: translateX(-50%); top: 88px; z-index: 1; } @@ -24348,7 +24356,8 @@ html { content: ""; height: 93%; width: 1px; - left: 45.7%; + left: 50%; + transform: translateX(-50%); top: 0; transition: all 0.4s; background: #E6E7E8; @@ -24382,7 +24391,8 @@ html { background: var(--ztc-text-text-15); border-radius: 50%; position: absolute; - left: 43%; + left: 50%; + transform: translateX(-50%); top: 88px; z-index: 1; } @@ -25296,7 +25306,8 @@ html { content: ""; height: 93%; width: 1px; - left: 45.7%; + left: 50%; + transform: translateX(-50%); top: 0; transition: all 0.4s; background: #E6E7E8; @@ -25330,7 +25341,8 @@ html { background: #FFBA00; border-radius: 50%; position: absolute; - left: 43%; + left: 50%; + transform: translateX(-50%); top: 88px; z-index: 1; } @@ -25610,7 +25622,8 @@ html { content: ""; height: 93%; width: 1px; - left: 45.7%; + left: 50%; + transform: translateX(-50%); top: 0; transition: all 0.4s; background: #5D9BFA; @@ -25644,7 +25657,8 @@ html { background: linear-gradient(135deg, #FF7A00 0%, #F00 100%); border-radius: 50%; position: absolute; - left: 43%; + left: 50%; + transform: translateX(-50%); top: 88px; z-index: 1; } @@ -26989,7 +27003,8 @@ html { content: ""; height: 93%; width: 1px; - left: 45.7%; + left: 50%; + transform: translateX(-50%); top: 0; transition: all 0.4s; background: #E6E7E8; @@ -27023,7 +27038,8 @@ html { background: var(--LLL, linear-gradient(135deg, #FF5000 0%, #FF00B8 100%)); border-radius: 50%; position: absolute; - left: 43%; + left: 50%; + transform: translateX(-50%); top: 88px; z-index: 1; } diff --git a/public/assets/img/event/upcoming-event/tca-singapengal-information-night.webp b/public/assets/img/event/upcoming-event/tca-singapengal-information-night.webp new file mode 100644 index 0000000..5665b53 Binary files /dev/null and b/public/assets/img/event/upcoming-event/tca-singapengal-information-night.webp differ diff --git a/public/assets/scss/layout/header/_header-1.scss b/public/assets/scss/layout/header/_header-1.scss index a34a86a..f8d6cc8 100644 --- a/public/assets/scss/layout/header/_header-1.scss +++ b/public/assets/scss/layout/header/_header-1.scss @@ -158,7 +158,7 @@ html { color: var(--ztc-text-text-2); display: inline-block; transition: all .4s; - padding: 0 20px; + padding: 0 12px; } &:hover>a { @@ -192,7 +192,9 @@ html { position: absolute; background: var(--ztc-text-text-1); top: 100px; - width: 225px; + min-width: 360px; + width: max-content; + max-width: 440px; z-index: 1; transition: all .4s; border-radius: 5px; diff --git a/public/assets/scss/layout/pages/_event.scss b/public/assets/scss/layout/pages/_event.scss index f3c28cb..14e4e59 100644 --- a/public/assets/scss/layout/pages/_event.scss +++ b/public/assets/scss/layout/pages/_event.scss @@ -597,7 +597,8 @@ content: ""; height: 93%; width: 1px; - left: 45.7%; + left: 50%; + transform: translateX(-50%); top: 0; transition: all 0.4s; background: #E6E7E8; @@ -627,7 +628,8 @@ background: var(--LLL, linear-gradient(135deg, #FF5000 0%, #FF00B8 100%)); border-radius: 50%; position: absolute; - left: 43%; + left: 50%; + transform: translateX(-50%); top: 88px; z-index: 1; @@ -1558,7 +1560,8 @@ content: ""; height: 93%; width: 1px; - left: 45.7%; + left: 50%; + transform: translateX(-50%); top: 0; transition: all 0.4s; background: #E6E7E8; @@ -1588,7 +1591,8 @@ background: #FFBA00; border-radius: 50%; position: absolute; - left: 43%; + left: 50%; + transform: translateX(-50%); top: 88px; z-index: 1; @@ -1871,7 +1875,8 @@ content: ""; height: 93%; width: 1px; - left: 45.7%; + left: 50%; + transform: translateX(-50%); top: 0; transition: all 0.4s; background: #5D9BFA; @@ -1901,7 +1906,8 @@ background: linear-gradient(135deg, #FF7A00 0%, #F00 100%); border-radius: 50%; position: absolute; - left: 43%; + left: 50%; + transform: translateX(-50%); top: 88px; z-index: 1; @@ -2967,7 +2973,8 @@ content: ""; height: 93%; width: 1px; - left: 45.7%; + left: 50%; + transform: translateX(-50%); top: 0; transition: all 0.4s; background: #E6E7E8; @@ -2997,7 +3004,8 @@ background: var(--LLL, linear-gradient(135deg, #FF5000 0%, #FF00B8 100%)); border-radius: 50%; position: absolute; - left: 43%; + left: 50%; + transform: translateX(-50%); top: 88px; z-index: 1;