diff --git a/apps/dimentorin/public/image/mascot-character.webp b/apps/dimentorin/public/image/mascot-character.webp new file mode 100644 index 0000000..34267f5 Binary files /dev/null and b/apps/dimentorin/public/image/mascot-character.webp differ diff --git a/apps/dimentorin/public/image/mascot-full.webp b/apps/dimentorin/public/image/mascot-full.webp new file mode 100644 index 0000000..ccce7b6 Binary files /dev/null and b/apps/dimentorin/public/image/mascot-full.webp differ diff --git a/apps/dimentorin/public/image/mascot-text.webp b/apps/dimentorin/public/image/mascot-text.webp new file mode 100644 index 0000000..18d2edb Binary files /dev/null and b/apps/dimentorin/public/image/mascot-text.webp differ diff --git a/apps/dimentorin/public/image/testimonial.webp b/apps/dimentorin/public/image/testimonial.webp new file mode 100644 index 0000000..b4555c9 Binary files /dev/null and b/apps/dimentorin/public/image/testimonial.webp differ diff --git a/apps/dimentorin/public/image/what-we-offer/1-on-1-mentoring.webp b/apps/dimentorin/public/image/what-we-offer/1-on-1-mentoring.webp new file mode 100644 index 0000000..a6a07b6 Binary files /dev/null and b/apps/dimentorin/public/image/what-we-offer/1-on-1-mentoring.webp differ diff --git a/apps/dimentorin/public/image/what-we-offer/ai-powered-learning.webp b/apps/dimentorin/public/image/what-we-offer/ai-powered-learning.webp new file mode 100644 index 0000000..e44f3cc Binary files /dev/null and b/apps/dimentorin/public/image/what-we-offer/ai-powered-learning.webp differ diff --git a/apps/dimentorin/public/image/what-we-offer/community.webp b/apps/dimentorin/public/image/what-we-offer/community.webp new file mode 100644 index 0000000..f309286 Binary files /dev/null and b/apps/dimentorin/public/image/what-we-offer/community.webp differ diff --git a/apps/dimentorin/public/logos/logo.svg b/apps/dimentorin/public/logos/logo.svg new file mode 100644 index 0000000..ca0f36e --- /dev/null +++ b/apps/dimentorin/public/logos/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dimentorin/public/logos/simple.svg b/apps/dimentorin/public/logos/simple.svg new file mode 100644 index 0000000..0fd70f3 --- /dev/null +++ b/apps/dimentorin/public/logos/simple.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/dimentorin/src/app/(public)/(home)/_components/cta-section.tsx b/apps/dimentorin/src/app/(public)/(home)/_components/cta-section.tsx new file mode 100644 index 0000000..65e0f6b --- /dev/null +++ b/apps/dimentorin/src/app/(public)/(home)/_components/cta-section.tsx @@ -0,0 +1,154 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { cn } from '@imphnen-frontend-service/utils'; +import { FC, useRef } from 'react'; +import { motion, useInView, Variants } from 'framer-motion'; + +export const CTASection: FC = () => { + const ref = useRef(null) + const isInView = useInView(ref, { once: true, amount: 0.2 }) + + const containerVariants: Variants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + delayChildren: 0.2, + }, + }, + } + + const scaleVariant: Variants = { + hidden: { scale: 0 }, + visible: { + scale: 1, + transition: { + duration: 0.5, + ease: [0.25, 0.46, 0.45, 0.94], + }, + }, + } + + const contentVariant: Variants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.5, + ease: [0.25, 0.46, 0.45, 0.94], + }, + }, + } + + return ( +
+ + {/* Background gradients and shapes */} +
+ + + + + + +
+
+ + + +
+ + Start Your IT Journey Now! + + + Jangan biarkan progress mu hanya jadi side story! + + + Dapatkan mentor terbaik dan gunakan AI untuk belajar lebih cepat. Waktunya jadi protagonist dalam perjalanan IT-mu! + +
+ + + + + +
+
+ ) +} diff --git a/apps/dimentorin/src/app/(public)/(home)/_components/faq-section.tsx b/apps/dimentorin/src/app/(public)/(home)/_components/faq-section.tsx new file mode 100644 index 0000000..3b2e480 --- /dev/null +++ b/apps/dimentorin/src/app/(public)/(home)/_components/faq-section.tsx @@ -0,0 +1,114 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { Accordion, AccordionOptions } from '@imphnen-frontend-service/ui/molecules' +import { FC, useRef } from 'react' +import { motion, useInView, Variants } from 'framer-motion' +import { cn, For } from '@imphnen-frontend-service/utils' + +const FAQ_DATA: AccordionOptions[] = [ + { title: 'Apakah saya bisa memilih mentor sendiri?', description: 'Tentu! Kamu bisa memilih mentor yang sesuai dengan kebutuhan dan level skill kamu.' }, + { title: 'Apakah mentoring ini cocok untuk pemula?', description: 'Yap! Baik newbie maupun seasoned dev bisa menemukan mentor yang pas untuk mempercepat progres belajarnya.' }, + { title: 'Apakah ini seperti sekolah coding biasa?', description: 'Nope! Ini lebih dari sekadar kursus-kursus AI guide dan mentor berpengalaman, kamu mendapatkan pengalaman belajar yang jauh lebih personal dan efektif.' }, +] + +export const FAQSection: FC = () => { + const ref = useRef(null) + const isInView = useInView(ref, { once: true, amount: 0.2 }) + + const containerVariants: Variants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + delayChildren: 0.2, + }, + }, + } + + const childVariants: Variants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.4, + ease: [0.25, 0.46, 0.45, 0.94], + }, + }, + } + + return ( +
+ {/* Background shapes */} +
+ + + +
+ + + + + +
+ + Console log for new developers + + + + + {(props) => ( + + + + )} + + +
+
+ ) +} diff --git a/apps/dimentorin/src/app/(public)/(home)/_components/hero-section.tsx b/apps/dimentorin/src/app/(public)/(home)/_components/hero-section.tsx new file mode 100644 index 0000000..06437ee --- /dev/null +++ b/apps/dimentorin/src/app/(public)/(home)/_components/hero-section.tsx @@ -0,0 +1,73 @@ +import { Button } from "@imphnen-frontend-service/ui/atoms"; +import { motion } from 'framer-motion' + +export function HeroSection() { + return ( +
+ {/* Background gradients and shapes */} +
+ + + + + + + +
+
+
+ +
+ +

DIMENTORIN

+

+ Learn IT smarter with AI & expert mentors! +

+

+ Apakah kamu siap untuk grind skill TI-mu ke level Ultimate? Dengan AI sebagai navigator dan mentor profesional sebagai sensei-mu, perjalanan belajarmu akan lebih cepat, efektif, dan penuh EXP! +

+
+ + + + +
+
+ ) +} \ No newline at end of file diff --git a/apps/dimentorin/src/app/(public)/(home)/_components/testimonial-section.tsx b/apps/dimentorin/src/app/(public)/(home)/_components/testimonial-section.tsx new file mode 100644 index 0000000..3ead980 --- /dev/null +++ b/apps/dimentorin/src/app/(public)/(home)/_components/testimonial-section.tsx @@ -0,0 +1,113 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms' +import { For } from '@imphnen-frontend-service/utils' +import { FC, useRef } from 'react' +import { motion, useInView, Variants } from 'framer-motion' +import { StarFilled } from '@ant-design/icons' + +export const TestimonialSection: FC = () => { + const ref = useRef(null) + const isInView = useInView(ref, { once: true, amount: 0.2 }) + + const containerVariants: Variants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + delayChildren: 0.2, + }, + }, + } + + const childVariants: Variants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.4, + ease: [0.25, 0.46, 0.45, 0.94], + }, + }, + } + + return ( +
+ + + + + + Words from our dellow devs + + +
+ + + {(_, index) => ( + +
+ Testimonial +
+
+

+ Riko, Junior Web Developer +

+

+ Rasanya seperti punya AI waifu yang ngajarin ngoding! Mentoringnya juga super insightful. Thanks, Dimentorin! +

+ + + +
+
+ )} +
+
+
+
+ ) +} diff --git a/apps/dimentorin/src/app/(public)/(home)/_components/what-we-offer-section.tsx b/apps/dimentorin/src/app/(public)/(home)/_components/what-we-offer-section.tsx new file mode 100644 index 0000000..1bf434e --- /dev/null +++ b/apps/dimentorin/src/app/(public)/(home)/_components/what-we-offer-section.tsx @@ -0,0 +1,84 @@ +import { Button } from "@imphnen-frontend-service/ui/atoms"; +import { For } from "@imphnen-frontend-service/utils"; +import { FC, useRef } from "react"; +import { motion, useInView, Variants } from 'framer-motion' + +const OFFERS: { title: string; description: string; image: string }[] = [ + { title: "AI-Powered Learning", description: "Level Up Instantly!", image: "/image/what-we-offer/ai-powered-learning.webp" }, + { title: "1-on-1 Mentoring", description: "Sensei dari Dunia Nyata!", image: "/image/what-we-offer/1-on-1-mentoring.webp" }, + { title: "Community & Resources", description: "Connect dengan Fellow Devs!", image: "/image/what-we-offer/community.webp" }, +] + +export const WhatWeOfferSection: FC = () => { + const ref = useRef(null) + const isInView = useInView(ref, { once: true, amount: 0.2 }) + + const containerVariants: Variants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + delayChildren: 0.2, + }, + }, + } + + const childVariants: Variants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.4, + ease: [0.25, 0.46, 0.45, 0.94], + }, + }, + } + + return ( +
+ + + + + + + {(offer) => ( + + {offer.title} +
+

{offer.title}

+

{offer.description}

+
+
+ )} +
+
+
+ ) +} \ No newline at end of file diff --git a/apps/dimentorin/src/app/(public)/(home)/page.tsx b/apps/dimentorin/src/app/(public)/(home)/page.tsx new file mode 100644 index 0000000..87b0dee --- /dev/null +++ b/apps/dimentorin/src/app/(public)/(home)/page.tsx @@ -0,0 +1,20 @@ +import { FC, ReactElement } from 'react'; +import { HeroSection } from './_components/hero-section'; +import { WhatWeOfferSection } from './_components/what-we-offer-section'; +import { TestimonialSection } from './_components/testimonial-section'; +import { FAQSection } from './_components/faq-section'; +import { CTASection } from './_components/cta-section'; + +export const Components: FC = (): ReactElement => { + return ( + <> + + + + + + + ); +}; + +export default Components; diff --git a/apps/dimentorin/src/app/(public)/_components/footer.tsx b/apps/dimentorin/src/app/(public)/_components/footer.tsx new file mode 100644 index 0000000..daf2a1f --- /dev/null +++ b/apps/dimentorin/src/app/(public)/_components/footer.tsx @@ -0,0 +1,157 @@ +import { DiscordOutlined, GithubOutlined, InstagramOutlined } from '@ant-design/icons'; +import { cn, For } from '@imphnen-frontend-service/utils'; +import React, { FC, useRef } from 'react'; +import { Link } from 'react-router-dom'; +import { motion, useInView, Variants } from 'framer-motion'; +import { SteamIcon } from '../../_components/icons'; + +const PAGES: { label: string; href: string }[] = [ + { label: "Home", href: "/" }, + { label: 'Mentoring', href: '/mentoring' }, + { label: 'Resources', href: '/resources' }, + { label: 'Articles', href: '/articles' }, +] + +const COMMUNITY: { label: string; href: string; icon: React.ReactNode }[] = [ + { label: 'Discord', href: 'https://discord.gg/imphnen', icon: }, + { label: 'Steam', href: 'https://steamcommunity.com/groups/IMPHNEN', icon: }, + { label: 'Github', href: 'https://github.com/IMPHNEN', icon: }, + { label: 'Instagram', href: 'https://www.instagram.com/imphnen.dev', icon: }, +] + +export const Footer: FC = () => { + const ref = useRef(null) + const isInView = useInView(ref, { once: true, amount: 0.2 }) + + const containerVariants: Variants = { + hidden: { opacity: 0 }, + visible: { opacity: 1 }, + } + + const childVariants: Variants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.5, + ease: [0.25, 0.46, 0.45, 0.94], + }, + }, + } + + const mascotVariants: Variants = { + hidden: { opacity: 0, y: 20, rotate: -12 }, + visible: { + opacity: 1, + y: 0, + rotate: 0, + transition: { + duration: 0.5, + ease: [0.25, 0.46, 0.45, 0.94], + }, + }, + } + + const mascotTextVariants: Variants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.4, + delay: 0.4, + ease: [0.25, 0.46, 0.45, 0.94], + }, + }, + } + + return ( +
+ + {/* Mascot */} + + + + + +
+
+ + + + Dimentorin by IMPHNEN + + + Karena Belajar IT Itu Harusnya Se-Seru Anime Favoritmu! + +
+ + + © IMPHNEN {new Date().getFullYear()} All Rights Reserved + +
+ + +
+

Pages

+
    + + {({ label, href }) => ( +
  • + {label} +
  • + )} +
    +
+
+ +
+

Join Our Community

+
    + + {({ label, href, icon }) => ( +
  • + + {icon} + {label} + +
  • + )} +
    +
+
+
+ + + © IMPHNEN {new Date().getFullYear()} All Rights Reserved + +
+
+ ) +} diff --git a/apps/dimentorin/src/app/(public)/_components/header.tsx b/apps/dimentorin/src/app/(public)/_components/header.tsx new file mode 100644 index 0000000..585e126 --- /dev/null +++ b/apps/dimentorin/src/app/(public)/_components/header.tsx @@ -0,0 +1,93 @@ +import { CloseOutlined, MenuOutlined } from "@ant-design/icons"; +import { Button } from "@imphnen-frontend-service/ui/atoms"; +import { cn, For, Show } from "@imphnen-frontend-service/utils"; +import { motion, useMotionValueEvent, useScroll, Variants } from "framer-motion"; +import { FC, useState } from "react"; +import { Link, useLocation } from "react-router-dom"; + +const MENUS: { label: string; href: string }[] = [ + { label: "Home", href: "/" }, + { label: 'Mentoring', href: '/mentoring' }, + { label: 'Resources', href: '/resources' }, + { label: 'Articles', href: '/articles' }, +] + +export const Header: FC = () => { + const location = useLocation(); + const { scrollY } = useScroll() + + const [expandMenu, setExpandMenu] = useState(false); + const [show, setShow] = useState(true) + + const headerVariants: Variants = { + hidden: { opacity: 0, y: -100 }, + show: { opacity: 1, y: 0 }, + }; + + useMotionValueEvent(scrollY, "change", (latest) => { + const prev = scrollY.getPrevious() || 0 + if (latest > prev && latest > 100) setShow(false) + else setShow(true) + }) + + return ( +
+ +
+
+ IMPHNEN Logo +
+ + + +
+ + +
+
+
+
+ ); +} diff --git a/apps/dimentorin/src/app/(public)/layout.tsx b/apps/dimentorin/src/app/(public)/layout.tsx new file mode 100644 index 0000000..d9ffaf8 --- /dev/null +++ b/apps/dimentorin/src/app/(public)/layout.tsx @@ -0,0 +1,15 @@ +import { Outlet } from "react-router-dom"; +import { Header } from "./_components/header"; +import { Footer } from "./_components/footer"; + +export default function Layout() { + return ( +
+
+
+ +
+
+
+ ); +} diff --git a/apps/dimentorin/src/app/_components/icons/index.ts b/apps/dimentorin/src/app/_components/icons/index.ts new file mode 100644 index 0000000..342889c --- /dev/null +++ b/apps/dimentorin/src/app/_components/icons/index.ts @@ -0,0 +1 @@ +export * from './steam'; \ No newline at end of file diff --git a/apps/dimentorin/src/app/_components/icons/steam.tsx b/apps/dimentorin/src/app/_components/icons/steam.tsx new file mode 100644 index 0000000..386439b --- /dev/null +++ b/apps/dimentorin/src/app/_components/icons/steam.tsx @@ -0,0 +1,9 @@ +import { FC } from "react" + +export const SteamIcon: FC> = ({ ...props }) => { + return ( + + + + ) +} diff --git a/apps/dimentorin/src/app/page.tsx b/apps/dimentorin/src/app/page.tsx deleted file mode 100644 index f135208..0000000 --- a/apps/dimentorin/src/app/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { FC, ReactElement } from 'react'; - -export const Components: FC = (): ReactElement => { - return <>Hallo; -}; - -export default Components; diff --git a/apps/dimentorin/src/index.css b/apps/dimentorin/src/index.css index ab312f2..931d17d 100644 --- a/apps/dimentorin/src/index.css +++ b/apps/dimentorin/src/index.css @@ -119,3 +119,9 @@ font-weight: 400; } } + +@layer utilities { + .scrollbar-hide::-webkit-scrollbar { + @apply hidden; + } +} diff --git a/libs/ui/src/molecules/accordion/accordion.tsx b/libs/ui/src/molecules/accordion/accordion.tsx new file mode 100644 index 0000000..5064814 --- /dev/null +++ b/libs/ui/src/molecules/accordion/accordion.tsx @@ -0,0 +1,53 @@ +import { cn, Show } from "@imphnen-frontend-service/utils" +import { DetailedHTMLProps, FC, HTMLAttributes, useState } from "react" +import { AnimatePresence, motion, Variants } from "framer-motion" +import { MinusOutlined, PlusOutlined } from "@ant-design/icons" + +export type AccordionOptions = { + title: string + description: string +} + +export type AccordionProps = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & AccordionOptions & { + titleClassName?: string + contentClassName?: string +} + +export const Accordion: FC = ({ title, description, className, titleClassName, contentClassName, ...rest }) => { + const [expand, setExpand] = useState(false) + + const variants: Variants = { + expand: { opacity: 1, y: 0, marginTop: 16, height: 'auto' }, + collapse: { opacity: 0, y: -10, marginTop: 0, height: 0 }, + } + + return ( +
+
setExpand(prev => !prev)}> +

{title}

+
+ }> + + +
+
+ + {expand && ( + + {description} + + )} + +
+ ) +} diff --git a/libs/ui/src/molecules/accordion/index.ts b/libs/ui/src/molecules/accordion/index.ts new file mode 100644 index 0000000..ce956fe --- /dev/null +++ b/libs/ui/src/molecules/accordion/index.ts @@ -0,0 +1 @@ +export * from './accordion'; diff --git a/libs/ui/src/molecules/index.ts b/libs/ui/src/molecules/index.ts index 1bc94a6..ad99b97 100644 --- a/libs/ui/src/molecules/index.ts +++ b/libs/ui/src/molecules/index.ts @@ -4,3 +4,4 @@ export * from './input-field'; export * from './pagination'; export * from './modal/modal'; export * from './stepper'; +export * from './accordion'; \ No newline at end of file diff --git a/libs/utils/src/index.ts b/libs/utils/src/index.ts index 722f7b8..93a8043 100644 --- a/libs/utils/src/index.ts +++ b/libs/utils/src/index.ts @@ -6,3 +6,4 @@ export * from './local-storage'; export * from './cookies'; export * from './session'; export * from './constants'; +export * from './logic'; diff --git a/libs/utils/src/logic/for.tsx b/libs/utils/src/logic/for.tsx new file mode 100644 index 0000000..abdf388 --- /dev/null +++ b/libs/utils/src/logic/for.tsx @@ -0,0 +1,27 @@ +export interface ForProps { + data: readonly T[] + children: (item: T, index: number) => U | null + fallback?: React.ReactNode | null +} + +/** + * A functional component that renders a list of children components from a given + * array of data. + * + * @param data - The array of data to render + * @param children - A function that takes the current item and index and returns + * the child component to render + * @param fallback - An optional fallback component to render when the data is empty + * + * @returns An array of rendered child components if the data is not empty, otherwise + * the fallback component if it is provided, null otherwise + */ +export function For({ + data, + children, + fallback = null +}: ForProps): (U | null)[] | React.ReactNode | null { + if (!Array.isArray(data) || !data?.length) return fallback + + return data.map((item, idx) => children(item, idx)) +} diff --git a/libs/utils/src/logic/index.ts b/libs/utils/src/logic/index.ts new file mode 100644 index 0000000..c3c1579 --- /dev/null +++ b/libs/utils/src/logic/index.ts @@ -0,0 +1,2 @@ +export * from './for' +export * from './show' diff --git a/libs/utils/src/logic/show.tsx b/libs/utils/src/logic/show.tsx new file mode 100644 index 0000000..c8305cc --- /dev/null +++ b/libs/utils/src/logic/show.tsx @@ -0,0 +1,19 @@ +export interface ShowProps { + condition: boolean + children: React.ReactNode + fallback?: React.ReactNode | null +} + +/** + * Conditionally renders the `children` component if `condition` is true. If + * `condition` is false, renders the `fallback` component instead. + * + * If `fallback` is `null`, renders nothing. + * + * @param condition - The condition to check + * @param children - The component to render if `condition` is true + * @param fallback - The component to render if `condition` is false + */ +export function Show({ condition, children, fallback = null }: ShowProps) { + return condition ? children : fallback +}