feat: add FeaturesSection global configuration and integrate with Features component
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -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".
|
||||||
|
|||||||
@@ -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',
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user