feat: add Learning Resources section with dynamic data fetching and UI integration
This commit is contained in:
@@ -1,65 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
import { Icon } from '@iconify/react';
|
||||||
BookOpenIcon,
|
import { Button } from '@imphnen-frontend-service/shadcn-ui/atoms';
|
||||||
Button,
|
import { LearningResourcesSection } from 'apps/landing/src/payload-types';
|
||||||
CodeIcon,
|
|
||||||
Share2Icon,
|
|
||||||
VideoIcon,
|
|
||||||
} from '@imphnen-frontend-service/shadcn-ui/atoms';
|
|
||||||
import { motion, useInView } from 'framer-motion';
|
import { motion, useInView } from 'framer-motion';
|
||||||
|
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
export function LearningResources() {
|
export function LearningResources(props: LearningResourcesSection) {
|
||||||
|
const { resources, featured, title, description } = props;
|
||||||
|
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const isInView = useInView(ref, { once: true, amount: 0.2 });
|
const isInView = useInView(ref, { once: true, amount: 0.2 });
|
||||||
|
|
||||||
const resources = [
|
|
||||||
{
|
|
||||||
icon: <VideoIcon className="h-6 w-6 text-blue-600 dark:text-blue-400" />,
|
|
||||||
iconBg: 'bg-blue-100 dark:bg-blue-900',
|
|
||||||
title: 'Video Tutorial',
|
|
||||||
description:
|
|
||||||
'Belajar melalui tutorial video dari langkah awal hingga mahir.',
|
|
||||||
buttonText: 'Lihat Semua Video',
|
|
||||||
buttonLink: '#',
|
|
||||||
color: 'from-blue-500 to-blue-700',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: (
|
|
||||||
<BookOpenIcon className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
|
||||||
),
|
|
||||||
iconBg: 'bg-blue-100 dark:bg-blue-900',
|
|
||||||
title: 'Artikel & Tutorial',
|
|
||||||
description:
|
|
||||||
'Pelajari konsep programming melalui artikel yang disusun secara terstruktur.',
|
|
||||||
buttonText: 'Baca Artikel',
|
|
||||||
buttonLink: '#',
|
|
||||||
color: 'from-blue-500 to-blue-700',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <CodeIcon className="h-6 w-6 text-blue-600 dark:text-blue-400" />,
|
|
||||||
iconBg: 'bg-blue-100 dark:bg-blue-900',
|
|
||||||
title: 'Tantangan Koding',
|
|
||||||
description:
|
|
||||||
'Uji kemampuan koding kamu dengan tantangan yang menyenangkan dan menantang.',
|
|
||||||
buttonText: 'Mulai Tantangan',
|
|
||||||
buttonLink: '#',
|
|
||||||
color: 'from-blue-500 to-blue-700',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Share2Icon className="h-6 w-6 text-blue-600 dark:text-blue-400" />,
|
|
||||||
iconBg: 'bg-blue-100 dark:bg-blue-900',
|
|
||||||
title: 'Sharing Session',
|
|
||||||
description:
|
|
||||||
'Ikuti sesi berbagi pengalaman dari programmer berpengalaman dan belajar dari pengalaman mereka.',
|
|
||||||
buttonText: 'Jadwal Session',
|
|
||||||
buttonLink: '#',
|
|
||||||
color: 'from-blue-500 to-blue-700',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const containerVariants = {
|
const containerVariants = {
|
||||||
hidden: { opacity: 0 },
|
hidden: { opacity: 0 },
|
||||||
visible: {
|
visible: {
|
||||||
@@ -100,14 +52,10 @@ export function LearningResources() {
|
|||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
>
|
>
|
||||||
<h2 className="text-3xl font-bold tracking-tighter md:text-4xl/tight lg:text-5xl">
|
<h2 className="text-3xl font-bold tracking-tighter md:text-4xl/tight lg:text-5xl">
|
||||||
Sumber{' '}
|
{title}
|
||||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-primary to-blue-400">
|
|
||||||
Belajar
|
|
||||||
</span>
|
|
||||||
</h2>
|
</h2>
|
||||||
<p className="max-w-[800px] mx-auto text-muted-foreground md:text-lg">
|
<p className="max-w-[800px] mx-auto text-muted-foreground md:text-lg">
|
||||||
Akses berbagai materi belajar yang akan membantu kamu menguasai
|
{description}
|
||||||
konsep programming dengan cara yang menyenangkan.
|
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,7 +66,7 @@ export function LearningResources() {
|
|||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate={isInView ? 'visible' : 'hidden'}
|
animate={isInView ? 'visible' : 'hidden'}
|
||||||
>
|
>
|
||||||
{resources.map((resource, index) => (
|
{resources?.map((resource, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={index}
|
key={index}
|
||||||
className="group relative overflow-hidden rounded-xl border bg-background p-6 transition-all hover:shadow-lg"
|
className="group relative overflow-hidden rounded-xl border bg-background p-6 transition-all hover:shadow-lg"
|
||||||
@@ -127,21 +75,24 @@ export function LearningResources() {
|
|||||||
<div className="absolute top-0 left-0 h-1 w-full bg-gradient-to-r from-primary/50 to-blue-400/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
<div className="absolute top-0 left-0 h-1 w-full bg-gradient-to-r from-primary/50 to-blue-400/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||||
|
|
||||||
<div className="relative z-10">
|
<div className="relative z-10">
|
||||||
<div
|
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900">
|
||||||
className={`mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full ${resource.iconBg}`}
|
<Icon
|
||||||
>
|
icon={resource.icon}
|
||||||
{resource.icon}
|
className="h-6 w-6 text-blue-600 dark:text-blue-400"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="mb-2 text-xl font-bold">{resource.title}</h3>
|
<h3 className="mb-2 text-xl font-bold">{resource.title}</h3>
|
||||||
<p className="mb-6 text-muted-foreground">
|
<p className="mb-6 text-muted-foreground">
|
||||||
{resource.description}
|
{resource.description}
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<a href={resource.buttonLink} target="_blank">
|
||||||
variant="link"
|
<Button
|
||||||
className="p-0 h-auto font-medium text-primary hover:text-primary/80"
|
variant="link"
|
||||||
>
|
className="p-0 h-auto font-medium text-primary hover:text-primary/80"
|
||||||
{resource.buttonText} →
|
>
|
||||||
</Button>
|
{resource.buttonText} →
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute -bottom-32 -right-32 w-64 h-64 bg-gradient-to-tl from-primary/10 to-transparent rounded-full opacity-0 group-hover:opacity-100 transition-all duration-500 group-hover:-translate-y-10 group-hover:-translate-x-10" />
|
<div className="absolute -bottom-32 -right-32 w-64 h-64 bg-gradient-to-tl from-primary/10 to-transparent rounded-full opacity-0 group-hover:opacity-100 transition-all duration-500 group-hover:-translate-y-10 group-hover:-translate-x-10" />
|
||||||
@@ -158,16 +109,14 @@ export function LearningResources() {
|
|||||||
>
|
>
|
||||||
<div className="grid md:grid-cols-2 gap-0">
|
<div className="grid md:grid-cols-2 gap-0">
|
||||||
<div className="p-8 md:p-12 flex flex-col justify-center">
|
<div className="p-8 md:p-12 flex flex-col justify-center">
|
||||||
<div className="inline-block rounded-full bg-primary/10 px-4 py-1.5 text-sm font-medium text-primary mb-4">
|
<div className="inline-block rounded-full bg-primary/10 px-4 py-1.5 text-sm font-medium text-primary mb-4 w-fit">
|
||||||
Rekomendasi Terbaik
|
{featured.label}
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl md:text-3xl font-bold mb-4">
|
<h3 className="text-2xl md:text-3xl font-bold mb-4">
|
||||||
Kursus Lengkap Web Development
|
{featured.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-muted-foreground mb-6">
|
<p className="text-muted-foreground mb-6">
|
||||||
Pelajari HTML, CSS, JavaScript, React, dan Node.js dalam satu
|
{featured.description}
|
||||||
kursus komprehensif yang dirancang untuk pemula hingga tingkat
|
|
||||||
menengah.
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
<Button className="bg-gradient-to-r from-primary to-blue-400 hover:from-primary/90 hover:to-blue-400/90">
|
<Button className="bg-gradient-to-r from-primary to-blue-400 hover:from-primary/90 hover:to-blue-400/90">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getGlobalsCommunitiesSection } from '../../services/get-globals-communitues-section';
|
import { getGlobalsCommunitiesSection } from '../../services/get-globals-communitues-section';
|
||||||
import { getGlobalsFeaturesSection } from '../../services/get-globals-features-section';
|
import { getGlobalsFeaturesSection } from '../../services/get-globals-features-section';
|
||||||
import { getGlobalsHeroSection } from '../../services/get-globals-hero-section';
|
import { getGlobalsHeroSection } from '../../services/get-globals-hero-section';
|
||||||
|
import { getGlobalsLearningResourcesSection } from '../../services/get-globals-learning-resources-section';
|
||||||
import { CallToAction } from './_components/call-to-action';
|
import { CallToAction } from './_components/call-to-action';
|
||||||
import { Community } from './_components/community';
|
import { Community } from './_components/community';
|
||||||
import { Features } from './_components/features';
|
import { Features } from './_components/features';
|
||||||
@@ -13,11 +14,13 @@ import { Testimonials } from './_components/testimonials';
|
|||||||
export const revalidate = 10; // Seconds
|
export const revalidate = 10; // Seconds
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const [heroData, featuresData, communitiesData] = await Promise.all([
|
const [heroData, featuresData, communitiesData, learningResourcesData] =
|
||||||
getGlobalsHeroSection(),
|
await Promise.all([
|
||||||
getGlobalsFeaturesSection(),
|
getGlobalsHeroSection(),
|
||||||
getGlobalsCommunitiesSection(),
|
getGlobalsFeaturesSection(),
|
||||||
]);
|
getGlobalsCommunitiesSection(),
|
||||||
|
getGlobalsLearningResourcesSection(),
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col">
|
<div className="flex min-h-screen flex-col">
|
||||||
@@ -26,7 +29,7 @@ export default async function Page() {
|
|||||||
<Hero {...heroData} />
|
<Hero {...heroData} />
|
||||||
<Features {...featuresData} />
|
<Features {...featuresData} />
|
||||||
<Community {...communitiesData} />
|
<Community {...communitiesData} />
|
||||||
<LearningResources />
|
<LearningResources {...learningResourcesData} />
|
||||||
<Testimonials />
|
<Testimonials />
|
||||||
<CallToAction />
|
<CallToAction />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { GlobalConfig } from 'payload';
|
||||||
|
|
||||||
|
export const LearningResourcesSection: GlobalConfig = {
|
||||||
|
slug: 'learning-resources-section',
|
||||||
|
label: 'Learning Resources Section',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'Sumber Belajar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
type: 'textarea',
|
||||||
|
required: true,
|
||||||
|
defaultValue:
|
||||||
|
'Akses berbagai materi belajar yang akan membantu kamu menguasai konsep programming dengan cara yang menyenangkan.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'resources',
|
||||||
|
type: 'array',
|
||||||
|
required: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'icon',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
type: 'textarea',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'buttonText',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'buttonLink',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: [
|
||||||
|
{
|
||||||
|
icon: 'tabler:video',
|
||||||
|
title: 'Video Tutorial',
|
||||||
|
description:
|
||||||
|
'Belajar melalui tutorial video dari langkah awal hingga mahir.',
|
||||||
|
buttonText: 'Lihat Semua Video',
|
||||||
|
buttonLink: '#',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'tabler:article',
|
||||||
|
title: 'Artikel & Tutorial',
|
||||||
|
description:
|
||||||
|
'Pelajari konsep programming melalui artikel yang disusun secara terstruktur.',
|
||||||
|
buttonText: 'Baca Artikel',
|
||||||
|
buttonLink: '#',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'tabler:brand-vscode',
|
||||||
|
title: 'Tantangan Koding',
|
||||||
|
description:
|
||||||
|
'Uji kemampuan koding kamu dengan tantangan yang menyenangkan dan menantang.',
|
||||||
|
buttonText: 'Mulai Tantangan',
|
||||||
|
buttonLink: '#',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'tabler:device-desktop-share',
|
||||||
|
title: 'Sharing Session',
|
||||||
|
description:
|
||||||
|
'Ikuti sesi berbagi pengalaman dari programmer berpengalaman dan belajar dari pengalaman mereka.',
|
||||||
|
buttonText: 'Jadwal Session',
|
||||||
|
buttonLink: '#',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'featured',
|
||||||
|
type: 'group',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'label',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'Rekomendasi Terbaik',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'Kursus Lengkap Web Development',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
type: 'textarea',
|
||||||
|
required: true,
|
||||||
|
defaultValue:
|
||||||
|
'Pelajari HTML, CSS, JavaScript, React, dan Node.js dalam satu kursus komprehensif yang dirancang untuk pemula hingga tingkat menengah.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'primaryButtonText',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'Mulai Kursus',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'secondaryButtonText',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'Lihat Silabus',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -88,11 +88,13 @@ export interface Config {
|
|||||||
'hero-section': HeroSection;
|
'hero-section': HeroSection;
|
||||||
'features-section': FeaturesSection;
|
'features-section': FeaturesSection;
|
||||||
'communities-section': CommunitiesSection;
|
'communities-section': CommunitiesSection;
|
||||||
|
'learning-resources-section': LearningResourcesSection;
|
||||||
};
|
};
|
||||||
globalsSelect: {
|
globalsSelect: {
|
||||||
'hero-section': HeroSectionSelect<false> | HeroSectionSelect<true>;
|
'hero-section': HeroSectionSelect<false> | HeroSectionSelect<true>;
|
||||||
'features-section': FeaturesSectionSelect<false> | FeaturesSectionSelect<true>;
|
'features-section': FeaturesSectionSelect<false> | FeaturesSectionSelect<true>;
|
||||||
'communities-section': CommunitiesSectionSelect<false> | CommunitiesSectionSelect<true>;
|
'communities-section': CommunitiesSectionSelect<false> | CommunitiesSectionSelect<true>;
|
||||||
|
'learning-resources-section': LearningResourcesSectionSelect<false> | LearningResourcesSectionSelect<true>;
|
||||||
};
|
};
|
||||||
locale: null;
|
locale: null;
|
||||||
user: User & {
|
user: User & {
|
||||||
@@ -347,6 +349,32 @@ export interface CommunitiesSection {
|
|||||||
updatedAt?: string | null;
|
updatedAt?: string | null;
|
||||||
createdAt?: string | null;
|
createdAt?: string | null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "learning-resources-section".
|
||||||
|
*/
|
||||||
|
export interface LearningResourcesSection {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
resources: {
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
buttonText: string;
|
||||||
|
buttonLink: string;
|
||||||
|
id?: string | null;
|
||||||
|
}[];
|
||||||
|
featured: {
|
||||||
|
label: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
primaryButtonText: string;
|
||||||
|
secondaryButtonText: string;
|
||||||
|
};
|
||||||
|
updatedAt?: string | null;
|
||||||
|
createdAt?: string | null;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "hero-section_select".
|
* via the `definition` "hero-section_select".
|
||||||
@@ -421,6 +449,36 @@ export interface CommunitiesSectionSelect<T extends boolean = true> {
|
|||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
globalType?: T;
|
globalType?: T;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "learning-resources-section_select".
|
||||||
|
*/
|
||||||
|
export interface LearningResourcesSectionSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
description?: T;
|
||||||
|
resources?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
icon?: T;
|
||||||
|
title?: T;
|
||||||
|
description?: T;
|
||||||
|
buttonText?: T;
|
||||||
|
buttonLink?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
featured?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
title?: T;
|
||||||
|
description?: T;
|
||||||
|
primaryButtonText?: T;
|
||||||
|
secondaryButtonText?: T;
|
||||||
|
};
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
globalType?: T;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "auth".
|
* via the `definition` "auth".
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { fileURLToPath } from 'url';
|
|||||||
import { CommunitiesSection } from './collections/CommunitiesSection';
|
import { CommunitiesSection } from './collections/CommunitiesSection';
|
||||||
import { FeaturesSection } from './collections/FeaturesSection';
|
import { FeaturesSection } from './collections/FeaturesSection';
|
||||||
import { HeroSection } from './collections/HeroSection';
|
import { HeroSection } from './collections/HeroSection';
|
||||||
|
import { LearningResourcesSection } from './collections/LearningResourcesSection';
|
||||||
import { Media } from './collections/Media';
|
import { Media } from './collections/Media';
|
||||||
import { Users } from './collections/Users';
|
import { Users } from './collections/Users';
|
||||||
|
|
||||||
@@ -31,7 +32,12 @@ export default buildConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
collections: [Users, Media],
|
collections: [Users, Media],
|
||||||
globals: [HeroSection, FeaturesSection, CommunitiesSection],
|
globals: [
|
||||||
|
HeroSection,
|
||||||
|
FeaturesSection,
|
||||||
|
CommunitiesSection,
|
||||||
|
LearningResourcesSection,
|
||||||
|
],
|
||||||
editor: lexicalEditor(),
|
editor: lexicalEditor(),
|
||||||
secret: process.env.CMS_SECRET || '',
|
secret: process.env.CMS_SECRET || '',
|
||||||
typescript: {
|
typescript: {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { payload } from '../lib/payload';
|
||||||
|
|
||||||
|
export async function getGlobalsLearningResourcesSection() {
|
||||||
|
return await payload.findGlobal({
|
||||||
|
slug: 'learning-resources-section',
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user