From bf31fda249436870612b54dd3f09da24063c7f95 Mon Sep 17 00:00:00 2001 From: Alvalens Date: Mon, 26 May 2025 23:09:29 +0700 Subject: [PATCH] feat: restyle experience section --- app/about/components/experience.jsx | 361 +++++++++++++++++++--------- 1 file changed, 242 insertions(+), 119 deletions(-) diff --git a/app/about/components/experience.jsx b/app/about/components/experience.jsx index be50447..87e0c75 100644 --- a/app/about/components/experience.jsx +++ b/app/about/components/experience.jsx @@ -1,5 +1,70 @@ +"use client"; import Hr from "@/components/Hr"; -import { motion } from "framer-motion"; +import { motion, AnimatePresence } from "framer-motion"; +import { useState } from "react"; + +const experiences = [ + { + id: 1, + period: "2023 - Present", + startDate: "Jan 2023", + endDate: "Present", + company: "PT Hafdzamedia Teknologi Aplikasi", + position: "Fullstack Web Developer", + type: "Part-time", + description: + "Responsible for building and maintaining scalable web applications using the latest technologies. Collaborated with cross-functional teams to deliver high-quality solutions that meet business requirements.", + skills: ["React", "Next.js", "Laravel", "MySql"], + }, + { + id: 2, + period: "2024 - Present", + startDate: "Mar 2024", + endDate: "Present", + company: "Outlier", + position: "AI Trainer", + type: "Freelance", + description: + "Crafted and refined chatbot prompts using prompt engineering and RLHF techniques to improve the AI responses in English and Indonesian, leading to more accurate and contextually relevant interactions.", + skills: ["Generative AI", "Prompt Engineering", "Problem Solving"], + }, + { + id: 3, + period: "2023 - Present", + startDate: "Jun 2023", + endDate: "Present", + company: "Self-Employed", + position: "Web Developer", + type: "Freelance", + description: + "Specialized in crafting innovative solutions using Laravel and Next.js, enhancing client satisfaction and project efficiency. Contributed expertise in both frontend and backend development during collaborative team projects.", + skills: ["React", "Next.js", "Laravel", "MySql"], + }, + { + id: 4, + period: "2022 - 2023", + startDate: "Sep 2022", + endDate: "Aug 2023", + company: "TechStart Solutions", + position: "Frontend Developer", + type: "Internship", + description: + "Developed responsive user interfaces using React and TypeScript. Worked closely with UX designers to implement pixel-perfect designs and improved website performance by 40%.", + skills: ["React", "TypeScript", "CSS3", "Figma"], + }, + { + id: 5, + period: "2021 - 2022", + startDate: "Oct 2021", + endDate: "Sep 2022", + company: "Digital Innovation Labs", + position: "Junior Developer", + type: "Contract", + description: + "Built REST APIs using Node.js and Express. Implemented database schemas and optimized queries for better performance. Participated in code reviews and agile development processes.", + skills: ["Node.js", "Express", "MongoDB", "Git"], + }, +]; function Title() { return ( @@ -18,7 +83,6 @@ function Title() { }} transition={{ delay: 0.7, - type: "spring", }}> Profesional Experience @@ -28,135 +92,194 @@ function Title() { ); } +function TimelineCard({ experience, index, isEven }) { + return ( + +
+
+
+
{experience.startDate}
+
Start
+
+
+
+
{experience.endDate}
+
End
+
+
+
+
+ {experience.type} +
+
Type
+
+
+
+
+ ); +} + +function ExperienceCard({ experience, index, isEven }) { + return ( + + {" "} + {/* Card */} +
+ {/* Company & Position */} +
+

+ {experience.company} +

+

+ {experience.position} + + • {experience.type} + +

+
+ + {/* Description */} +

+ {experience.description} +

+ + {/* Skills */} +
+ {experience.skills.map((skill, idx) => ( + + {skill} + + ))} +
+
+
+ ); +} + function Wrapper({ children }) { return ( -
- -
-
- {children} -
-
-
+
+
+ {children} +
); } export default function Experience() { + const [showAll, setShowAll] = useState(false); + const displayedExperiences = showAll ? experiences : experiences.slice(0, 3); + return ( <> - + <Title /> <Wrapper> - <div className="grid gap-4 relative"> - <div className="aspect-square w-5 bg-black rounded-full absolute left-0 translate-x-[-29.5px] z-10" /> - <div className="font-medium text-lg">2023 - Present</div> - <div> - <h3 className="font-semibold text-xl text-black"> - PT Hafdzamedia Teknologi Aplikasi - </h3> - <h4 className=" font-light text-md mb-4"> - Fullstack Web Developer | Part-time - </h4> - <p className="text-justify"> - Responsible for building and maintaining scalable - web applications using the latest technologies. - Collaborated with cross-functional teams to deliver - high-quality solutions that meet business - requirements. - </p> - <div className="flex flex-wrap gap-2 mt-4 text-sm"> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - React - </div> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - Next.js - </div> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - Laravel - </div> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - MySql - </div> - </div> - </div> - </div> - <div className="grid gap-4 relative"> - <div className="aspect-square w-5 bg-black rounded-full absolute left-0 translate-x-[-29.5px] z-10" /> - <div className="font-medium text-lg">2024 - Present</div> - <div> - <h3 className="font-semibold text-xl text-black"> - Outlier - </h3> - <h4 className=" font-light text-md mb-4"> - AI Trainer | Freelance - </h4> - <p className="text-justify"> - Crafted and refined chatbot prompts using prompt - engineering and RLHF techniques to improve the AI - responses in English and Indonesian, leading to more - accurate and contextually relevant interactions. - </p> + {" "} + <div className="relative w-full max-w-6xl mx-auto"> + {" "} + {/* Timeline line - hidden on mobile, visible on md+ */} + <div className="hidden md:block absolute left-1/2 transform -translate-x-1/2 w-1 bg-gradient-to-b from-black via-gray-400 to-transparent h-full"></div> + {/* Mobile timeline line */} + <div className="md:hidden absolute left-0 w-1 bg-gradient-to-b from-black via-gray-400 to-transparent h-full"></div>{" "} + {/* Experience cards */} + <div className="space-y-12 md:space-y-16 relative"> + <AnimatePresence> + {displayedExperiences.map((experience, index) => ( + <div key={experience.id} className="relative"> + {/* Timeline period card - flows naturally above content */} + <TimelineCard + experience={experience} + index={index} + isEven={index % 2 === 1} + /> - <div className="flex flex-wrap gap-2 mt-4 text-sm"> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - Generative AI - </div> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - Prompt Engineering - </div> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - Problem Solving - </div> - </div> - </div> - </div> - <div className="grid gap-4 relative"> - <div className="aspect-square w-5 bg-black rounded-full absolute left-0 translate-x-[-29.5px] z-10" /> - <div className="font-medium text-lg">2023 - Present</div> - <div> - <h3 className="font-semibold text-xl text-black"> - Self-Employed - </h3> - <h4 className=" font-light text-md mb-4"> - Web Developer | Freelance - </h4> - <p className="text-justify"> - Specialized in crafting innovative solutions using - Laravel and Next.js, enhancing client satisfaction - and project efficiency. Contributed expertise in - both frontend and backend development during - collaborative team projects, leading to successful - project completions - </p> - <div className="flex flex-wrap gap-2 mt-4 text-sm"> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - React - </div> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - Next.js - </div> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - Laravel - </div> - <div className="bg-gray-300 text-black px-2 py-1 rounded-2xl"> - MySql - </div> - </div> + {/* Timeline dot - positioned at the start of the experience card */} + <div + className={`absolute w-6 h-6 bg-black rounded-full border-4 border-white shadow-lg z-30 + md:left-1/2 md:-translate-x-1/2 md:top-4 + left-0 -translate-x-1/2 top-2`} + /> + + {/* Experience content card */} + <ExperienceCard + experience={experience} + index={index} + isEven={index % 2 === 1} + /> + </div> + ))} + </AnimatePresence> </div> + {/* Expand/Collapse button */} + {experiences.length > 3 && ( + <motion.div + className="flex justify-center mt-12" + initial={{ opacity: 0 }} + whileInView={{ opacity: 1 }} + transition={{ delay: 0.5 }}> + <button + onClick={() => setShowAll(!showAll)} + className="bg-black hover:bg-gray-800 text-white px-8 py-3 rounded-full font-medium + transition-all duration-300 hover:scale-105 shadow-lg flex items-center gap-2"> + {showAll ? ( + <> + Show Less + <svg + className="w-4 h-4 transform rotate-180" + fill="none" + stroke="currentColor" + viewBox="0 0 24 24"> + <path + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth={2} + d="M19 9l-7 7-7-7" + /> + </svg> + </> + ) : ( + <> + View More Experience + <svg + className="w-4 h-4" + fill="none" + stroke="currentColor" + viewBox="0 0 24 24"> + <path + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth={2} + d="M19 9l-7 7-7-7" + /> + </svg> + </> + )} + </button> + </motion.div> + )}{" "} + {/* Gradient fade effect at bottom */} + {!showAll && ( + <div className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-stale-300 to-transparent pointer-events-none"></div> + )} </div> </Wrapper> </>