first commit
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
colors:{
|
||||||
|
|
||||||
|
},
|
||||||
|
api_link: "http://localhost/api/v1/",
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react'
|
||||||
|
import ResponsiveNav from "@/components/Navbar/ResponsiveNav";
|
||||||
|
import { fetchCourses, fetchCourse, fetchSettings } from "@/utils";
|
||||||
|
import { CoursesProps } from "@/types";
|
||||||
|
import Course from '@/components/Course/Course';
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
const DynamicComponent = dynamic(() => import('@/components/Course/Course'), {
|
||||||
|
ssr: false,
|
||||||
|
})
|
||||||
|
async function getCourses() {
|
||||||
|
const courses = await fetchCourses();
|
||||||
|
return courses;
|
||||||
|
}
|
||||||
|
async function getCourse(slug: string) {
|
||||||
|
const course = await fetchCourse(slug);
|
||||||
|
return course;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSettings() {
|
||||||
|
const settings = await fetchSettings();
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "All In One",
|
||||||
|
description: "**",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ params }: { params: { slug: string } }) {
|
||||||
|
const courses = await getCourses();
|
||||||
|
const settings = await getSettings();
|
||||||
|
//const course = await getCourse(params.slug);
|
||||||
|
const course = courses.find(course => course.id === params.slug);
|
||||||
|
if (!course) {
|
||||||
|
throw new Error(`Course with slug ${params.slug} not found`);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='bg-[#F6E8E8]'>
|
||||||
|
<ResponsiveNav courses={courses} settings={settings} />
|
||||||
|
<DynamicComponent course={course} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
BIN
app/favicon.ico
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 5.9 KiB |
|
@ -2,26 +2,45 @@
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
@layer components {
|
||||||
--background: #ffffff;
|
.nav__link {
|
||||||
--foreground: #171717;
|
@apply relative text-base font-medium w-fit hover:text-[#D60050];
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
}
|
||||||
}
|
.custom-position {
|
||||||
|
object-position: 60% -3px;
|
||||||
|
}
|
||||||
|
.mainBgColor {
|
||||||
|
background-color: #D60050;
|
||||||
|
}
|
||||||
|
.mainColor{
|
||||||
|
color: #D60050;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dots {
|
||||||
|
li{
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
button::before {
|
||||||
color: var(--foreground);
|
font-size: 2rem !important;
|
||||||
background: var(--background);
|
color: #D60050 !important;
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.overflow-hidden {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.text-balance {
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
text-wrap: balance;
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
/* Hide scrollbar for IE, Edge and Firefox */
|
||||||
|
.no-scrollbar {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
}
|
}
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import localFont from "next/font/local";
|
import localFont from "next/font/local";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import ResponsiveNav from "../components/Navbar/ResponsiveNav";
|
||||||
|
import { fetchCourses } from "@/utils";
|
||||||
|
import { TailSpin } from "react-loader-spinner";
|
||||||
|
import { Toaster } from "react-hot-toast";
|
||||||
const geistSans = localFont({
|
const geistSans = localFont({
|
||||||
src: "./fonts/GeistVF.woff",
|
src: "./fonts/GeistVF.woff",
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
|
@ -19,6 +22,7 @@ export const metadata: Metadata = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -26,10 +30,13 @@ export default function RootLayout({
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-[#F6E5E9]`}
|
||||||
>
|
>
|
||||||
|
<Toaster position="bottom-center" />
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
122
app/page.tsx
|
@ -1,101 +1,33 @@
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import Home from "../components/Home/Home";
|
||||||
|
import { fetchCourses, fetchSettings} from "../utils/index";
|
||||||
|
import { CoursesProps } from "@/types";
|
||||||
|
import ResponsiveNav from "../components/Navbar/ResponsiveNav";
|
||||||
|
type Props = {}
|
||||||
|
|
||||||
export default function Home() {
|
async function getCourses() {
|
||||||
|
const courses = await fetchCourses();
|
||||||
|
return courses;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSettings(){
|
||||||
|
const settings = await fetchSettings();
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "All In One",
|
||||||
|
description: "**",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function HomePage({ }: Props) {
|
||||||
|
const courses = await getCourses();
|
||||||
|
const settings = await getSettings();
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
<div>
|
||||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
<ResponsiveNav courses={courses} settings={settings} />
|
||||||
<Image
|
<Home courses={courses} settings={settings} />
|
||||||
className="dark:invert"
|
|
||||||
src="https://nextjs.org/icons/next.svg"
|
|
||||||
alt="Next.js logo"
|
|
||||||
width={180}
|
|
||||||
height={38}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
|
||||||
<li className="mb-2">
|
|
||||||
Get started by editing{" "}
|
|
||||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
|
||||||
app/page.tsx
|
|
||||||
</code>
|
|
||||||
.
|
|
||||||
</li>
|
|
||||||
<li>Save and see your changes instantly.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
|
||||||
<a
|
|
||||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="https://nextjs.org/icons/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
Deploy now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Read our docs
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="https://nextjs.org/icons/file.svg"
|
|
||||||
alt="File icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Learn
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="https://nextjs.org/icons/window.svg"
|
|
||||||
alt="Window icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Examples
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="https://nextjs.org/icons/globe.svg"
|
|
||||||
alt="Globe icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Go to nextjs.org →
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
"use client"
|
||||||
|
import React from 'react'
|
||||||
|
import CustomButton from './CustomButton'
|
||||||
|
import { useForm, SubmitHandler } from "react-hook-form"
|
||||||
|
import { postMessage } from '@/utils'
|
||||||
|
import { MessageProps } from '@/types'
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
startLoading: () => void
|
||||||
|
stopLoading: () => void
|
||||||
|
}
|
||||||
|
const ContactForm = ({ startLoading, stopLoading }: Props) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
reset,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<MessageProps>()
|
||||||
|
const onSubmit: SubmitHandler<MessageProps> = async (data) => {
|
||||||
|
try {
|
||||||
|
startLoading; // Set loading state to true
|
||||||
|
const result = await postMessage(data);
|
||||||
|
if (result.success) {
|
||||||
|
toast.success('已收到您的訊息,我們會盡快回覆您!');
|
||||||
|
reset(); // Reset form fields
|
||||||
|
// Reset form or perform any other actions on success
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to send message. Please try again.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending message:', error);
|
||||||
|
toast.error('An error occurred. Please try again later.');
|
||||||
|
} finally {
|
||||||
|
stopLoading; // Set loading state back to false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className="w-full flex flex-col h-auto items-center justify-center relative " >
|
||||||
|
<div className="text-4xl sm:text-3xl lg:text-5xl text-center mt-[10vh]">
|
||||||
|
讓我們知道您的想法
|
||||||
|
</div>
|
||||||
|
<div className="bg-[url('/images/014.png')] absolute bottom--10 inset-0 h-auto w-full bg-contain bg-bottom bg-no-repeat hidden lg:block"></div>
|
||||||
|
|
||||||
|
<div className="font-[sans-serif] w-full md:h-[62vh] max-w-5xl relative bg-transparent lg:bg-white shadow-none lg:shadow-[0_2px_10px_-3px_rgba(6,81,237,0.3)] rounded-3xl overflow-hidden lg:mt-14 lg:mb-56 sm:my-15 max-md:my-0 sm:mx-20">
|
||||||
|
|
||||||
|
|
||||||
|
<div className="grid lg:grid-cols-10 ">
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="flex rounded-tl-3xl rounded-bl-3xl h-[62vh] lg:col-span-6 items-center justify-center">
|
||||||
|
|
||||||
|
<div className="space-y-3 w-full mx-11">
|
||||||
|
<div className="grid md:grid-cols-2 space-y-3">
|
||||||
|
<div className=" md:mr-3 mt-3">
|
||||||
|
<p className='text-sm'>姓名</p>
|
||||||
|
<input type='text'
|
||||||
|
{...register("name", { required: true })}
|
||||||
|
className="w-full mt-3 max-lg:bg-gray-100 md:bg-gray-100 max-sm:bg-[#F6E2E3] rounded-md py-3 px-4 text-sm outline-[#EA004F] md:focus-within:bg-transparent border border-gray-200" />
|
||||||
|
</div>
|
||||||
|
<div >
|
||||||
|
<p className='text-sm'>電話</p>
|
||||||
|
<input type='phone'
|
||||||
|
{...register("phone", { required: true })}
|
||||||
|
className="w-full mt-3 max-lg:bg-gray-100 md:bg-gray-100 max-sm:bg-[#F6E2E3] rounded-md py-3 px-4 text-sm outline-[#EA004F] md:focus-within:bg-transparent border border-gray-200" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-5">
|
||||||
|
<p className='text-sm'>電郵</p>
|
||||||
|
<input type='email'
|
||||||
|
{...register("email", { required: true })}
|
||||||
|
className="w-full mt-3 max-lg:bg-gray-100 md:bg-gray-100 max-sm:bg-[#F6E2E3] rounded-md py-3 px-4 text-sm outline-[#EA004F] md:focus-within:bg-transparent border border-gray-200" />
|
||||||
|
</div>
|
||||||
|
<div className="mt-5">
|
||||||
|
<p className='text-sm'>訊息</p>
|
||||||
|
<textarea
|
||||||
|
{...register("message", { required: true })}
|
||||||
|
className="w-full mt-3 h-36 max-lg:bg-gray-100 md:bg-gray-100 max-sm:bg-[#F6E2E3] rounded-md px-4 text-sm pt-3 outline-[#EA004F] md:focus-within:bg-transparent border border-gray-200"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className={`middle none center rounded-full md:px-8 md:py-3 text-sm max-sm:w-full py-3 px-8 bg-mainColor font-bold uppercase text-white shadow-md shadow-pink-500/20 transition-all hover:shadow-lg hover:shadow-pink-500/40`}
|
||||||
|
data-ripple-light="true"
|
||||||
|
|
||||||
|
>
|
||||||
|
{'發送'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div className="text-center bg-[url('/images/contact.png')] bg-cover h-auto w-full lg:col-span-4 sm:hidden md:hidden lg:block">
|
||||||
|
{/* <img src="/images/contact.png" /> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContactForm
|
|
@ -0,0 +1,124 @@
|
||||||
|
"use client"
|
||||||
|
import React from 'react'
|
||||||
|
import Collapse from './Collapse'
|
||||||
|
import ScheduleCollapse from './ScheduleCollapse'
|
||||||
|
import { CoursesProps, ScheduleProps, RerganizedScheduleProps } from '@/types'
|
||||||
|
|
||||||
|
|
||||||
|
async function reorganizedSchedule(schedule: ScheduleProps[]) {
|
||||||
|
|
||||||
|
const sortedSchedule = schedule.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
||||||
|
|
||||||
|
// Then, reorganize the sorted schedule
|
||||||
|
const reorganizedSchedule = sortedSchedule.reduce<Record<string, { yearAndMonth: string; schedule: ScheduleProps[] }>>((acc, item) => {
|
||||||
|
const date = new Date(item.date);
|
||||||
|
const yearMonth = `${date.getFullYear()}年${String(date.getMonth() + 1).padStart(2, '0')}月`;
|
||||||
|
|
||||||
|
if (!acc[yearMonth]) {
|
||||||
|
acc[yearMonth] = {
|
||||||
|
yearAndMonth: yearMonth,
|
||||||
|
schedule: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[yearMonth].schedule.push(item);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return Object.values(reorganizedSchedule);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const Accordion = async ({ courseData }: { courseData: CoursesProps }) => {
|
||||||
|
const newSchedule = await reorganizedSchedule(courseData.schedule)
|
||||||
|
|
||||||
|
const fakeData = [
|
||||||
|
{
|
||||||
|
"yearAndMonth": "2024年10月",
|
||||||
|
"schedule": [
|
||||||
|
{
|
||||||
|
"info2": "A room, 14F, Sha Tin Market, N.T.",
|
||||||
|
"info1": "11:00-12:00, 13:00-14:00, 15:00-16:00, 17:00-18:00",
|
||||||
|
"id": "e19a7934-82fe-4027-aca6-57a419e9623e",
|
||||||
|
"title": "A班",
|
||||||
|
"date": "2024-10-05T01:30:00Z",
|
||||||
|
"course_id": "29ab193d-2927-459d-9277-5520771b2dd6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"info2": "Room B, Sheung Shui Village, N.T.",
|
||||||
|
"info1": "11:00-12:00, 13:00-14:00, 15:00-16:00, 17:00-18:00",
|
||||||
|
"id": "dc49a1b6-1c1f-44d6-bc52-b757aeba7b5b",
|
||||||
|
"title": "B班",
|
||||||
|
"date": "2024-10-05T01:31:00Z",
|
||||||
|
"course_id": "29ab193d-2927-459d-9277-5520771b2dd6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"info2": "Room B, Sheung Shui Village, N.T.",
|
||||||
|
"info1": "11:00-12:00, 13:00-14:00, 15:00-16:00, 17:00-18:00",
|
||||||
|
"id": "d4785a90-311b-4a91-8fb7-beb077245f4f",
|
||||||
|
"title": "A班",
|
||||||
|
"date": "2024-10-06T01:31:00Z",
|
||||||
|
"course_id": "29ab193d-2927-459d-9277-5520771b2dd6"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"yearAndMonth": "2024年11月",
|
||||||
|
"schedule": [
|
||||||
|
{
|
||||||
|
"info2": "Room B, Sheung Shui Village, N.T.",
|
||||||
|
"info1": "11:00-12:00, 13:00-14:00, 15:00-16:00, 17:00-18:00",
|
||||||
|
"id": "5c88b2a7-6b4d-455f-b341-1fec3173d208",
|
||||||
|
"title": "A班",
|
||||||
|
"date": "2024-11-06T01:31:00Z",
|
||||||
|
"course_id": "29ab193d-2927-459d-9277-5520771b2dd6"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"yearAndMonth": "2025年01月",
|
||||||
|
"schedule": [
|
||||||
|
{
|
||||||
|
"info2": "Room B, Sheung Shui Village, N.T.",
|
||||||
|
"info1": "11:00-12:00, 13:00-14:00, 15:00-16:00, 17:00-18:00",
|
||||||
|
"id": "8a30dec2-63ed-4237-8f5b-177c42f29dda",
|
||||||
|
"title": "B班",
|
||||||
|
"date": "2025-01-04T01:31:00Z",
|
||||||
|
"course_id": "29ab193d-2927-459d-9277-5520771b2dd6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"info2": "Room B, Sheung Shui Village, N.T.",
|
||||||
|
"info1": "11:00-12:00, 13:00-14:00, 15:00-16:00, 17:00-18:00",
|
||||||
|
"id": "86ddf906-499b-4dbd-967d-cc4dddd9f27b",
|
||||||
|
"title": "A班",
|
||||||
|
"date": "2025-01-05T01:31:00Z",
|
||||||
|
"course_id": "29ab193d-2927-459d-9277-5520771b2dd6"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
console.log(newSchedule)
|
||||||
|
return (
|
||||||
|
<div className='relative flex flex-col w-full h-auto items-center pb-60'>
|
||||||
|
<Collapse title='課程資訊' children={courseData.information} />
|
||||||
|
<Collapse title='課程內容' children={courseData.contant} info={true} info_images={courseData.info_images} />
|
||||||
|
<Collapse title='備註' children={courseData.remark} />
|
||||||
|
<p className='text-5xl text-center mt-48'>
|
||||||
|
{"課程時間表"}
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
fakeData.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<ScheduleCollapse key={index} title={item.yearAndMonth} rerganizedSchedule={{ yearAndMonth: item.yearAndMonth, schedules: item.schedule }} />
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<div className="bg-[url('/images/014.png')] absolute bottom-0 inset-0 h-auto w-full bg-contain bg-bottom bg-no-repeat -z-10"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Accordion
|
|
@ -0,0 +1,24 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { CoursesProps } from '@/types'
|
||||||
|
const Banner = ({ courseData }: { courseData: CoursesProps }) => {
|
||||||
|
console.log(courseData)
|
||||||
|
return (
|
||||||
|
<div className='relative flex w-full h-[40vh] lg:h-[70vh] bg-gradient-to-r from-[#FDB2B8] to-[#FEB3BA] items-center'>
|
||||||
|
|
||||||
|
<div className='flex-col flex lg:ml-48 ml-4 z-10'>
|
||||||
|
<p className='text-black lg:text-5xl text-3xl font-bold text-center'>
|
||||||
|
{courseData?.title}
|
||||||
|
</p>
|
||||||
|
<img src={"/images/line.png"} className='lg:w-[35vh] w-[20vh] h-auto object-cover mt-2' />
|
||||||
|
</div>
|
||||||
|
{/* <div className='absolute bottom-0 right-0 max-sm:-right-32 md:-right-32 lg:right-32 bg-[url("/images/kid2.png")] bg-cover bg-center bg-no-repeat h-full w-[80%]'></div> */}
|
||||||
|
<img
|
||||||
|
src={"/images/kid2.png"}
|
||||||
|
alt="kid2"
|
||||||
|
className="absolute bottom-0 right-0 lg:right-32 object-cover object-left h-full lg:w-auto :w-40"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Banner
|
|
@ -0,0 +1,168 @@
|
||||||
|
import React, { useState, useRef, useEffect } from 'react'
|
||||||
|
import { cn } from '../utils'
|
||||||
|
import { VscAdd, VscChromeMinimize } from "react-icons/vsc";
|
||||||
|
import { imageProps, } from '@/types';
|
||||||
|
import "slick-carousel/slick/slick.css";
|
||||||
|
import "slick-carousel/slick/slick-theme.css";
|
||||||
|
import Slider from "react-slick";
|
||||||
|
import './slick.css'
|
||||||
|
|
||||||
|
interface CollapseProps {
|
||||||
|
title: string
|
||||||
|
children?: string
|
||||||
|
className?: string
|
||||||
|
info?: boolean
|
||||||
|
info_images?: Array<imageProps>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const Collapse: React.FC<CollapseProps> = ({ title, children, className, info, info_images, }) => {
|
||||||
|
const [currentSlide, setCurrentSlide] = useState(0);
|
||||||
|
var settings2 = {
|
||||||
|
arrows: false,
|
||||||
|
infinite: true,
|
||||||
|
//centerPadding: "30px",
|
||||||
|
dots: true,
|
||||||
|
speed: 500,
|
||||||
|
slidesToShow: 3,
|
||||||
|
slidesToScroll: 1,
|
||||||
|
appendDots: (dots: any) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '24px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ul> {dots} </ul>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
dotsClass: 'dots_custom'
|
||||||
|
};
|
||||||
|
|
||||||
|
var settings1 = {
|
||||||
|
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) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '24px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ul> {dots} </ul>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
dotsClass: 'dots_custom'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [height, setHeight] = useState<number | undefined>(0)
|
||||||
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) setHeight(ref.current?.scrollHeight)
|
||||||
|
else setHeight(0)
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
|
return (
|
||||||
|
children && children.length > 0 ? (
|
||||||
|
<div className={cn('mt-6 rounded-3xl ', className)}>
|
||||||
|
<button
|
||||||
|
className={`flex justify-between items-center w-full p-6 text-left ${isOpen ? "rounded-t-lg bg-mainColor" : "rounded-lg bg-[#F2D6D5] "}`}
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
>
|
||||||
|
<span className={`text-xl font-bold ml-3 ${isOpen ? "text-white " : " text-black"}`}>{title}</span>
|
||||||
|
{isOpen ? (
|
||||||
|
<VscChromeMinimize className='text-white mr-3' />
|
||||||
|
) : (
|
||||||
|
<VscAdd className='text-black mr-3' />
|
||||||
|
)}
|
||||||
|
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
style={{ height: height, }}
|
||||||
|
className="flex relative overflow-hidden transition-[height] duration-300 ease-in-out "
|
||||||
|
>
|
||||||
|
<div className='relative flex flex-col bg-[#FAF6F5] rounded-b-lg w-[65vw] max-sm:w-[90vw]'>
|
||||||
|
|
||||||
|
{info ? (<div className='relative justify-between items-center '>
|
||||||
|
|
||||||
|
{info_images && info_images.length > 0 && (
|
||||||
|
<div className="flex flex-col relative w-ful h-auto ">
|
||||||
|
<div className='relative justify-between items-center hidden lg:flex pt-10'>
|
||||||
|
<Slider className=" w-full h-[17vw] px-12 " {...settings2}>
|
||||||
|
{info_images.map((image, index) => (
|
||||||
|
<div key={index} className="relative w-full h-[13vw] px-1 transition-all justify-items-center ">
|
||||||
|
<img
|
||||||
|
src={`http://localhost/${image.image}`}
|
||||||
|
alt={`Course Image ${index + 1}`}
|
||||||
|
className="w-full h-full object-cover object-center "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Slider>
|
||||||
|
</div>
|
||||||
|
{/* <div className='relative flex sm:block md:block lg:hidden xl:hidden 2xl:hidden'> */}
|
||||||
|
<div className='sm:block md:block lg:hidden xl:hidden 2xl:hidden pt-10'>
|
||||||
|
<Slider {...settings1}>
|
||||||
|
{info_images.map((image, index) => (
|
||||||
|
<div key={index} className="px-4">
|
||||||
|
<div key={index} className="relative h-[50vw] transition-all justify-items-center mb-16">
|
||||||
|
<img
|
||||||
|
src={`http://localhost/${image.image}`}
|
||||||
|
alt={`Course Image ${index + 1}`}
|
||||||
|
className="w-full h-full object-cover object-center "
|
||||||
|
/>
|
||||||
|
{index !== currentSlide && (
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-white bg-opacity-50 transition-opacity duration-300 "
|
||||||
|
style={{
|
||||||
|
objectFit: 'cover',
|
||||||
|
objectPosition: 'center'
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Slider>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>)
|
||||||
|
:
|
||||||
|
(<div></div>)}
|
||||||
|
<div
|
||||||
|
className='px-12 py-14 [&_*]:!text-[1.5rem] [&_*]:!text-gray-600 [&_*]:!bg-transparent'
|
||||||
|
dangerouslySetInnerHTML={{ __html: children || '' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div></div>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Collapse
|
|
@ -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 (
|
||||||
|
<div className='bg-[#F6E8E8]'>
|
||||||
|
<Banner courseData={course} />
|
||||||
|
<LongDesc courseData={course} />
|
||||||
|
<CourseImagesSilder courseData={course} />
|
||||||
|
<Accordion courseData={course} />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Course
|
|
@ -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 (
|
||||||
|
<div
|
||||||
|
id='prev'
|
||||||
|
onClick={onClick}
|
||||||
|
className="group flex h-12 w-12 rounded-full bg-[#F2D6D5] justify-center items-center absolute top-1/2 -translate-x-1/2 -left-8 transform -translate-y-1/2 z-10 cursor-pointer hover:bg-[#D60050]"
|
||||||
|
|
||||||
|
>
|
||||||
|
<IoIosArrowBack className='text-black mr-1 group-hover:text-white transition-colors duration-200' size={30} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function SampleNextArrow(props: any) {
|
||||||
|
const { className, style, onClick } = props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id='next'
|
||||||
|
onClick={onClick}
|
||||||
|
className="group flex h-12 w-12 rounded-full bg-[#F2D6D5] justify-center items-center absolute top-1/2 -translate-x-1/2 -right-20 transform -translate-y-1/2 z-10 cursor-pointer hover:bg-[#D60050]"
|
||||||
|
>
|
||||||
|
<IoIosArrowForward className='text-black ml-1 group-hover:text-white transition-colors duration-200' size={30} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '24px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ul> {dots} </ul>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
dotsClass: 'dots_custom'
|
||||||
|
};
|
||||||
|
|
||||||
|
var settings2 = {
|
||||||
|
fade: true,
|
||||||
|
centerMode: true,
|
||||||
|
infinite: true,
|
||||||
|
dots: true,
|
||||||
|
speed: 500,
|
||||||
|
slidesToShow: 1,
|
||||||
|
slidesToScroll: 1,
|
||||||
|
nextArrow: <SampleNextArrow to="next" />,
|
||||||
|
prevArrow: <SamplePrevArrow to="prev" />,
|
||||||
|
beforeChange: (current: number, next: number) => setCurrentSlide(next),
|
||||||
|
appendDots: (dots: any) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '24px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ul> {dots} </ul>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
dotsClass: 'dots_custom'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className='w-full h-auto my-20'>
|
||||||
|
|
||||||
|
<div className='relative mx-[15vw] justify-between items-center hidden lg:flex'>
|
||||||
|
{courseData.images.length > 0 && (
|
||||||
|
<div className="w-full ">
|
||||||
|
<Slider className="w-full h-[40vw]" {...settings2}>
|
||||||
|
{courseData.images.map((image, index) => (
|
||||||
|
<div key={index} className="relative w-full h-[35vw] transition-all justify-items-center mb-28 ">
|
||||||
|
<img
|
||||||
|
src={`http://localhost/${image.image}`}
|
||||||
|
alt={`Course Image ${index + 1}`}
|
||||||
|
className="w-full h-full object-cover object-center rounded-xl "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Slider>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='relative flex justify-between items-center sm:block md:block lg:hidden xl:hidden 2xl:hidden'>
|
||||||
|
{courseData.images.length > 0 && (
|
||||||
|
<div className="w-full">
|
||||||
|
<Slider {...settings}>
|
||||||
|
{courseData.images.map((image, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className="px-4">
|
||||||
|
<div key={index} className="relative h-[38vw] transition-all justify-items-center mb-16">
|
||||||
|
<img
|
||||||
|
src={`http://localhost/${image.image}`}
|
||||||
|
alt={`Course Image ${index + 1}`}
|
||||||
|
className="w-full h-full object-cover object-center rounded-xl"
|
||||||
|
/>
|
||||||
|
{index !== currentSlide && (
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-white bg-opacity-50 transition-opacity duration-300 rounded-xl"
|
||||||
|
style={{
|
||||||
|
objectFit: 'cover',
|
||||||
|
objectPosition: 'center'
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Slider>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CourseImagesSilder
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { CoursesProps } from '@/types'
|
||||||
|
const LongDesc = ({ courseData }: { courseData: CoursesProps }) => {
|
||||||
|
return (
|
||||||
|
<div className='w-full h-auto lg:mt-64 mt-20'>
|
||||||
|
<div className='flex flex-col items-center justify-center mx-[5vw] lg:mx-[20vw]'>
|
||||||
|
<p className='text-5xl text-center'>
|
||||||
|
{courseData.title}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
className='mt-10 [&_*]:!text-[1.5rem] [&_*]:!text-gray-600 [&_*]:!text-center'
|
||||||
|
dangerouslySetInnerHTML={{ __html: courseData.long_description }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LongDesc
|
|
@ -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<CollapseProps> = ({ title, rerganizedSchedule }) => {
|
||||||
|
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [height, setHeight] = useState<number | undefined>(0)
|
||||||
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) setHeight(ref.current?.scrollHeight)
|
||||||
|
else setHeight(0)
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('mt-6 rounded-3xl ')}>
|
||||||
|
<button
|
||||||
|
className={`flex justify-between items-center w-full p-6 text-left ${isOpen ? "rounded-t-lg bg-[#D60050]" : "rounded-lg bg-[#F2D6D5] "}`}
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
>
|
||||||
|
<span className={`text-xl font-bold ml-3 ${isOpen ? "text-white " : " text-black"}`}>{title}</span>
|
||||||
|
{isOpen ? (
|
||||||
|
<VscChromeMinimize className='text-white mr-3' />
|
||||||
|
) : (
|
||||||
|
<VscAdd className='text-black mr-3' />
|
||||||
|
)}
|
||||||
|
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
style={{ height: height, }}
|
||||||
|
className="flex relative overflow-hidden transition-[height] duration-300 ease-in-out "
|
||||||
|
>
|
||||||
|
<div className='relative flex flex-col bg-[#FAF6F5] rounded-b-lg w-[65vw] max-sm:w-[90vw]'>
|
||||||
|
|
||||||
|
|
||||||
|
{rerganizedSchedule ? (
|
||||||
|
<div className='px-12 py-14'>
|
||||||
|
{rerganizedSchedule.schedules.map((schedule, index) => (
|
||||||
|
<div key={index} className='mb-4'>
|
||||||
|
<h3 className='text-xl font-bold'>
|
||||||
|
{ moment.utc(schedule.date).format("MM月DD日")}
|
||||||
|
</h3>
|
||||||
|
<p className='text-gray-600'>{schedule.title}</p>
|
||||||
|
<p className='text-gray-600'>{schedule.info1}</p>
|
||||||
|
<p className='text-gray-600'>{schedule.info2}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
export default ScheduleCollapse
|
|
@ -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;
|
||||||
|
}
|
|
@ -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) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '24px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ul> {dots} </ul>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
dotsClass: 'dots_custom'
|
||||||
|
};
|
||||||
|
console.log(courses)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{courses.length > 0 ? (
|
||||||
|
<div className=" bg-[#F6E8E9] items-center h-[100vh] w-full pt-30 transition-all duration-200">
|
||||||
|
<Slider className="w-full" {...settings}>
|
||||||
|
{courses.map((course, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className=" bg-[#F6E8E9] h-auto pt-30 transition-all justify-items-center duration-20">
|
||||||
|
<div className="hidden lg:grid lg:grid-cols-2 w-full mb-28">
|
||||||
|
<div className="flex col-span-1 h-auto justify-self-end mr-20">
|
||||||
|
<img
|
||||||
|
src={`http://localhost/${course.images[0].image}`}
|
||||||
|
alt="Course Image"
|
||||||
|
className="w-[50vh] h-[70vh] object-cover object-center rounded-3xl shadow-md"
|
||||||
|
style={{ aspectRatio: '1 / 1' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex col-span-1 h-auto justify-start items-center ml-20">
|
||||||
|
<div className="flex flex-col items-start w-[50vh]">
|
||||||
|
<p className="text-4xl">
|
||||||
|
{course.title}
|
||||||
|
</p>
|
||||||
|
<p className="text-base line-clamp-3 overflow-hidden mt-8">
|
||||||
|
{course.sort_description}
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className={`mt-16 middle none center rounded-full bg-[#D60050] h-12 w-28 text-base text-white shadow-md shadow-pink-500/20 transition-all hover:shadow-lg hover:shadow-pink-500/40`}
|
||||||
|
data-ripple-light="true"
|
||||||
|
>
|
||||||
|
{"了解更多"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className=" relative w-[50vh] h-[70vh] max-sm:w-[36vh] max-sm:h-[60vh] mt-20 mb-28 overflow-hidden rounded-3xl shadow-md sm:block md:block lg:hidden xl:hidden 2xl:hidden mx-auto">
|
||||||
|
<img
|
||||||
|
src={`http://localhost/${course.images[0].image}`}
|
||||||
|
alt="Course Image"
|
||||||
|
className="absolute inset-0 w-full h-full object-cover object-center "
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 flex flex-col items-center justify-center bg-black bg-opacity-50 text-white p-6">
|
||||||
|
<p className="text-2xl text-center mb-4">
|
||||||
|
{course.title}
|
||||||
|
</p>
|
||||||
|
<p className="text-base line-clamp-3 overflow-hidden text-center mb-8">
|
||||||
|
{course.sort_description}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className="rounded-full bg-white h-12 w-28 text-sm text-black shadow-md shadow-gray-400/20 transition-all hover:shadow-lg hover:shadow-gray-500/40"
|
||||||
|
data-ripple-light="true"
|
||||||
|
>
|
||||||
|
{"了解更多"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Slider>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-10" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CoursesSilder;
|
||||||
|
|
|
@ -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 (
|
||||||
|
<button
|
||||||
|
className={`middle none center rounded-full bg-[#D60050] ${text_and_button_size} font-bold uppercase text-white shadow-md shadow-pink-500/20 transition-all hover:shadow-lg hover:shadow-pink-500/40`}
|
||||||
|
data-ripple-light="true"
|
||||||
|
disabled={isDisabled}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomButton
|
|
@ -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 (
|
||||||
|
<footer className="rounded-lg py-8">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-between">
|
||||||
|
<Link className="mb-4 sm:mb-0 flex justify-center sm:justify-start"
|
||||||
|
href="/">
|
||||||
|
<img src="/images/logo.png" width={170} height={170} alt="logo" />
|
||||||
|
</Link>
|
||||||
|
<div className="text-center mb-4 sm:mb-0">
|
||||||
|
<span className="text-xs text-black">
|
||||||
|
Copyright @ 2024 <a href="https://flowbite.com/" className="hover:underline">All In One</a> All Rights Reserved.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center space-x-4 mt-7 lg:mt-0 md:mt-0">
|
||||||
|
<div className="bg-[#DCCECF] rounded-full h-8 w-8 flex items-center justify-center hover:bg-mainColor"
|
||||||
|
onClick={() => window.open(settings.facebook)}>
|
||||||
|
<FaFacebookF className="w-4 h-4 cursor-pointer text-[#F6E5E9]" />
|
||||||
|
</div>
|
||||||
|
<div className="bg-[#DCCECF] rounded-full h-8 w-8 flex items-center justify-center hover:bg-mainColor"
|
||||||
|
onClick={() => window.open(settings.instagram)}>
|
||||||
|
<RiInstagramFill className="w-4 h-4 cursor-pointer text-[#F6E5E9]" />
|
||||||
|
</div>
|
||||||
|
<div className="bg-[#DCCECF] rounded-full h-8 w-8 flex items-center justify-center hover:bg-mainColor"
|
||||||
|
onClick={() => window.open(settings.youtube)}>
|
||||||
|
<FaYoutube className="w-4 h-4 cursor-pointer text-[#F6E5E9]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Footer
|
|
@ -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<number[]>([]);
|
||||||
|
const scrollRef = useRef<HTMLDivElement | null>(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 (
|
||||||
|
<div className=" relative flex flex-col items-center bg-gradient-to-b from-[#F4D7D7] to-[#e2b2c4] max-sm:h-[94vh] max-lg:h-[170] md:h-[170vh] z-[10] w-full transition-all duration-200 " >
|
||||||
|
{/* Existing content */}
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<div className="flex flex-row items-center justify-center mt-[30vh]">
|
||||||
|
<div className='max-lg:text-6xl max-sm:text-3xl md:text-6xl'>
|
||||||
|
發掘你的
|
||||||
|
</div>
|
||||||
|
<div className=" bg-[url('/images/kid.png')] max-lg:h-[60px] max-lg:w-[140px] max-sm:h-[40px] max-sm:w-[95px] md:h-[60px] md:w-[140px] bg-cover bg-no-repeat rounded-full " />
|
||||||
|
<div className='max-lg:text-6xl max-sm:text-3xl md:text-6xl'>
|
||||||
|
音樂之路
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="max-lg: w-[61vh] h-[15vh] max-sm:w-[40vh] mt-[5vh] items-center">
|
||||||
|
<div className="flex-grow h-[10vh] overflow-y-auto items-center no-scrollbar" snap-mandatory snap-y ref={scrollRef}>
|
||||||
|
{[
|
||||||
|
"學音樂能陶冶性情,專注,舒緩壓力,放鬆心情。實際上是否做到呢?",
|
||||||
|
"我們認為音樂不單單是「睇五線譜」",
|
||||||
|
"我們可以透過不同方式",
|
||||||
|
"例如聆聽,唱歌,身體律動等學習音樂,令學習不再是沉悶艱難",
|
||||||
|
"真正做到「陶冶性情」",
|
||||||
|
"One & ALL Music 提供全面的音樂課程",
|
||||||
|
"我們相信每位學生都各有天賦",
|
||||||
|
"「誰都可發光 只要找對地方」",
|
||||||
|
"只要運用合適的教學法,同學必定能發揮所長",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
].map((text, index) => (
|
||||||
|
<p
|
||||||
|
key={index}
|
||||||
|
className={`text-black max-lg:text-2xl max-sm:text-lg md:text-2xl text-center ${paragraphOpacities[index] === 0 ? 'opacity-25' : ''}`}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-[7vh] max-sm:hidden max-lg:block md:block">
|
||||||
|
<div className="flex flex-row items-center justify-center mt-[30vh] ">
|
||||||
|
<div className='max-lg:text-6xl max-sm:text-4xl md:text-6xl'>
|
||||||
|
我們認為
|
||||||
|
</div>
|
||||||
|
<div className=" bg-[url('/images/piano2.png')] max-lg:h-[60px] max-lg:w-[140px] max-sm:h-[40px] max-sm:w-[95px] md:h-[60px] md:w-[140px] bg-cover bg-no-repeat rounded-full " />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-[61vh] h-[19vh] mt-[5vh] items-center max-sm:hidden max-lg:block md:block">
|
||||||
|
<p
|
||||||
|
className={`text-black text-2xl text-center `}
|
||||||
|
>
|
||||||
|
{"音樂不單單是「睇五線譜」,我們可以透過不同方式,例如聆聽唱歌,身體律動等學習音樂,令學習不再是沉悶艱難,真正做到「陶治性情」。"}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className={`text-black text-2xl text-center mt-[5vh]`}
|
||||||
|
>
|
||||||
|
One & ALL Music 提供全面的音樂課程,我們相信每位學生都各有天赋,<br></br>「誰都可發光 只要找對地方」,只要運用合適的教學法,同學必定能發揮所長。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="max-lg:mt-[20vh] md:mt-[20vh] max-sm:mt-[5vh]">
|
||||||
|
<CustomButton text_and_button_size={" max-lg:px-14 max-lg:py-5 max-lg:text-md max-sm:px-8 max-sm:py-3 max-sm:text-sm md:px-14 md:py-5 md:text-md"} title={'關於我們更多'} handleClick={() => {
|
||||||
|
console.log('Button clicked');
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute bottom-0 bg-[url('/images/pianobackground2.png')] max-lg:h-[30vh] max-sm:h-[15vh] md:h-[30vh] w-full bg-cover bg-center bg-no-repeat" style={{ opacity: 0.7 }}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default Hero1
|
|
@ -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 (
|
||||||
|
<div className="relative flex flex-col items-center bg-[#F6E8E9] h-auto z-[10] w-full pt-30 transition-all duration-200">
|
||||||
|
<div className="flex w-full h-auto justify-center items-center relative">
|
||||||
|
<div className="bg-[url('/images/014.png')] absolute bottom-0 inset-0 h-auto w-full bg-contain bg-bottom bg-no-repeat"></div>
|
||||||
|
<iframe
|
||||||
|
className="rounded-2xl w-[100vh] aspect-video justify-center relative z-10 lg:m-[15vh] max-sm:m-[5vh] md:m-[10vh]"
|
||||||
|
src={convertToEmbedLink(settings.youtube_link)}
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowFullScreen
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Hero2
|
|
@ -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<CoursesProps[]>([]);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className='bg-[#F2D5D5]'>
|
||||||
|
{loading ? (
|
||||||
|
<div className="flex justify-center items-center h-screen">
|
||||||
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<Loading />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<Hero1 />
|
||||||
|
<Hero2 settings={settings} />
|
||||||
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<CoursesSilder courses={courses} />
|
||||||
|
</Suspense>
|
||||||
|
<ContactForm startLoading={startLoading} stopLoading={stopLoading} />
|
||||||
|
<Whatsapp settings={settings} />
|
||||||
|
<Footer settings={settings} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Home
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const Loading = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center min-h-screen p-5 min-w-screen">
|
||||||
|
|
||||||
|
<div className="flex space-x-2 animate-pulse">
|
||||||
|
<div className="w-3 h-3 bg-[#D60050] rounded-full"></div>
|
||||||
|
<div className="w-3 h-3 bg-[#D60050] rounded-full"></div>
|
||||||
|
<div className="w-3 h-3 bg-[#D60050] rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Loading
|
|
@ -0,0 +1,85 @@
|
||||||
|
"use client"
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { FiChevronDown, FiChevronUp } from 'react-icons/fi';
|
||||||
|
import { CoursesProps } from '@/types'
|
||||||
|
//define props type
|
||||||
|
type Props = {
|
||||||
|
showNav: boolean;
|
||||||
|
closeNav: () => void
|
||||||
|
courses: CoursesProps[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const MobileNav = ({ closeNav, showNav, courses }: Props) => {
|
||||||
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (showNav) {
|
||||||
|
document.body.classList.add('overflow-hidden');
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('overflow-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.body.classList.remove('overflow-hidden');
|
||||||
|
};
|
||||||
|
}, [showNav]);
|
||||||
|
|
||||||
|
const navOpen = showNav ? 'translate-x-0' : 'translate-x-[-100%]'
|
||||||
|
const navClose = !showNav ? 'translate-x-[-100%]' : 'translate-x-0'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/*overlay*/}
|
||||||
|
<div className={`fixed ${navOpen} ${navClose} transform transition-all duration-500 top-[7vh] inset-x-0 bottom-0 z-[1000]`}>
|
||||||
|
|
||||||
|
{/* nav links*/}
|
||||||
|
<div className={`text-white ${navOpen} fixed flex flex-col h-full w-[80%] sm:w-[60%] bg-[#F9E7E9] z-[10000] `}>
|
||||||
|
|
||||||
|
<div className='w-full h-16 flex justify-start items-center border-b-[1.5px] border-[#F5DADF]'>
|
||||||
|
<Link key={"01"} href={"#"} >
|
||||||
|
<p className="text-xl ml-6 text-black">
|
||||||
|
主頁
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className={`w-full flex flex-col ${dropdownOpen ? "border-b-[0px]" : "border-b-[1.5px]"} border-[#F5DADF]`}>
|
||||||
|
<div
|
||||||
|
className='h-16 flex justify-between items-center px-6 cursor-pointer'
|
||||||
|
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||||
|
>
|
||||||
|
<p className="text-xl text-black">課程</p>
|
||||||
|
{dropdownOpen ? <FiChevronUp className='text-black' size={30}/> : <FiChevronDown className='text-black' size={30}/>}
|
||||||
|
</div>
|
||||||
|
{dropdownOpen && (
|
||||||
|
<div>
|
||||||
|
{courses?.map((course) => (
|
||||||
|
<div className='bg-[#F5DADF] w-full h-16 flex justify-start items-center border-b-[1.5px] border-[#F9E7E9]'>
|
||||||
|
<Link key={course.id} href={`/courses/${course.id}`}>
|
||||||
|
<p className="text-lg text-black ml-8 ">
|
||||||
|
{course.title}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='w-full h-16 flex justify-start items-center border-b-[1.5px] border-[#F5DADF]'>
|
||||||
|
<Link key={"03"} href={"#"} >
|
||||||
|
<p className="text-xl ml-6 text-black">
|
||||||
|
關於我們
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MobileNav
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
"use client"
|
||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { FiChevronDown } from "react-icons/fi";
|
||||||
|
import { HiBars3BottomRight } from 'react-icons/hi2'
|
||||||
|
import { FaFacebookF, FaYoutube } from "react-icons/fa";
|
||||||
|
import { RiInstagramFill } from "react-icons/ri";
|
||||||
|
import { IoMenu, IoClose } from "react-icons/io5";
|
||||||
|
import { CoursesProps, SettingsProps } from '@/types'
|
||||||
|
import { colors } from '@/public/themes'
|
||||||
|
//define props type
|
||||||
|
type Props = {
|
||||||
|
openNav: () => void
|
||||||
|
showNav: boolean
|
||||||
|
courses: CoursesProps[]
|
||||||
|
settings: SettingsProps
|
||||||
|
}
|
||||||
|
const Nav = ({ openNav, courses, showNav, settings }: Props) => {
|
||||||
|
const [navBg, setNavBg] = useState(false)
|
||||||
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = () => {
|
||||||
|
if (window.scrollY >= 90) {
|
||||||
|
setNavBg(true)
|
||||||
|
} else if (window.scrollY <= 90) {
|
||||||
|
setNavBg(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("scroll", handler)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("scroll", handler)
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`bg-[#F6E5E9] max-sm:bg-[#FFF9F9] h-[7vh] z-[10] w-full transition-all duration-200 `}>
|
||||||
|
<div className="flex items-center justify-between w-[80%] sm:w-[80%] x1:w-[80%] mx-auto">
|
||||||
|
<div className='flex items-center space-x-10'>
|
||||||
|
<Link href={"/"}>
|
||||||
|
<Image
|
||||||
|
|
||||||
|
src="/images/logo.png"
|
||||||
|
alt='logo'
|
||||||
|
width={170}
|
||||||
|
height={170}
|
||||||
|
className="m1-[-1.5rem] sm:m1-0 mr-4"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<div className="flex items-center space-x-15">
|
||||||
|
<div className='hidden lg:flex items-center space-x-8'>
|
||||||
|
<Link key={"01"} href={"/"} >
|
||||||
|
<p className="nav__link">
|
||||||
|
{"主頁"}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
<div key={"02"} className="relative">
|
||||||
|
<button
|
||||||
|
className="nav__link flex items-center"
|
||||||
|
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||||
|
>
|
||||||
|
{"課程"}
|
||||||
|
<FiChevronDown className='text-gray-400' />
|
||||||
|
</button>
|
||||||
|
{dropdownOpen && (
|
||||||
|
<div className="absolute left-0 top-full mt-1 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50">
|
||||||
|
<div className="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
|
||||||
|
{courses.map((course) => (
|
||||||
|
<a
|
||||||
|
key={course.id}
|
||||||
|
href={`/courses/${course.id}`}
|
||||||
|
className={`block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-mainColor`}
|
||||||
|
role="menuitem"
|
||||||
|
>
|
||||||
|
{course.title}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Link key={"03"} href={"#"} >
|
||||||
|
<p className="nav__link">
|
||||||
|
{"關於我們"}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/*button*/}
|
||||||
|
<div className="flex items-center space-x-5 ">
|
||||||
|
<div className="flex items-center space-x-2 max-sm:hidden">
|
||||||
|
<div className="bg-[#DCCECF] rounded-full h-8 w-8 flex items-center justify-center hover:bg-mainColor"
|
||||||
|
onClick={() => window.open(settings.facebook)} >
|
||||||
|
<FaFacebookF
|
||||||
|
className="w-4 h-4 cursor-pointer text-[#F6E5E9]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="bg-[#DCCECF] rounded-full h-8 w-8 flex items-center justify-center hover:bg-mainColor"
|
||||||
|
onClick={() => window.open(settings.instagram)} >
|
||||||
|
<RiInstagramFill
|
||||||
|
className="w-4 h-4 cursor-pointer text-[#F6E5E9]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-[#DCCECF] rounded-full h-8 w-8 flex items-center justify-center hover:bg-mainColor"
|
||||||
|
onClick={() => window.open(settings.youtube)} >
|
||||||
|
<FaYoutube
|
||||||
|
className="w-4 h-4 cursor-pointer text-[#F6E5E9]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/*burger*/}
|
||||||
|
{showNav ? (
|
||||||
|
<IoClose
|
||||||
|
onClick={openNav}
|
||||||
|
className="w-8 h-8 cursor-pointer text-mainColor lg:hidden ml-2"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IoMenu
|
||||||
|
id='menubutton'
|
||||||
|
onClick={openNav}
|
||||||
|
className="w-8 h-8 cursor-pointer text-mainColor lg:hidden ml-2"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Nav
|
|
@ -0,0 +1,20 @@
|
||||||
|
"use client"
|
||||||
|
import React, {useState} from 'react'
|
||||||
|
import MobileNav from './MobileNav'
|
||||||
|
import Nav from './Nav'
|
||||||
|
import { CoursesProps,SettingsProps } from '@/types'
|
||||||
|
|
||||||
|
const ResponsiveNav = ({ courses, settings }: { courses: CoursesProps[], settings: SettingsProps }) => {
|
||||||
|
const [showNav, setShowNav] = useState(false)
|
||||||
|
const toggleNavHandler = () => setShowNav(!showNav)
|
||||||
|
const showNavHandler = () => setShowNav(true)
|
||||||
|
const closeNavHandler = () => setShowNav(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Nav openNav={toggleNavHandler} showNav={showNav} courses={courses} settings={settings} />
|
||||||
|
<MobileNav showNav={showNav} closeNav={closeNavHandler} courses={courses} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ResponsiveNav
|
|
@ -0,0 +1,143 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { createContext, useContext, useRef } from "react";
|
||||||
|
import { useScroll, useTransform, motion, MotionValue } from "framer-motion";
|
||||||
|
import { cn } from "./utils";
|
||||||
|
|
||||||
|
type TextOpacityEnum = "none" | "soft" | "medium";
|
||||||
|
type ViewTypeEnum = "word" | "letter";
|
||||||
|
|
||||||
|
type TextGradientScrollType = {
|
||||||
|
text: string;
|
||||||
|
type?: ViewTypeEnum;
|
||||||
|
className?: string;
|
||||||
|
textOpacity?: TextOpacityEnum;
|
||||||
|
};
|
||||||
|
|
||||||
|
type LetterType = {
|
||||||
|
children: React.ReactNode | string;
|
||||||
|
progress: MotionValue<number>;
|
||||||
|
range: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type WordType = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
progress: MotionValue<number>;
|
||||||
|
range: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type CharType = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
progress: MotionValue<number>;
|
||||||
|
range: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type TextGradientScrollContextType = {
|
||||||
|
textOpacity?: TextOpacityEnum;
|
||||||
|
type?: ViewTypeEnum;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TextGradientScrollContext = createContext<TextGradientScrollContextType>(
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
function useGradientScroll() {
|
||||||
|
const context = useContext(TextGradientScrollContext);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TextGradientScroll({
|
||||||
|
text,
|
||||||
|
className,
|
||||||
|
type = "letter",
|
||||||
|
textOpacity = "soft",
|
||||||
|
}: TextGradientScrollType) {
|
||||||
|
const ref = useRef<HTMLParagraphElement>(null);
|
||||||
|
const { scrollYProgress } = useScroll({
|
||||||
|
target: ref,
|
||||||
|
offset: ["start center", "end center"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const words = text.split(" ");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextGradientScrollContext.Provider value={{ textOpacity, type }}>
|
||||||
|
<p ref={ref} className={cn("relative flex m-0 flex-wrap", className)}>
|
||||||
|
{words.map((word, i) => {
|
||||||
|
const start = i / words.length;
|
||||||
|
const end = start + 1 / words.length;
|
||||||
|
return type === "word" ? (
|
||||||
|
<Word key={i} progress={scrollYProgress} range={[start, end]}>
|
||||||
|
{word}
|
||||||
|
</Word>
|
||||||
|
) : (
|
||||||
|
<Letter key={i} progress={scrollYProgress} range={[start, end]}>
|
||||||
|
{word}
|
||||||
|
</Letter>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</TextGradientScrollContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Word = ({ children, progress, range }: WordType) => {
|
||||||
|
const opacity = useTransform(progress, range, [0, 1]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="relative me-2 mt-2">
|
||||||
|
<span style={{ position: "absolute", opacity: 0.1 }}>{children}</span>
|
||||||
|
<motion.span style={{ transition: "all .5s", opacity: opacity }}>
|
||||||
|
{children}
|
||||||
|
</motion.span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Letter = ({ children, progress, range }: LetterType) => {
|
||||||
|
if (typeof children === "string") {
|
||||||
|
const amount = range[1] - range[0];
|
||||||
|
const step = amount / children.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="relative me-2 mt-2">
|
||||||
|
{children.split("").map((char: string, i: number) => {
|
||||||
|
const start = range[0] + i * step;
|
||||||
|
const end = range[0] + (i + 1) * step;
|
||||||
|
return (
|
||||||
|
<Char key={`c_${i}`} progress={progress} range={[start, end]}>
|
||||||
|
{char}
|
||||||
|
</Char>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Char = ({ children, progress, range }: CharType) => {
|
||||||
|
const opacity = useTransform(progress, range, [0, 1]);
|
||||||
|
const { textOpacity } = useGradientScroll();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
className={cn("absolute", {
|
||||||
|
"opacity-0": textOpacity == "none",
|
||||||
|
"opacity-10": textOpacity == "soft",
|
||||||
|
"opacity-30": textOpacity == "medium",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
<motion.span
|
||||||
|
style={{
|
||||||
|
transition: "all .5s",
|
||||||
|
opacity: opacity,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { IoLogoWhatsapp } from "react-icons/io";
|
||||||
|
import { SettingsProps } from '@/types';
|
||||||
|
const Whatsapp = ({ settings }: { settings: SettingsProps }) => {
|
||||||
|
return (
|
||||||
|
<div className=" relative">
|
||||||
|
<button className=" z-20 text-white flex flex-col shrink-0 grow-0 justify-around
|
||||||
|
fixed bottom-0 right-0 right-5 rounded-lg
|
||||||
|
mr-1 mb-5 lg:mr-5 lg:mb-5 xl:mr-10 xl:mb-10"
|
||||||
|
onClick={()=>window.open(`https://wa.me/${settings.whatsapp}`)}>
|
||||||
|
<IoLogoWhatsapp size={50} className='text-[#D60050]'/>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Whatsapp
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { ClassValue, clsx } from 'clsx';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
|
@ -8,14 +8,34 @@
|
||||||
"name": "webfrontend",
|
"name": "webfrontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"animate.css": "^4.1.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"embla-carousel-react": "^8.3.0",
|
||||||
|
"framer-motion": "^11.7.0",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
|
"keen-slider": "^6.8.6",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"next": "14.2.13",
|
"next": "14.2.13",
|
||||||
|
"nextjs-cors": "^2.2.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18"
|
"react-animate-on-scroll": "^2.1.9",
|
||||||
|
"react-dom": "^18",
|
||||||
|
"react-hook-form": "^7.53.0",
|
||||||
|
"react-hot-toast": "^2.4.1",
|
||||||
|
"react-icons": "^5.3.0",
|
||||||
|
"react-loader-spinner": "^6.1.6",
|
||||||
|
"react-scroll-motion": "^0.3.3",
|
||||||
|
"react-slick": "^0.30.2",
|
||||||
|
"slick-carousel": "^1.8.1",
|
||||||
|
"tailwind-merge": "^2.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
|
"@types/react-animate-on-scroll": "^2.1.8",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@types/react-slick": "^0.23.13",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.2.13",
|
"eslint-config-next": "14.2.13",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
|
@ -36,6 +56,27 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emotion/is-prop-valid": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/memoize": "^0.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/memoize": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/unitless": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
||||||
|
@ -518,6 +559,16 @@
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-animate-on-scroll": {
|
||||||
|
"version": "2.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-animate-on-scroll/-/react-animate-on-scroll-2.1.8.tgz",
|
||||||
|
"integrity": "sha512-Lyd1hb1aY9T0bOUL3VE7bKuOlAv2nBziJg5piDLqW+jxzy5jCa/nIftsOpYxZ0+Sdo0wFXuI6tpLo6B0Q288IQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react-dom": {
|
"node_modules/@types/react-dom": {
|
||||||
"version": "18.3.0",
|
"version": "18.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
|
||||||
|
@ -528,6 +579,22 @@
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-slick": {
|
||||||
|
"version": "0.23.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-slick/-/react-slick-0.23.13.tgz",
|
||||||
|
"integrity": "sha512-bNZfDhe/L8t5OQzIyhrRhBr/61pfBcWaYJoq6UDqFtv5LMwfg4NsVDD2J8N01JqdAdxLjOt66OZEp6PX+dGs/A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/stylis": {
|
||||||
|
"version": "4.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
|
||||||
|
"integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.7.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
|
||||||
|
@ -791,6 +858,12 @@
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/animate.css": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
@ -1158,6 +1231,15 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/camelize": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001663",
|
"version": "1.0.30001663",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz",
|
||||||
|
@ -1233,12 +1315,27 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/classnames": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/client-only": {
|
"node_modules/client-only": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
@ -1276,6 +1373,19 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cors": {
|
||||||
|
"version": "2.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||||
|
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"object-assign": "^4",
|
||||||
|
"vary": "^1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
@ -1291,6 +1401,26 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-color-keywords": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-to-react-native": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"camelize": "^1.0.0",
|
||||||
|
"css-color-keywords": "^1.0.0",
|
||||||
|
"postcss-value-parser": "^4.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
|
@ -1308,7 +1438,6 @@
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
|
@ -1493,6 +1622,18 @@
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||||
|
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
|
@ -1500,6 +1641,34 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/embla-carousel": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-Ve8dhI4w28qBqR8J+aMtv7rLK89r1ZA5HocwFz6uMB/i5EiC7bGI7y+AM80yAVUJw3qqaZYK7clmZMUR8kM3UA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/embla-carousel-react": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-P1FlinFDcIvggcErRjNuVqnUR8anyo8vLMIH8Rthgofw7Nj8qTguCa2QjFAbzxAUTQTPNNjNL7yt0BGGinVdFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"embla-carousel": "8.3.0",
|
||||||
|
"embla-carousel-reactive-utils": "8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.1 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/embla-carousel-reactive-utils": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-EYdhhJ302SC4Lmkx8GRsp0sjUhEN4WyFXPOk0kGu9OXZSRMmcBlRgTvHcq8eKJE1bXWBsOi1T83B+BSSVZSmwQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"embla-carousel": "8.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "9.2.2",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||||
|
@ -1521,6 +1690,12 @@
|
||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/enquire.js": {
|
||||||
|
"version": "2.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz",
|
||||||
|
"integrity": "sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/es-abstract": {
|
"node_modules/es-abstract": {
|
||||||
"version": "1.23.3",
|
"version": "1.23.3",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
|
||||||
|
@ -2322,6 +2497,31 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "11.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.7.0.tgz",
|
||||||
|
"integrity": "sha512-m+1E3mMzDIQ5DsVghMvXyC+jSkZSm5RHBLA2gHa/LczcXwW6JbQK4Uz48LsuCTGV8bZFVUezcauHj3M33tY/5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
@ -2529,6 +2729,15 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/goober": {
|
||||||
|
"version": "2.1.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz",
|
||||||
|
"integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"csstype": "^3.0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||||
|
@ -2555,6 +2764,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/gsap": {
|
||||||
|
"version": "3.12.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz",
|
||||||
|
"integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ==",
|
||||||
|
"license": "Standard 'no charge' license: https://gsap.com/standard-license. Club GSAP members get more: https://gsap.com/licensing/. Why GreenSock doesn't employ an MIT license: https://gsap.com/why-license/"
|
||||||
|
},
|
||||||
"node_modules/has-bigints": {
|
"node_modules/has-bigints": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
|
||||||
|
@ -3196,6 +3411,13 @@
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jquery": {
|
||||||
|
"version": "3.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
||||||
|
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
@ -3236,6 +3458,15 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/json2mq": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"string-convert": "^0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/json5": {
|
"node_modules/json5": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
|
@ -3265,6 +3496,12 @@
|
||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/keen-slider": {
|
||||||
|
"version": "6.8.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/keen-slider/-/keen-slider-6.8.6.tgz",
|
||||||
|
"integrity": "sha512-dcEQ7GDBpCjUQA8XZeWh3oBBLLmyn8aoeIQFGL/NTVkoEOsmlnXqA4QykUm/SncolAZYGsEk/PfUhLZ7mwMM2w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
|
@ -3342,6 +3579,12 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.debounce": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
|
@ -3349,6 +3592,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.throttle": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/loose-envify": {
|
"node_modules/loose-envify": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
@ -3425,6 +3674,15 @@
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||||
|
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
@ -3547,6 +3805,18 @@
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nextjs-cors": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nextjs-cors/-/nextjs-cors-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-FZu/A+L59J4POJNqwXYyCPDvsLDeu5HjSBvytzS6lsrJeDz5cmnH45zV+VoNic0hjaeER9xGaiIjZIWzEHnxQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": "^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
|
@ -3561,7 +3831,6 @@
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
@ -4039,7 +4308,6 @@
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
|
@ -4056,7 +4324,6 @@
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
|
@ -4107,6 +4374,20 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-animate-on-scroll": {
|
||||||
|
"version": "2.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-animate-on-scroll/-/react-animate-on-scroll-2.1.9.tgz",
|
||||||
|
"integrity": "sha512-E4PZLX6RDLLn+/iIMhnQrC1xU74ixGcCQ5/TBX8fBsaO+SnaU9VFoZLvIfUqVf3mH5HUNzO8wAqA11niot5Obw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.throttle": "^4.1.1",
|
||||||
|
"prop-types": "^15.5.9"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"classnames": "^2.2.5",
|
||||||
|
"react": ">= 15.4.1 < 19.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
|
@ -4120,13 +4401,103 @@
|
||||||
"react": "^18.3.1"
|
"react": "^18.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-hook-form": {
|
||||||
|
"version": "7.53.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.0.tgz",
|
||||||
|
"integrity": "sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/react-hook-form"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-hot-toast": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"goober": "^2.1.10"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16",
|
||||||
|
"react-dom": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-icons": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/react-loader-spinner": {
|
||||||
|
"version": "6.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-6.1.6.tgz",
|
||||||
|
"integrity": "sha512-x5h1Jcit7Qn03MuKlrWcMG9o12cp9SNDVHVJTNRi9TgtGPKcjKiXkou4NRfLAtXaFB3+Z8yZsVzONmPzhv2ErA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": "^18.2.0",
|
||||||
|
"styled-components": "^6.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-loader-spinner/node_modules/react-is": {
|
||||||
|
"version": "18.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
|
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/react-scroll-motion": {
|
||||||
|
"version": "0.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-scroll-motion/-/react-scroll-motion-0.3.3.tgz",
|
||||||
|
"integrity": "sha512-Q33prpcdWDfQaHVmED7bo/EReUEBHfL6mOfvMGFP3FhDxTKddIoocMcd3bT9qw2KQ/fqNdJNADf7aiHWSLkXuQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.11",
|
||||||
|
"react-dom": "^18.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-slick": {
|
||||||
|
"version": "0.30.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.30.2.tgz",
|
||||||
|
"integrity": "sha512-XvQJi7mRHuiU3b9irsqS9SGIgftIfdV5/tNcURTb5LdIokRA5kIIx3l4rlq2XYHfxcSntXapoRg/GxaVOM1yfg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"classnames": "^2.2.5",
|
||||||
|
"enquire.js": "^2.1.6",
|
||||||
|
"json2mq": "^0.2.0",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"resize-observer-polyfill": "^1.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
@ -4191,6 +4562,12 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/resize-observer-polyfill": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
|
@ -4396,6 +4773,12 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/shallowequal": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
@ -4451,6 +4834,15 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/slick-carousel": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/slick-carousel/-/slick-carousel-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"jquery": ">=1.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
@ -4481,6 +4873,12 @@
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string-convert": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||||
|
@ -4702,6 +5100,68 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/styled-components": {
|
||||||
|
"version": "6.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz",
|
||||||
|
"integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/is-prop-valid": "1.2.2",
|
||||||
|
"@emotion/unitless": "0.8.1",
|
||||||
|
"@types/stylis": "4.2.5",
|
||||||
|
"css-to-react-native": "3.2.0",
|
||||||
|
"csstype": "3.1.3",
|
||||||
|
"postcss": "8.4.38",
|
||||||
|
"shallowequal": "1.1.0",
|
||||||
|
"stylis": "4.3.2",
|
||||||
|
"tslib": "2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/styled-components"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8.0",
|
||||||
|
"react-dom": ">= 16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/styled-components/node_modules/postcss": {
|
||||||
|
"version": "8.4.38",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||||
|
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"nanoid": "^3.3.7",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"source-map-js": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/styled-components/node_modules/tslib": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/styled-jsx": {
|
"node_modules/styled-jsx": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
|
||||||
|
@ -4725,6 +5185,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stylis": {
|
||||||
|
"version": "4.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
|
||||||
|
"integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/sucrase": {
|
"node_modules/sucrase": {
|
||||||
"version": "3.35.0",
|
"version": "3.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||||
|
@ -4774,6 +5240,16 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tailwind-merge": {
|
||||||
|
"version": "2.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz",
|
||||||
|
"integrity": "sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/dcastil"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.13",
|
"version": "3.4.13",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",
|
||||||
|
@ -5061,6 +5537,15 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/vary": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|
28
package.json
|
@ -9,18 +9,38 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"animate.css": "^4.1.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"embla-carousel-react": "^8.3.0",
|
||||||
|
"framer-motion": "^11.7.0",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
|
"keen-slider": "^6.8.6",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"next": "14.2.13",
|
||||||
|
"nextjs-cors": "^2.2.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
"react-animate-on-scroll": "^2.1.9",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"next": "14.2.13"
|
"react-hook-form": "^7.53.0",
|
||||||
|
"react-hot-toast": "^2.4.1",
|
||||||
|
"react-icons": "^5.3.0",
|
||||||
|
"react-loader-spinner": "^6.1.6",
|
||||||
|
"react-scroll-motion": "^0.3.3",
|
||||||
|
"react-slick": "^0.30.2",
|
||||||
|
"slick-carousel": "^1.8.1",
|
||||||
|
"tailwind-merge": "^2.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
|
"@types/react-animate-on-scroll": "^2.1.8",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@types/react-slick": "^0.23.13",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.2.13",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"eslint": "^8",
|
"typescript": "^5"
|
||||||
"eslint-config-next": "14.2.13"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 534 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 648 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 794 KiB |
|
@ -0,0 +1,7 @@
|
||||||
|
export const colors = {
|
||||||
|
mainColor: "#D60050"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
colors
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Config } from "tailwindcss";
|
import type { Config, } from "tailwindcss";
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
content: [
|
content: [
|
||||||
|
@ -9,6 +9,7 @@ const config: Config = {
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
|
mainColor: "#D60050",
|
||||||
background: "var(--background)",
|
background: "var(--background)",
|
||||||
foreground: "var(--foreground)",
|
foreground: "var(--foreground)",
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { MouseEventHandler } from "react";
|
||||||
|
|
||||||
|
export interface SettingsProps {
|
||||||
|
|
||||||
|
address: string;
|
||||||
|
google_map_api_key: string;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
phone: string;
|
||||||
|
email: string;
|
||||||
|
facebook: string;
|
||||||
|
instagram: string;
|
||||||
|
youtube: string;
|
||||||
|
youtube_link: string;
|
||||||
|
whatsapp: string;
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageProps {
|
||||||
|
name: string,
|
||||||
|
phone: string,
|
||||||
|
email: string,
|
||||||
|
message: string,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomButtonProps {
|
||||||
|
isDisabled?: boolean;
|
||||||
|
// btnType?: "button" | "submit";
|
||||||
|
//containerStyles?: string;
|
||||||
|
//textStyles?: string;
|
||||||
|
title: string;
|
||||||
|
text_and_button_size: string;
|
||||||
|
//rightIcon?: string;
|
||||||
|
handleClick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface imageProps {
|
||||||
|
image: string,
|
||||||
|
course_id: string,
|
||||||
|
index: number,
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoursesProps {
|
||||||
|
id: string,
|
||||||
|
title: string,
|
||||||
|
images: Array<imageProps>,
|
||||||
|
info_images: Array<imageProps>,
|
||||||
|
schedule: Array<ScheduleProps>,
|
||||||
|
sort_description: string,
|
||||||
|
long_description: string,
|
||||||
|
information: string,
|
||||||
|
contant: string,
|
||||||
|
remark: string,
|
||||||
|
created_at: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScheduleProps {
|
||||||
|
id: string,
|
||||||
|
course_id: string,
|
||||||
|
title: string,
|
||||||
|
info1: string,
|
||||||
|
info2: string,
|
||||||
|
date: string,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RerganizedScheduleProps {
|
||||||
|
yearAndMonth: string,
|
||||||
|
schedules: Array<ScheduleProps>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoursesArrayProps {
|
||||||
|
courses: Array<CoursesProps>
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
|
||||||
|
import { CoursesProps, CoursesArrayProps, SettingsProps, MessageProps } from "@/types";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export async function postMessage(data: MessageProps) {
|
||||||
|
const headers: HeadersInit = {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = JSON.stringify(data);
|
||||||
|
const url = `http://localhost/api/v1/messages/`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get("content-type");
|
||||||
|
if (contentType && contentType.indexOf("application/json") !== -1) {
|
||||||
|
return { success: true }
|
||||||
|
} else {
|
||||||
|
const text = await response.text();
|
||||||
|
throw new Error(`Expected JSON, got ${contentType}: ${text}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in postMessage:", error);
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function fetchCourses() {
|
||||||
|
const headers: HeadersInit = {
|
||||||
|
accept: "application/json"
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = `${process.env.api_url}course/?skip=0&limit=100`;
|
||||||
|
console.log('Fetching from URL:', url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { headers });
|
||||||
|
console.log('Response status:', response.status);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
console.log('Fetched data:', result);
|
||||||
|
|
||||||
|
const coursesArray: CoursesProps[] = result.data.map((course: CoursesProps) => ({
|
||||||
|
...course,
|
||||||
|
images: course.images.sort((a: any, b: any) => a.index - b.index),
|
||||||
|
info_images: course?.info_images?.sort((a: any, b: any) => a.index - b.index),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return coursesArray;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fetch error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchCourse(id: string) {
|
||||||
|
const headers: HeadersInit = {
|
||||||
|
accept: "application/json"
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = `${process.env.api_url}course/${id}`;
|
||||||
|
console.log('Fetching from URL:', url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { headers });
|
||||||
|
console.log('Response status:', response.status);
|
||||||
|
|
||||||
|
var result: CoursesProps = await response.json();
|
||||||
|
console.log('Fetched data:', result);
|
||||||
|
|
||||||
|
if (result.info_images && Array.isArray(result.info_images)) {
|
||||||
|
result.info_images.sort((a, b) => a.index - b.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fetch error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchSettings() {
|
||||||
|
const headers: HeadersInit = {
|
||||||
|
accept: "application/json"
|
||||||
|
};
|
||||||
|
const url = `${process.env.api_url}setting`;
|
||||||
|
console.log('Fetching from URL:', url);
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { headers });
|
||||||
|
console.log('Response status:', response.status);
|
||||||
|
const result: SettingsProps = await response.json();
|
||||||
|
console.log('Fetched data:', result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Fetch error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|