feat: add FeaturesSection global configuration and integrate with Features component

This commit is contained in:
ArraysID
2025-05-10 04:08:37 +07:00
parent 7b490702b9
commit 39a57b4fed
6 changed files with 152 additions and 38 deletions
@@ -6,40 +6,22 @@ import {
LaptopIcon,
UsersIcon,
} from '@imphnen-frontend-service/shadcn-ui/atoms';
import { FeaturesSection } from 'apps/landing/src/payload-types';
import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';
export function Features() {
export function Features(props: FeaturesSection) {
const { heading, subheading, features } = props;
const featuresFormatted = features?.map((f) => ({
icon: IconMapper(String(f.icon)),
title: f.title,
description: f.description,
}));
const ref = useRef(null);
const isInView = useInView(ref, { once: true, amount: 0.2 });
const features = [
{
icon: <LaptopIcon className="h-6 w-6 text-primary" />,
title: 'Belajar Tanpa Koding',
description:
'Pelajari konsep programming dengan cara yang mudah dipahami tanpa harus menulis kode yang rumit.',
},
{
icon: <UsersIcon className="h-6 w-6 text-primary" />,
title: 'Komunitas Supportif',
description:
'Bergabunglah dengan komunitas programmer Indonesia yang siap membantu dan berbagi pengalaman.',
},
{
icon: <BookOpenIcon className="h-6 w-6 text-primary" />,
title: 'Tutorial Interaktif',
description:
'Akses tutorial interaktif yang membuat konsep programming lebih mudah untuk dipahami.',
},
{
icon: <CodeIcon className="h-6 w-6 text-primary" />,
title: 'Proyek Praktis',
description:
'Terapkan pengetahuan Anda dalam proyek nyata dengan panduan langkah demi langkah.',
},
];
const containerVariants = {
hidden: { opacity: 0 },
visible: {
@@ -64,7 +46,6 @@ export function Features() {
id="fitur"
className="w-full py-20 md:py-32 relative overflow-hidden"
>
{/* Background Elements */}
<div className="absolute inset-0 -z-10">
<div className="absolute inset-0 bg-gradient-to-b from-background via-background to-muted/50" />
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] rounded-full bg-gradient-to-tr from-primary/5 to-blue-400/5 blur-3xl" />
@@ -82,14 +63,10 @@ export function Features() {
Fitur Unggulan
</div>
<h2 className="text-3xl font-bold tracking-tighter md:text-4xl/tight lg:text-5xl">
Belajar programming dengan{' '}
<span className="bg-clip-text text-transparent bg-gradient-to-r from-primary to-blue-400">
cara yang lebih baik
</span>
{heading}
</h2>
<p className="max-w-[800px] mx-auto text-muted-foreground md:text-lg">
IMPHNEN hadir dengan berbagai fitur untuk membantu kamu menjadi
programmer handal tanpa harus pusing dengan coding.
{subheading}
</p>
</motion.div>
</div>
@@ -100,7 +77,7 @@ export function Features() {
initial="hidden"
animate={isInView ? 'visible' : 'hidden'}
>
{features.map((feature, index) => (
{featuresFormatted?.map((feature, index) => (
<motion.div
key={index}
className="group relative overflow-hidden rounded-xl border bg-background/50 backdrop-blur-sm p-6 transition-all hover:shadow-md hover:shadow-primary/5 hover:border-primary/50"
@@ -124,3 +101,18 @@ export function Features() {
</section>
);
}
function IconMapper(name: string) {
switch (name) {
case 'LaptopIcon':
return <LaptopIcon className="h-6 w-6 text-primary" />;
case 'UsersIcon':
return <UsersIcon className="h-6 w-6 text-primary" />;
case 'BookOpenIcon':
return <BookOpenIcon className="h-6 w-6 text-primary" />;
case 'CodeIcon':
return <CodeIcon className="h-6 w-6 text-primary" />;
default:
return null;
}
}
+3 -1
View File
@@ -1,3 +1,4 @@
import { getGlobalsFeaturesSection } from '../../repositories/get-globals-features-section';
import { getGlobalsHeroSection } from '../../repositories/get-globals-hero-section';
import { CallToAction } from './_components/call-to-action';
import { Community } from './_components/community';
@@ -10,13 +11,14 @@ import { Testimonials } from './_components/testimonials';
export default async function Page() {
const heroData = await getGlobalsHeroSection();
const featuresData = await getGlobalsFeaturesSection();
return (
<div className="flex min-h-screen flex-col">
<Header />
<main className="flex-1">
<Hero {...heroData} />
<Features />
<Features {...featuresData} />
<Community />
<LearningResources />
<Testimonials />
@@ -0,0 +1,72 @@
import { GlobalConfig } from 'payload';
export const FeaturesSection: GlobalConfig = {
slug: 'features-section',
label: 'Features Section',
access: {
read: (): boolean => true,
},
fields: [
{
name: 'heading',
type: 'text',
defaultValue: 'Belajar programming dengan cara yang lebih baik',
},
{
name: 'subheading',
type: 'text',
defaultValue:
'IMPHNEN hadir dengan berbagai fitur untuk membantu kamu menjadi programmer handal tanpa harus pusing dengan coding.',
},
{
name: 'features',
type: 'array',
defaultValue: [
{
icon: 'LaptopIcon',
title: 'Belajar Tanpa Koding',
description:
'Pelajari konsep programming dengan cara yang mudah dipahami tanpa harus menulis kode yang rumit.',
},
{
icon: 'UsersIcon',
title: 'Komunitas Supportif',
description:
'Bergabunglah dengan komunitas programmer Indonesia yang siap membantu dan berbagi pengalaman.',
},
{
icon: 'BookOpenIcon',
title: 'Tutorial Interaktif',
description:
'Akses tutorial interaktif yang membuat konsep programming lebih mudah untuk dipahami.',
},
{
icon: 'CodeIcon',
title: 'Proyek Praktis',
description:
'Terapkan pengetahuan Anda dalam proyek nyata dengan panduan langkah demi langkah.',
},
],
fields: [
{
name: 'icon',
type: 'select',
options: [
{ label: 'LaptopIcon', value: 'LaptopIcon' },
{ label: 'UsersIcon', value: 'UsersIcon' },
{ label: 'BookOpenIcon', value: 'BookOpenIcon' },
{ label: 'CodeIcon', value: 'CodeIcon' },
],
},
{
name: 'title',
type: 'text',
},
{
name: 'description',
type: 'textarea',
},
],
},
],
};
+40
View File
@@ -86,9 +86,11 @@ export interface Config {
};
globals: {
'hero-section': HeroSection;
'features-section': FeaturesSection;
};
globalsSelect: {
'hero-section': HeroSectionSelect<false> | HeroSectionSelect<true>;
'features-section': FeaturesSectionSelect<false> | FeaturesSectionSelect<true>;
};
locale: null;
user: User & {
@@ -301,6 +303,25 @@ export interface HeroSection {
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "features-section".
*/
export interface FeaturesSection {
id: number;
heading?: string | null;
subheading?: string | null;
features?:
| {
icon?: ('LaptopIcon' | 'UsersIcon' | 'BookOpenIcon' | 'CodeIcon') | null;
title?: string | null;
description?: string | null;
id?: string | null;
}[]
| null;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "hero-section_select".
@@ -329,6 +350,25 @@ export interface HeroSectionSelect<T extends boolean = true> {
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "features-section_select".
*/
export interface FeaturesSectionSelect<T extends boolean = true> {
heading?: T;
subheading?: T;
features?:
| T
| {
icon?: T;
title?: T;
description?: T;
id?: T;
};
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
+2 -1
View File
@@ -5,6 +5,7 @@ import { buildConfig } from 'payload';
import sharp from 'sharp';
import { fileURLToPath } from 'url';
import { FeaturesSection } from './collections/FeaturesSection';
import { HeroSection } from './collections/HeroSection';
import { Media } from './collections/Media';
import { Users } from './collections/Users';
@@ -26,7 +27,7 @@ export default buildConfig({
},
},
collections: [Users, Media],
globals: [HeroSection],
globals: [HeroSection, FeaturesSection],
editor: lexicalEditor(),
secret: process.env.CMS_SECRET || '',
typescript: {
@@ -0,0 +1,7 @@
import { payload } from '../lib/payload';
export async function getGlobalsFeaturesSection() {
return await payload.findGlobal({
slug: 'features-section',
});
}