8 Commits

Author SHA1 Message Date
7d9b42af4a Merge version_4_1782129763092 into main
Merge version_4_1782129763092 into main
2026-06-22 12:04:18 +00:00
kudinDmitriyUp
7f89e181ad Bob AI: Added volume selector to product cards in shop section 2026-06-22 12:03:33 +00:00
28de81dcec Merge version_3_1782129265911 into main
Merge version_3_1782129265911 into main
2026-06-22 11:57:02 +00:00
kudinDmitriyUp
1ddb677ab1 Bob AI: Populate src/pages/InventoryPage.tsx (snippet builder, 1 sections) 2026-06-22 11:56:26 +00:00
kudinDmitriyUp
47ef186991 Bob AI: Add inventory page 2026-06-22 11:55:21 +00:00
b5cb235306 Merge version_2_1782128502374 into main
Merge version_2_1782128502374 into main
2026-06-22 11:45:23 +00:00
kudinDmitriyUp
e367a99a38 Bob AI: Hid the About and Testimonial sections. 2026-06-22 11:44:41 +00:00
1f37230e95 Merge version_1_1782126683224 into main
Merge version_1_1782126683224 into main
2026-06-22 11:12:30 +00:00
12 changed files with 445 additions and 200 deletions

View File

@@ -2,11 +2,13 @@ import { Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import HomePage from './pages/HomePage';
import InventoryPage from "@/pages/InventoryPage";
export default function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<HomePage />} />
<Route path="/inventory" element={<InventoryPage />} />
</Route>
</Routes>
);

View File

@@ -27,7 +27,9 @@ export default function Layout() {
},
{
"name": "Contact", "href": "#contact"
}
},
{ name: "Inventory", href: "/inventory" },
];
return (

View File

@@ -1,210 +1,33 @@
import AboutTestimonial from '@/components/sections/about/AboutTestimonial';
import ContactCta from '@/components/sections/contact/ContactCta';
import FaqSimple from '@/components/sections/faq/FaqSimple';
import FeaturesRevealCardsBento from '@/components/sections/features/FeaturesRevealCardsBento';
import HeroCenteredLogos from '@/components/sections/hero/HeroCenteredLogos';
import MetricsSimpleCards from '@/components/sections/metrics/MetricsSimpleCards';
import TestimonialTrustCard from '@/components/sections/testimonial/TestimonialTrustCard';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
// AUTO-GENERATED shell by per-section-migrate.
// Section bodies live in ./<PageBase>/sections/<X>.tsx. Edit the section
// files directly. Non-block content (wrappers, non-inlinable sections) is
// preserved inline; extracted section blocks become <XSection/> refs.
export default function HomePage() {
import React from 'react';
import HomeSection from './HomePage/sections/Home';
import AboutSection from './HomePage/sections/About';
import ShopSection from './HomePage/sections/Shop';
import MetricsSection from './HomePage/sections/Metrics';
import TestimonialSection from './HomePage/sections/Testimonial';
import FaqSection from './HomePage/sections/Faq';
import ContactSection from './HomePage/sections/Contact';
export default function HomePage(): React.JSX.Element {
return (
<>
<div id="home" data-section="home">
<SectionErrorBoundary name="home">
<HeroCenteredLogos
avatarsSrc={[
"http://img.b2bpic.net/free-photo/young-caucasian-model-with-long-dark-hair-poses-camera-with-hand-near-her-face_132075-10061.jpg",
"http://img.b2bpic.net/free-photo/portrait-blonde-woman-with-glass-water-standing-yellow_114579-81442.jpg",
"http://img.b2bpic.net/free-photo/close-up-woman-holding-globe-fir-tree_23-2148332703.jpg",
"http://img.b2bpic.net/free-photo/young-woman-holding-pinecone-with-star-red-wall_114579-55960.jpg",
]}
avatarText="Loved by 10,000+ fragrance enthusiasts"
title="Experience Luxury, Simplified."
description="Authentic fragrance decants from world-class perfume houses delivered to your door."
primaryButton={{
text: "Browse Collection",
href: "#shop",
}}
secondaryButton={{
text: "Learn More",
href: "#about",
}}
names={[
"JPG",
"Dior",
"YSL",
"Creed",
"Tom Ford",
"Armani",
]}
imageSrc="http://img.b2bpic.net/free-photo/luxury-black-perfume-bottle-with-gold-cap-dark-textured-background_84443-84117.jpg"
/>
</SectionErrorBoundary>
</div>
<>
<HomeSection />
<div id="about" data-section="about">
<SectionErrorBoundary name="about">
<AboutTestimonial
tag="Our Story"
quote="We believe everyone should have access to the finest scents in the world, without the commitment of a full bottle. Our decants ensure you find your perfect fragrance signature."
author="The FragranceLab Team"
role="Curators of Fine Scent"
imageSrc="http://img.b2bpic.net/free-photo/laboratory-glassware-assortment-with-red-liquids_23-2149481715.jpg"
/>
</SectionErrorBoundary>
</div>
<AboutSection />
<div id="shop" data-section="shop">
<SectionErrorBoundary name="shop">
<FeaturesRevealCardsBento
tag="Fragrance Catalog"
title="Exquisite Fragrance Collection"
description="Select from our curated list of elite scents available in 5ml decants or full bottles."
items={[
{
title: "JPG Le Male Elixir",
description: "Intense, honeyed tobacco and vanilla.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/luxury-perfume-fragrance-bottle_116380-80.jpg",
},
{
title: "JPG Ultra Male",
description: "The ultimate clubbing fragrance.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/top-view-male-self-care-product_23-2150347093.jpg",
},
{
title: "JPG Le Male Le Parfum",
description: "Elegant lavender meets cardamom.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/elegant-vegan-alcohol-arrangement_23-2149337695.jpg",
},
{
title: "Dior Sauvage",
description: "Timeless, fresh, and masculine.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/perfume-bottle-natural-background-tree-bark-flowers-stones-top-view-beauty-fashion-perfume-template_166373-1585.jpg",
},
{
title: "Azzaro The Most Wanted",
description: "Spicy, sweet, and charismatic.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/top-view-male-self-care-items_23-2150347141.jpg",
},
{
title: "YSL Y EDP",
description: "Crisp apple and sage sophistication.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/bottle-serum-based-soil-ingredients_23-2150756823.jpg",
},
{
title: "Creed Aventus",
description: "The king of modern niche scents.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/single-glass-bottle-filled-with-clear-liquid-generated-by-ai_188544-19684.jpg",
},
]}
/>
</SectionErrorBoundary>
</div>
<ShopSection />
<div id="metrics" data-section="metrics">
<SectionErrorBoundary name="metrics">
<MetricsSimpleCards
tag="Quality Assured"
title="The Decant Difference"
description="Why fragrance lovers trust us with their collection building."
metrics={[
{
value: "100%",
description: "Authentic Fragrances",
},
{
value: "24h",
description: "Swift Processing",
},
{
value: "5ml+",
description: "Flexible Sizes",
},
]}
/>
</SectionErrorBoundary>
</div>
<MetricsSection />
<div id="testimonial" data-section="testimonial">
<SectionErrorBoundary name="testimonial">
<TestimonialTrustCard
quote="FragranceLab changed the way I sample. I found my signature scent through their 5ml decants. Highly recommend for any fragrance enthusiast!"
rating={5}
author="Marcus R., Verified Buyer"
avatars={[
{
name: "Sarah",
imageSrc: "http://img.b2bpic.net/free-photo/medium-shot-young-woman-with-curly-hair_23-2151317375.jpg",
},
{
name: "John",
imageSrc: "http://img.b2bpic.net/free-photo/medium-shot-woman-sitting-table_23-2149708133.jpg",
},
{
name: "Emily",
imageSrc: "http://img.b2bpic.net/free-photo/cheerful-young-caucasian-woman-applies-cream-her-hands-while-sitting-table-light-room-brunette-girl-with-smooth-skin-wears-shirt-wellness-self-care-concept_197531-32266.jpg",
},
{
name: "David",
imageSrc: "http://img.b2bpic.net/free-photo/person-enjoying-berry-snack-outdoors_52683-107522.jpg",
},
{
name: "Anna",
imageSrc: "http://img.b2bpic.net/free-photo/attractive-woman-posing-with-dry-leaf-blue-background_197531-28551.jpg",
},
]}
/>
</SectionErrorBoundary>
</div>
<TestimonialSection />
<div id="faq" data-section="faq">
<SectionErrorBoundary name="faq">
<FaqSimple
tag="Support"
title="Common Questions"
description="Everything you need to know about our decant process and shipping."
items={[
{
question: "Are these authentic perfumes?",
answer: "Yes, all our decants are 100% original fragrances sourced from authorized retailers.",
},
{
question: "How do you package the decants?",
answer: "We use high-quality glass atomizers with Teflon sealing to prevent evaporation.",
},
{
question: "Do you offer international shipping?",
answer: "Yes, we ship globally using secure packaging.",
},
]}
/>
</SectionErrorBoundary>
</div>
<FaqSection />
<div id="contact" data-section="contact">
<SectionErrorBoundary name="contact">
<ContactCta
tag="Ready to Sample?"
text="Start your journey today. Browse our full list of fragrances and pick your samples."
primaryButton={{
text: "Shop Now",
href: "#shop",
}}
secondaryButton={{
text: "Contact Support",
href: "mailto:support@fragrancelab.com",
}}
/>
</SectionErrorBoundary>
</div>
<ContactSection />
</>
);
}

View File

@@ -0,0 +1,24 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "about" section.
import React from 'react';
import AboutTestimonial from '@/components/sections/about/AboutTestimonial';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function AboutSection(): React.JSX.Element {
return (
<div id="about" data-section="about">
<div className="hidden">
<SectionErrorBoundary name="about">
<AboutTestimonial
tag="Our Story"
quote="We believe everyone should have access to the finest scents in the world, without the commitment of a full bottle. Our decants ensure you find your perfect fragrance signature."
author="The FragranceLab Team"
role="Curators of Fine Scent"
imageSrc="http://img.b2bpic.net/free-photo/laboratory-glassware-assortment-with-red-liquids_23-2149481715.jpg"
/>
</SectionErrorBoundary>
</div>
</div>
);
}

View File

@@ -0,0 +1,27 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "contact" section.
import React from 'react';
import ContactCta from '@/components/sections/contact/ContactCta';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function ContactSection(): React.JSX.Element {
return (
<div id="contact" data-section="contact">
<SectionErrorBoundary name="contact">
<ContactCta
tag="Ready to Sample?"
text="Start your journey today. Browse our full list of fragrances and pick your samples."
primaryButton={{
text: "Shop Now",
href: "#shop",
}}
secondaryButton={{
text: "Contact Support",
href: "mailto:support@fragrancelab.com",
}}
/>
</SectionErrorBoundary>
</div>
);
}

View File

@@ -0,0 +1,34 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "faq" section.
import React from 'react';
import FaqSimple from '@/components/sections/faq/FaqSimple';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function FaqSection(): React.JSX.Element {
return (
<div id="faq" data-section="faq">
<SectionErrorBoundary name="faq">
<FaqSimple
tag="Support"
title="Common Questions"
description="Everything you need to know about our decant process and shipping."
items={[
{
question: "Are these authentic perfumes?",
answer: "Yes, all our decants are 100% original fragrances sourced from authorized retailers.",
},
{
question: "How do you package the decants?",
answer: "We use high-quality glass atomizers with Teflon sealing to prevent evaporation.",
},
{
question: "Do you offer international shipping?",
answer: "Yes, we ship globally using secure packaging.",
},
]}
/>
</SectionErrorBoundary>
</div>
);
}

View File

@@ -0,0 +1,43 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "home" section.
import React from 'react';
import HeroCenteredLogos from '@/components/sections/hero/HeroCenteredLogos';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function HomeSection(): React.JSX.Element {
return (
<div id="home" data-section="home">
<SectionErrorBoundary name="home">
<HeroCenteredLogos
avatarsSrc={[
"http://img.b2bpic.net/free-photo/young-caucasian-model-with-long-dark-hair-poses-camera-with-hand-near-her-face_132075-10061.jpg",
"http://img.b2bpic.net/free-photo/portrait-blonde-woman-with-glass-water-standing-yellow_114579-81442.jpg",
"http://img.b2bpic.net/free-photo/close-up-woman-holding-globe-fir-tree_23-2148332703.jpg",
"http://img.b2bpic.net/free-photo/young-woman-holding-pinecone-with-star-red-wall_114579-55960.jpg",
]}
avatarText="Loved by 10,000+ fragrance enthusiasts"
title="Experience Luxury, Simplified."
description="Authentic fragrance decants from world-class perfume houses delivered to your door."
primaryButton={{
text: "Browse Collection",
href: "#shop",
}}
secondaryButton={{
text: "Learn More",
href: "#about",
}}
names={[
"JPG",
"Dior",
"YSL",
"Creed",
"Tom Ford",
"Armani",
]}
imageSrc="http://img.b2bpic.net/free-photo/luxury-black-perfume-bottle-with-gold-cap-dark-textured-background_84443-84117.jpg"
/>
</SectionErrorBoundary>
</div>
);
}

View File

@@ -0,0 +1,34 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "metrics" section.
import React from 'react';
import MetricsSimpleCards from '@/components/sections/metrics/MetricsSimpleCards';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function MetricsSection(): React.JSX.Element {
return (
<div id="metrics" data-section="metrics">
<SectionErrorBoundary name="metrics">
<MetricsSimpleCards
tag="Quality Assured"
title="The Decant Difference"
description="Why fragrance lovers trust us with their collection building."
metrics={[
{
value: "100%",
description: "Authentic Fragrances",
},
{
value: "24h",
description: "Swift Processing",
},
{
value: "5ml+",
description: "Flexible Sizes",
},
]}
/>
</SectionErrorBoundary>
</div>
);
}

View File

@@ -0,0 +1,196 @@
/* eslint-disable */
// @ts-nocheck — generated by catalog-eject; runtime-correct but TS strict-mode false-positives on inlined catalog body
import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import ScrollReveal from "@/components/ui/ScrollReveal";
import SelectorButton from "@/components/ui/SelectorButton";
import Modal from "@/components/ui/Modal";
import Input from "@/components/ui/Input";
import Textarea from "@/components/ui/Textarea";
import Label from "@/components/ui/Label";
import { useState } from "react";
import { cls } from "@/lib/utils";
const items = [
{
title: "JPG Le Male Elixir",
description: "Intense, honeyed tobacco and vanilla.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/luxury-perfume-fragrance-bottle_116380-80.jpg"
},
{
title: "JPG Ultra Male",
description: "The ultimate clubbing fragrance.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/top-view-male-self-care-product_23-2150347093.jpg"
},
{
title: "JPG Le Male Le Parfum",
description: "Elegant lavender meets cardamom.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/elegant-vegan-alcohol-arrangement_23-2149337695.jpg"
},
{
title: "Dior Sauvage",
description: "Timeless, fresh, and masculine.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/perfume-bottle-natural-background-tree-bark-flowers-stones-top-view-beauty-fashion-perfume-template_166373-1585.jpg"
},
{
title: "Azzaro The Most Wanted",
description: "Spicy, sweet, and charismatic.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/top-view-male-self-care-items_23-2150347141.jpg"
},
{
title: "YSL Y EDP",
description: "Crisp apple and sage sophistication.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/bottle-serum-based-soil-ingredients_23-2150756823.jpg"
},
{
title: "Creed Aventus",
description: "The king of modern niche scents.",
href: "#",
imageSrc: "http://img.b2bpic.net/free-photo/single-glass-bottle-filled-with-clear-liquid-generated-by-ai_188544-19684.jpg"
}
];
type FeatureItem = {
title: string;
description: string;
href: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesRevealCardsBentoProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: [FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem];
}
const ProductCard = ({ item, index, staggerDelays, gridClasses }: any) => {
const [volume, setVolume] = useState("5ml");
const volumeOptions = [
{ value: "5ml", label: "5ml" },
{ value: "10ml", label: "10ml" },
{ value: "30ml", label: "30ml" },
{ value: "full", label: "Full" },
];
return (
<ScrollReveal
variant="slide-up"
delay={staggerDelays[index]}
className={cls(
"card rounded-lg overflow-hidden flex flex-col group relative",
gridClasses[index]
)}
>
<div className="relative w-full aspect-video md:aspect-auto md:h-64 overflow-hidden">
<ImageOrVideo
imageSrc={item.imageSrc}
videoSrc={item.videoSrc}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
</div>
<div className="p-6 flex flex-col flex-grow gap-4">
<div>
<h3 className="text-xl font-semibold text-foreground mb-2">
{item.title}
</h3>
<p className="text-accent text-sm">
{item.description}
</p>
</div>
<div className="mt-auto flex flex-col gap-4">
<div className="flex flex-col gap-2">
<Label className="text-xs font-medium text-foreground">Select Volume</Label>
<SelectorButton
options={volumeOptions}
activeValue={volume}
onValueChange={setVolume}
className="w-full"
/>
</div>
<Button text={`Add to Cart`} variant="primary" className="w-full" />
</div>
</div>
</ScrollReveal>
);
};
const ShopInline = () => {
const gridClasses = [
"md:col-span-2",
"md:col-span-4",
"md:col-span-3",
"md:col-span-3",
"md:col-span-2",
"md:col-span-2",
"md:col-span-2",
];
const staggerDelays = [
0,
0.1,
0,
0.1,
0,
0.1,
0.2,
];
return (
<section aria-label="Features reveal cards bento section" className="py-20">
<div className="flex flex-col gap-8 md:gap-10">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<div className="px-3 py-1 mb-1 text-sm card rounded w-fit">
<p>{"Fragrance Catalog"}</p>
</div>
<TextAnimation
text={"Exquisite Fragrance Collection"}
variant="fade-blur"
gradientText={true}
tag="h2"
className="md:max-w-8/10 text-6xl 2xl:text-7xl leading-[1.15] font-semibold text-center text-balance"
/>
<TextAnimation
text={"Select from our curated list of elite scents available in 5ml decants or full bottles."}
variant="fade-blur"
gradientText={false}
tag="p"
className="md:max-w-7/10 text-lg md:text-xl leading-snug text-center text-balance"
/>
{(undefined || undefined) && (
<div className="flex flex-wrap justify-center gap-3 mt-2 md:mt-3">
{undefined && <Button text={undefined.text} href={undefined.href} variant="primary"/>}
{undefined && <Button text={undefined.text} href={undefined.href} variant="secondary" animationDelay={0.1} />}
</div>
)}
</div>
<div className="w-content-width mx-auto grid grid-cols-1 md:grid-cols-6 gap-3">
{items.map((item, index) => (
<ProductCard key={item.title} item={item} index={index} staggerDelays={staggerDelays} gridClasses={gridClasses} />
))}
</div>
</div>
</section>
);
};
export default function ShopSection() {
return (
<div data-webild-section="shop" id="shop">
<ShopInline />
</div>
);
}

View File

@@ -0,0 +1,44 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "testimonial" section.
import React from 'react';
import TestimonialTrustCard from '@/components/sections/testimonial/TestimonialTrustCard';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function TestimonialSection(): React.JSX.Element {
return (
<div id="testimonial" data-section="testimonial">
<div className="hidden">
<SectionErrorBoundary name="testimonial">
<TestimonialTrustCard
quote="FragranceLab changed the way I sample. I found my signature scent through their 5ml decants. Highly recommend for any fragrance enthusiast!"
rating={5}
author="Marcus R., Verified Buyer"
avatars={[
{
name: "Sarah",
imageSrc: "http://img.b2bpic.net/free-photo/medium-shot-young-woman-with-curly-hair_23-2151317375.jpg",
},
{
name: "John",
imageSrc: "http://img.b2bpic.net/free-photo/medium-shot-woman-sitting-table_23-2149708133.jpg",
},
{
name: "Emily",
imageSrc: "http://img.b2bpic.net/free-photo/cheerful-young-caucasian-woman-applies-cream-her-hands-while-sitting-table-light-room-brunette-girl-with-smooth-skin-wears-shirt-wellness-self-care-concept_197531-32266.jpg",
},
{
name: "David",
imageSrc: "http://img.b2bpic.net/free-photo/person-enjoying-berry-snack-outdoors_52683-107522.jpg",
},
{
name: "Anna",
imageSrc: "http://img.b2bpic.net/free-photo/attractive-woman-posing-with-dry-leaf-blue-background_197531-28551.jpg",
},
]}
/>
</SectionErrorBoundary>
</div>
</div>
);
}

View File

@@ -0,0 +1,15 @@
import { ArrowUpRight, Loader2 } from "lucide-react";
import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import GridOrCarousel from "@/components/ui/GridOrCarousel";
import ScrollReveal from "@/components/ui/ScrollReveal";
import useProducts from "@/hooks/useProducts";
export default function InventoryPage() {
return (
<>
<div data-webild-section="ProductVariantCards"><section aria-label="Products section" className="py-20"><div className="w-content-width mx-auto flex justify-center"><Loader2 className="size-8 animate-spin text-foreground" strokeWidth={1.5} /></div></section></div>
</>
);
}

View File

@@ -6,4 +6,5 @@ export interface Route {
export const routes: Route[] = [
{ path: '/', label: 'Home', pageFile: 'HomePage' },
{ path: '/inventory', label: 'Inventory', pageFile: 'InventoryPage' },
];