+
+
+
+
+ {info ? (
+
+ {info_images && info_images.length > 0 && (
+
+
+
+ {info_images.map((image, index) => (
+
+
+
+ ))}
+
+
+ {/*
*/}
+
+
+ {info_images.map((image, index) => (
+
+
+
+ {index !== currentSlide && (
+
+ )}
+
+
+ ))}
+
+
+
+
+ )}
+
)
+ :
+ (
)}
+
+
+
+
+
+ ) : (
+
+ )
+ )
+}
+export default Collapse
diff --git a/components/Course/Course.tsx b/components/Course/Course.tsx
new file mode 100644
index 0000000..6c9cd98
--- /dev/null
+++ b/components/Course/Course.tsx
@@ -0,0 +1,20 @@
+import React from 'react'
+import Banner from './Banner'
+import LongDesc from './LongDesc'
+import CourseImagesSilder from './CourseImagesSilder'
+import Accordion from './Accordion'
+import { CoursesProps } from '@/types'
+import Footer from '../Footer'
+const Course = ({ course }: { course: CoursesProps }) => {
+ return (
+
+ )
+}
+
+export default Course
diff --git a/components/Course/CourseImagesSilder.tsx b/components/Course/CourseImagesSilder.tsx
new file mode 100644
index 0000000..3f20327
--- /dev/null
+++ b/components/Course/CourseImagesSilder.tsx
@@ -0,0 +1,158 @@
+"use client"
+import React, { useState } from 'react'
+import "slick-carousel/slick/slick.css";
+import "slick-carousel/slick/slick-theme.css";
+import Slider from "react-slick";
+import './slick.css'
+import { CoursesProps, CoursesArrayProps } from "@/types";
+import { IoIosArrowForward, IoIosArrowBack } from "react-icons/io";
+
+const SamplePrevArrow = (props: any) => {
+ const { className, style, onClick } = props;
+ return (
+
+
+
+ );
+}
+
+
+function SampleNextArrow(props: any) {
+ const { className, style, onClick } = props;
+ return (
+
+
+
+ );
+}
+
+const CourseImagesSilder = ({ courseData }: { courseData: CoursesProps }) => {
+ const [currentSlide, setCurrentSlide] = useState(0);
+ var settings = {
+ arrows: false,
+ className: "center",
+ centerMode: true,
+ infinite: true,
+ centerPadding: "30px",
+ dots: true,
+ speed: 500,
+ slidesToShow: 1,
+ slidesToScroll: 1,
+ beforeChange: (current: number, next: number) => setCurrentSlide(next),
+ appendDots: (dots: any) => (
+
+ ),
+ dotsClass: 'dots_custom'
+ };
+
+ var settings2 = {
+ fade: true,
+ centerMode: true,
+ infinite: true,
+ dots: true,
+ speed: 500,
+ slidesToShow: 1,
+ slidesToScroll: 1,
+ nextArrow:
,
+ prevArrow:
,
+ beforeChange: (current: number, next: number) => setCurrentSlide(next),
+ appendDots: (dots: any) => (
+
+ ),
+ dotsClass: 'dots_custom'
+ };
+
+
+
+
+ return (
+
+
+
+
+ {courseData.images.length > 0 && (
+
+
+ {courseData.images.map((image, index) => (
+
+
+
+ ))}
+
+
+
+ )}
+
+
+ {courseData.images.length > 0 && (
+
+
+ {courseData.images.map((image, index) => {
+ return (
+
+
+
+ {index !== currentSlide && (
+
+ )}
+
+
+ )
+ })}
+
+
+ )}
+
+
+
+
+
+ )
+}
+
+export default CourseImagesSilder
diff --git a/components/Course/LongDesc.tsx b/components/Course/LongDesc.tsx
new file mode 100644
index 0000000..3447b3f
--- /dev/null
+++ b/components/Course/LongDesc.tsx
@@ -0,0 +1,20 @@
+import React from 'react'
+import { CoursesProps } from '@/types'
+const LongDesc = ({ courseData }: { courseData: CoursesProps }) => {
+ return (
+
+
+
+ {courseData.title}
+
+
+
+
+
+ )
+}
+
+export default LongDesc
diff --git a/components/Course/ScheduleCollapse.tsx b/components/Course/ScheduleCollapse.tsx
new file mode 100644
index 0000000..5369e04
--- /dev/null
+++ b/components/Course/ScheduleCollapse.tsx
@@ -0,0 +1,75 @@
+import React, { useState, useRef, useEffect } from 'react'
+import { cn } from '../utils'
+import { VscAdd, VscChromeMinimize } from "react-icons/vsc";
+import { ScheduleProps } from '@/types';
+import moment from 'moment';
+
+interface CollapseProps {
+ title: string
+ rerganizedSchedule?: {
+ yearAndMonth: string
+ schedules: ScheduleProps[]
+ }
+
+}
+
+const ScheduleCollapse: React.FC
= ({ title, rerganizedSchedule }) => {
+
+
+ const [isOpen, setIsOpen] = useState(false)
+ const [height, setHeight] = useState(0)
+ const ref = useRef(null)
+
+ useEffect(() => {
+ if (isOpen) setHeight(ref.current?.scrollHeight)
+ else setHeight(0)
+ }, [isOpen])
+
+ return (
+
+
+
+
+
+
+ {rerganizedSchedule ? (
+
+ {rerganizedSchedule.schedules.map((schedule, index) => (
+
+
+ { moment.utc(schedule.date).format("MM月DD日")}
+
+
{schedule.title}
+
{schedule.info1}
+
{schedule.info2}
+
+ ))}
+
+ ) : (
+
+ )}
+
+
+
+
+ )
+
+}
+export default ScheduleCollapse
diff --git a/components/Course/slick.css b/components/Course/slick.css
new file mode 100644
index 0000000..b48ece5
--- /dev/null
+++ b/components/Course/slick.css
@@ -0,0 +1,30 @@
+.dots_custom {
+ display: inline-block;
+ vertical-align: middle;
+ margin: auto 0;
+ padding: 0;
+ }
+
+ .dots_custom li {
+ list-style: none;
+ cursor: pointer;
+ display: inline-block;
+ margin: 0 6px;
+ padding: 0;
+ }
+
+ .dots_custom li button {
+ border: none;
+ background: #d1d1d1;
+ color: transparent;
+ cursor: pointer;
+ display: block;
+ height: 8px;
+ width: 8px;
+ border-radius: 100%;
+ padding: 0;
+ }
+
+ .dots_custom li.slick-active button {
+ background-color: #D60050;
+ }
\ No newline at end of file
diff --git a/components/CoursesSilder.tsx b/components/CoursesSilder.tsx
new file mode 100644
index 0000000..dc7ac45
--- /dev/null
+++ b/components/CoursesSilder.tsx
@@ -0,0 +1,112 @@
+"use client";
+import CustomButton from "./CustomButton";
+import { useRef, useEffect, useState } from 'react';
+import Image from 'next/image';
+import { CoursesProps, CoursesArrayProps } from "@/types";
+import CustomButtom from "./CustomButton";
+import "slick-carousel/slick/slick.css";
+import "slick-carousel/slick/slick-theme.css";
+import Slider from "react-slick";
+import './slick.css'
+import { GoDot } from "react-icons/go";
+//npm i --save-dev @types/react-slick
+
+
+
+
+const CoursesSilder = ({ courses }: { courses: CoursesProps[] }) => {
+ var settings = {
+ dots: true,
+ infinite: true,
+ speed: 500,
+ slidesToShow: 1,
+ slidesToScroll: 1,
+ appendDots: (dots: any) => (
+
+ ),
+ dotsClass: 'dots_custom'
+ };
+ console.log(courses)
+ return (
+
+ {courses.length > 0 ? (
+
+
+ {courses.map((course, index) => {
+ return (
+
+
+
+
+
+
+
+
+ {course.title}
+
+
+ {course.sort_description}
+
+
+
+
+
+
+
+
+
+
+
+
+ {course.title}
+
+
+ {course.sort_description}
+
+
+
+
+
+ )
+ })}
+
+
+ ) : (
+
+ )}
+
+ )
+}
+
+export default CoursesSilder;
+
diff --git a/components/CustomButton.tsx b/components/CustomButton.tsx
new file mode 100644
index 0000000..e09ed05
--- /dev/null
+++ b/components/CustomButton.tsx
@@ -0,0 +1,17 @@
+"use client"
+import React from 'react'
+import { CustomButtonProps } from '@/types'
+const CustomButton = ({ isDisabled, title, handleClick, text_and_button_size }: CustomButtonProps) => {
+ return (
+
+ )
+}
+
+export default CustomButton
diff --git a/components/Footer.tsx b/components/Footer.tsx
new file mode 100644
index 0000000..6ff328d
--- /dev/null
+++ b/components/Footer.tsx
@@ -0,0 +1,43 @@
+"use client"
+import Link from 'next/link'
+import { FaFacebookF, FaYoutube } from "react-icons/fa";
+import { RiInstagramFill } from "react-icons/ri";
+import { SettingsProps } from '@/types';
+import { IoMenu } from "react-icons/io5";
+const Footer = ({ settings }: { settings: SettingsProps }) => {
+ return (
+
+
+
+ )
+}
+
+export default Footer
diff --git a/components/Hero1.tsx b/components/Hero1.tsx
new file mode 100644
index 0000000..07c027b
--- /dev/null
+++ b/components/Hero1.tsx
@@ -0,0 +1,124 @@
+"use client";
+
+import "animate.css/animate.compat.css"
+import ScrollAnimation from 'react-animate-on-scroll';
+import { useRef, useEffect, useState } from 'react';
+import TextGradientScroll from './TextGradientScrollContext';
+import Image from 'next/image';
+import CustomButton from "./CustomButton";
+
+const Hero1 = () => {
+ const [paragraphOpacities, setParagraphOpacities] = useState([]);
+ const scrollRef = useRef(null);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ if (scrollRef.current) {
+ const container = scrollRef.current;
+ const isAtBottom = Math.abs(container.scrollHeight - container.scrollTop - container.clientHeight) < 5;
+
+
+ if (isAtBottom) {
+ // Set all paragraphs to full opacity when at the bottom
+ setParagraphOpacities(new Array(11).fill(1));
+ } else {
+ const paragraphs = container.querySelectorAll('p');
+ const newOpacities = Array.from(paragraphs).map(p => {
+ const rect = p.getBoundingClientRect();
+ const yPosition = rect.top - container.getBoundingClientRect().top;
+ return yPosition > 30 ? 0 : 1;
+ });
+ setParagraphOpacities(newOpacities);
+ }
+ }
+ };
+ setParagraphOpacities(new Array(9).fill(0));
+ if (scrollRef.current) {
+ scrollRef.current.addEventListener('scroll', handleScroll);
+ setTimeout(handleScroll, 100);
+
+ }
+
+ return () => {
+ if (scrollRef.current) {
+ scrollRef.current.removeEventListener('scroll', handleScroll);
+ }
+ };
+ }, []);
+
+
+ return (
+
+ {/* Existing content */}
+
+
+
+
+ 發掘你的
+
+
+
+ 音樂之路
+
+
+
+
+
+ {[
+ "學音樂能陶冶性情,專注,舒緩壓力,放鬆心情。實際上是否做到呢?",
+ "我們認為音樂不單單是「睇五線譜」",
+ "我們可以透過不同方式",
+ "例如聆聽,唱歌,身體律動等學習音樂,令學習不再是沉悶艱難",
+ "真正做到「陶冶性情」",
+ "One & ALL Music 提供全面的音樂課程",
+ "我們相信每位學生都各有天賦",
+ "「誰都可發光 只要找對地方」",
+ "只要運用合適的教學法,同學必定能發揮所長",
+ " ",
+ " ",
+ ].map((text, index) => (
+
+ {text}
+
+ ))}
+
+
+
+
+
+
+ {"音樂不單單是「睇五線譜」,我們可以透過不同方式,例如聆聽唱歌,身體律動等學習音樂,令學習不再是沉悶艱難,真正做到「陶治性情」。"}
+
+
+ One & ALL Music 提供全面的音樂課程,我們相信每位學生都各有天赋,
「誰都可發光 只要找對地方」,只要運用合適的教學法,同學必定能發揮所長。
+
+
+
+ {
+ console.log('Button clicked');
+ }} />
+
+
+
+
+
+
+
+
+ );
+}
+export default Hero1
diff --git a/components/Hero2.tsx b/components/Hero2.tsx
new file mode 100644
index 0000000..9d4c91d
--- /dev/null
+++ b/components/Hero2.tsx
@@ -0,0 +1,33 @@
+"use client";
+import CustomButton from "./CustomButton";
+import { useRef, useEffect, useState } from 'react';
+import Image from 'next/image';
+import { CoursesProps, SettingsProps } from "@/types";
+
+
+function convertToEmbedLink(watchLink: string): string {
+ const videoIdMatch = watchLink.match(/(?:v=|\/)([a-zA-Z0-9_-]{11})/);
+ if (videoIdMatch && videoIdMatch[1]) {
+ const videoId = videoIdMatch[1];
+ return `https://www.youtube.com/embed/${videoId}`;
+ }
+ return watchLink; // Return original link if conversion fails
+}
+
+const Hero2 = ({ settings }: { settings: SettingsProps }) => {
+ return (
+
+ )
+}
+
+export default Hero2
diff --git a/components/Home/Home.tsx b/components/Home/Home.tsx
new file mode 100644
index 0000000..d42be10
--- /dev/null
+++ b/components/Home/Home.tsx
@@ -0,0 +1,53 @@
+"use client";
+
+import { useState, useEffect, lazy, Suspense } from "react";
+import { Audio } from "react-loader-spinner";
+const Hero1 = lazy(() => import('../Hero1'));
+import Hero2 from '../Hero2'
+import CoursesSilder from "../CoursesSilder";
+import ContactForm from '../ContactForm'
+import Loading from '../Loading'
+import Footer from '../Footer'
+import { fetchCourses } from '../../utils/index'
+import { CoursesArrayProps, CoursesProps, SettingsProps } from "@/types";
+import Whatsapp from "../Whatsapp";
+const Home = ({ courses, settings }: { courses: CoursesProps[], settings: SettingsProps }) => {
+ const [loading, setLoading] = useState(true);
+ // const [courses, setCourses] = useState([]);
+
+ useEffect(() => {
+ const loadCourses = async () => {
+ setLoading(true);
+ await new Promise(resolve => setTimeout(resolve, 300)); // 0.5 second delay
+ setLoading(false);
+ }; loadCourses();
+ }, []);
+
+ const startLoading = () => setLoading(true);
+ const stopLoading = () => setLoading(false);
+
+ console.log(courses);
+ return (
+
+ {loading ? (
+
+ Loading...
}>
+
+
+
+ ) : (
+
+
+
+ Loading...
}>
+
+
+
+
+
+
+ )}
+