feat: add FeaturesSection global configuration and integrate with Features component

This commit is contained in:
ArraysID
2025-05-11 05:24:31 +07:00
parent 18d11fd1c3
commit fd35cb9525
6 changed files with 152 additions and 38 deletions
@@ -6,40 +6,22 @@ import {
LaptopIcon, LaptopIcon,
UsersIcon, UsersIcon,
} from '@imphnen-frontend-service/shadcn-ui/atoms'; } from '@imphnen-frontend-service/shadcn-ui/atoms';
import { FeaturesSection } from 'apps/landing/src/payload-types';
import { motion, useInView } from 'framer-motion'; import { motion, useInView } from 'framer-motion';
import { useRef } from 'react'; 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 ref = useRef(null);
const isInView = useInView(ref, { once: true, amount: 0.2 }); 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 = { const containerVariants = {
hidden: { opacity: 0 }, hidden: { opacity: 0 },
visible: { visible: {
@@ -64,7 +46,6 @@ export function Features() {
id="fitur" id="fitur"
className="w-full py-20 md:py-32 relative overflow-hidden" 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 -z-10">
<div className="absolute inset-0 bg-gradient-to-b from-background via-background to-muted/50" /> <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" /> <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 Fitur Unggulan
</div> </div>
<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">
Belajar programming dengan{' '} {heading}
<span className="bg-clip-text text-transparent bg-gradient-to-r from-primary to-blue-400">
cara yang lebih baik
</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">
IMPHNEN hadir dengan berbagai fitur untuk membantu kamu menjadi {subheading}
programmer handal tanpa harus pusing dengan coding.
</p> </p>
</motion.div> </motion.div>
</div> </div>
@@ -100,7 +77,7 @@ export function Features() {
initial="hidden" initial="hidden"
animate={isInView ? 'visible' : 'hidden'} animate={isInView ? 'visible' : 'hidden'}
> >
{features.map((feature, index) => ( {featuresFormatted?.map((feature, index) => (
<motion.div <motion.div
key={index} 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" 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> </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 { getGlobalsHeroSection } from '../../repositories/get-globals-hero-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';
@@ -10,13 +11,14 @@ import { Testimonials } from './_components/testimonials';
export default async function Page() { export default async function Page() {
const heroData = await getGlobalsHeroSection(); const heroData = await getGlobalsHeroSection();
const featuresData = await getGlobalsFeaturesSection();
return ( return (
<div className="flex min-h-screen flex-col"> <div className="flex min-h-screen flex-col">
<Header /> <Header />
<main className="flex-1"> <main className="flex-1">
<Hero {...heroData} /> <Hero {...heroData} />
<Features /> <Features {...featuresData} />
<Community /> <Community />
<LearningResources /> <LearningResources />
<Testimonials /> <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: { globals: {
'hero-section': HeroSection; 'hero-section': HeroSection;
'features-section': FeaturesSection;
}; };
globalsSelect: { globalsSelect: {
'hero-section': HeroSectionSelect<false> | HeroSectionSelect<true>; 'hero-section': HeroSectionSelect<false> | HeroSectionSelect<true>;
'features-section': FeaturesSectionSelect<false> | FeaturesSectionSelect<true>;
}; };
locale: null; locale: null;
user: User & { user: User & {
@@ -301,6 +303,25 @@ export interface HeroSection {
updatedAt?: string | null; updatedAt?: string | null;
createdAt?: 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 * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "hero-section_select". * via the `definition` "hero-section_select".
@@ -329,6 +350,25 @@ export interface HeroSectionSelect<T extends boolean = true> {
createdAt?: T; createdAt?: T;
globalType?: 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 * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth". * via the `definition` "auth".
+2 -1
View File
@@ -5,6 +5,7 @@ import { buildConfig } from 'payload';
import sharp from 'sharp'; import sharp from 'sharp';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { FeaturesSection } from './collections/FeaturesSection';
import { HeroSection } from './collections/HeroSection'; import { HeroSection } from './collections/HeroSection';
import { Media } from './collections/Media'; import { Media } from './collections/Media';
import { Users } from './collections/Users'; import { Users } from './collections/Users';
@@ -26,7 +27,7 @@ export default buildConfig({
}, },
}, },
collections: [Users, Media], collections: [Users, Media],
globals: [HeroSection], globals: [HeroSection, FeaturesSection],
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 getGlobalsFeaturesSection() {
return await payload.findGlobal({
slug: 'features-section',
});
}