diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx index 1f357a0..b902569 100644 --- a/apps/admin/src/App.tsx +++ b/apps/admin/src/App.tsx @@ -8,6 +8,7 @@ import ProductCmsPage from './pages/ProductCmsPage' import CategoriesPage from './pages/CategoriesPage' import CustomersPage from './pages/CustomersPage' import PageBuilderPage from './pages/PageBuilderPage' +import CmsMockupPage from './pages/CmsMockupPage' import RawMaterialsPage from './pages/RawMaterialsPage' import LoginPage from './pages/LoginPage' import ProtectedRoute from './components/ProtectedRoute' @@ -64,6 +65,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/apps/admin/src/components/Sidebar.tsx b/apps/admin/src/components/Sidebar.tsx index 1783b1e..fc999f9 100644 --- a/apps/admin/src/components/Sidebar.tsx +++ b/apps/admin/src/components/Sidebar.tsx @@ -26,6 +26,7 @@ import { FileText, Wrench, ChevronDown, + Sparkles, } from 'lucide-react' const MAIN_MENU = [ @@ -39,6 +40,7 @@ const MAIN_MENU = [ { icon: Ticket, label: 'Promotions', to: '/promotions' }, { icon: Ticket, label: 'Coupons', to: '/coupons' }, { icon: Layers, label: 'Page Builder (CMS)', to: '/page-builder' }, + { icon: Sparkles, label: 'CMS Block Showcase', to: '/cms-mockup' }, { icon: ImageIcon, label: 'Media Library', to: '/media' }, { icon: BookOpen, label: 'Scent Guide', to: '/articles' }, { icon: TrendingUp, label: 'TikTok Data', to: '/tiktok' }, diff --git a/apps/admin/src/components/page-builder/EditorialTileForm.tsx b/apps/admin/src/components/page-builder/EditorialTileForm.tsx index 72d4f5e..c3b798e 100644 --- a/apps/admin/src/components/page-builder/EditorialTileForm.tsx +++ b/apps/admin/src/components/page-builder/EditorialTileForm.tsx @@ -78,6 +78,24 @@ export default function EditorialTileForm({ +
+ + +
React.ReactNode; +} + +export default function CmsMockupPage() { + const [activeCategory, setActiveCategory] = useState('all'); + const [copiedType, setCopiedType] = useState(null); + const [showJsonFor, setShowJsonFor] = useState(null); + + const copyPayload = (type: string, payload: any) => { + navigator.clipboard.writeText(JSON.stringify(payload, null, 2)); + setCopiedType(type); + setTimeout(() => setCopiedType(null), 2000); + }; + + const mockBlocks: MockBlock[] = [ + // 1. ANNOUNCEMENT_BAR + { + type: 'ANNOUNCEMENT_BAR', + category: 'hero', + description: 'Top notice bar with custom styling and scrolling announcements.', + payload: { + slides: ['[FREE SHIPPING] on orders over ฿1,500', 'New Premium Collection is out now!'], + backgroundColor: '#1F3D2D', + textColor: '#FDFDF9', + speed: 3000 + }, + renderPreview: () => ( +
+ ✨ [FREE SHIPPING] on orders over ฿1,500 | New Premium Collection is out now! +
+ ) + }, + // 2. HERO + { + type: 'HERO', + category: 'hero', + description: 'Premium full-screen cover with headline, background image, and CTA.', + payload: { + tagline: 'Vibrant Elegance', + title: 'New Scent [Sanctuary]', + description: 'Transform your room into a peaceful escape.', + imageUrl: 'https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?auto=format&fit=crop&w=800&q=80', + buttonText: 'Explore Collection', + buttonLink: '/shop' + }, + renderPreview: () => ( +
+
+ Vibrant Elegance +

New Scent [Sanctuary]

+

Transform your room into a peaceful escape.

+ +
+
+ ) + }, + // 3. HERO_V2 + { + type: 'HERO_V2', + category: 'hero', + description: 'Split Layout Hero featuring text on one side and a premium image card on the other.', + payload: { + tagline: 'Pure Wellness', + title: 'The Organic [Reed Diffuser]', + description: 'Crafted with hand-harvested lavender and sweet orange essential oils.', + imageUrl: 'https://images.unsplash.com/photo-1547887537-6158d64c35b3?auto=format&fit=crop&w=800&q=80', + buttonText: 'Shop Diffusers', + buttonLink: '/shop/diffusers', + imagePosition: 'right' + }, + renderPreview: () => ( +
+
+ Pure Wellness +

The Organic [Reed Diffuser]

+

Crafted with hand-harvested lavender and sweet orange essential oils.

+ +
+
+ mock +
+
+ ) + }, + // 4. USP_BAR + { + type: 'USP_BAR', + category: 'hero', + description: 'Unique Selling Proposition Bar highlighting product perks (Icons & Labels).', + payload: { + items: [ + { icon: 'ShieldCheck', title: '100% Organic', desc: 'Certificated natural oils' }, + { icon: 'Truck', title: 'Free Shipping', desc: 'On orders above ฿1,500' }, + { icon: 'RotateCcw', title: 'Easy Returns', desc: '30-day money back guarantee' } + ] + }, + renderPreview: () => ( +
+
+
+
100% Organic
+
Certificated natural oils
+
+
+
+
Free Shipping
+
On orders above ฿1,500
+
+
+
+
Easy Returns
+
30-day money back guarantee
+
+
+ ) + }, + // 5. BANNER + { + type: 'BANNER', + category: 'hero', + description: 'Clickable promotional block with headline overlay, ideal for campaigns.', + payload: { + imageUrl: 'https://images.unsplash.com/photo-1540555700478-4be289fbecef?auto=format&fit=crop&w=800&q=80', + linkUrl: '/promotions', + title: 'Mid-Year Secret Sale', + description: 'Get up to 30% off on premium scent reed diffusers.', + buttonText: 'Shop Sale' + }, + renderPreview: () => ( +
+
+

Mid-Year Secret Sale

+

Get up to 30% off on premium scent reed diffusers.

+ +
+
+ ) + }, + // 6. CATEGORIES + { + type: 'CATEGORIES', + category: 'products', + description: 'Visual category grid showcase to guide users to specific collections.', + payload: { + title: 'Shop by Collection', + subtitle: 'Choose your preferred sensory style', + categoryIds: [1, 2, 3] + }, + renderPreview: () => ( +
+
+

Shop by Collection

+

Choose your preferred sensory style

+
+
+ {[ + { name: 'Essential Oils', img: 'https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?auto=format&fit=crop&w=200&q=80' }, + { name: 'Reed Diffusers', img: 'https://images.unsplash.com/photo-1547887537-6158d64c35b3?auto=format&fit=crop&w=200&q=80' }, + { name: 'Room Sprays', img: 'https://images.unsplash.com/photo-1612817288484-6f916006741a?auto=format&fit=crop&w=200&q=80' } + ].map((cat, i) => ( +
+ {cat.name} +
+ {cat.name} +
+
+ ))} +
+
+ ) + }, + // 7. PRODUCTS + { + type: 'PRODUCTS', + category: 'products', + description: 'Showcase grid or carousel for featured products, brand items, or a customized list with Quick Shop option.', + payload: { + title: 'Our Best Sellers', + subtitle: 'Hand-picked organic favorites', + type: 'featured', + limit: 3, + layout: 'grid', + enableQuickShop: true + }, + renderPreview: () => ( +
+
+

Our Best Sellers

+

Hand-picked organic favorites

+
+
+ {[ + { name: 'Ozone Ambient Blend', price: '฿850', img: 'https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?auto=format&fit=crop&w=200&q=80' }, + { name: 'Lavender Sanctuary Diffuser', price: '฿1,290', img: 'https://images.unsplash.com/photo-1547887537-6158d64c35b3?auto=format&fit=crop&w=200&q=80' }, + { name: 'Citrus Zest Room Mist', price: '฿450', img: 'https://images.unsplash.com/photo-1612817288484-6f916006741a?auto=format&fit=crop&w=200&q=80' } + ].map((prod, i) => ( +
+
+
+ {prod.name} +
+
{prod.name}
+

{prod.price}

+
+ +
+ ))} +
+
+ ) + }, + // 8. PRODUCT_CAROUSEL + { + type: 'PRODUCT_CAROUSEL', + category: 'products', + description: 'Swipeable horizontal card carousel displaying specific product collections.', + payload: { + title: 'Featured Collection', + description: 'Selected scents for wellness journeys.', + collectionId: 'wellness-campaign', + limit: 5, + showPrice: true, + showAddToCart: true + }, + renderPreview: () => ( +
+
+
+

Featured Collection

+

Selected scents for wellness journeys

+
+ Slide next → +
+
+ {[ + { name: 'Ozone Ambient', price: '฿850', img: 'https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?auto=format&fit=crop&w=150&q=80' }, + { name: 'Lavender Sanctuary', price: '฿1,290', img: 'https://images.unsplash.com/photo-1547887537-6158d64c35b3?auto=format&fit=crop&w=150&q=80' }, + { name: 'Citrus Zest Mist', price: '฿450', img: 'https://images.unsplash.com/photo-1612817288484-6f916006741a?auto=format&fit=crop&w=150&q=80' }, + { name: 'Eucalyptus Mint', price: '฿890', img: 'https://images.unsplash.com/photo-1540555700478-4be289fbecef?auto=format&fit=crop&w=150&q=80' } + ].map((prod, i) => ( +
+
+
+ {prod.name} +
+
{prod.name}
+

{prod.price}

+
+
+ ))} +
+
+ ) + }, + // 9. RELATED_PRODUCTS + { + type: 'RELATED_PRODUCTS', + category: 'products', + description: 'Dynamic block recommending items from the same category for the product page layout.', + payload: { + title: 'You May Also Like', + showHeading: true, + showViewAllLink: true + }, + renderPreview: () => ( +
+
+

You May Also Like

+ View All +
+
+
+
+ mock +
+
+

Ozone Ambient Blend

+

฿850

+
+
+
+
+ mock +
+
+

Lavender sanctuary

+

฿1,290

+
+
+
+
+ ) + }, + // 10. EDITORIAL_TILE + { + type: 'EDITORIAL_TILE', + category: 'editorial', + description: 'Stunning editorial grid block presenting storytelling text alongside a curated campaign photo.', + payload: { + title: 'Moodboard [IRL]', + description: 'An expressive mix of texture, shape and movement. Deconstructed details, utilitarian elements and fluid tailoring come together in looks that feel personal.', + imageUrl: 'https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?auto=format&fit=crop&w=800&q=80', + imagePosition: 'left', + imageAspectRatio: 'tall', + buttonText: 'Shop the looks', + buttonLink: '/shop', + backgroundColor: '#1E293B', + textColor: '#FFFFFF' + }, + renderPreview: () => ( +
+
+ mock +
+
+

Crafted with [Sensory Passion]

+

+ Every formulation at HomAroma is a tribute to pure botanical essences, hand-blended to capture nature’s quiet intelligence. +

+ +
+
+ ) + }, + // 11. EDITORIAL_CAROUSEL + { + type: 'EDITORIAL_CAROUSEL', + category: 'editorial', + description: 'Stories and values slider displaying image slideshows side-by-side with localized editorial statements.', + payload: { + title: 'The Wellness Philosophy', + slides: [ + { + category: 'Heritage', + title: 'Rooted in Nature', + description: 'Our ingredients are sustainably sourced from organic growers who share our respect for ecological balance.', + imageUrl: 'https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?auto=format&fit=crop&w=400&q=80', + buttonText: 'Discover Sourcing', + buttonLink: '/sourcing' + } + ] + }, + renderPreview: () => ( +
+

The Wellness Philosophy

+
+
+ mock +
+
+ Heritage +
Rooted in Nature
+

Our ingredients are sustainably sourced from organic growers who share our respect for ecological balance.

+ +
+
+
+ ) + }, + // 12. SCENT_NOTES_BREAKDOWN + { + type: 'SCENT_NOTES_BREAKDOWN', + category: 'editorial', + description: 'Fragrance profile breakdowns containing Top, Heart/Middle, and Base notes lists.', + payload: { + title: 'Scent Signature Breakdown', + description: 'Detailed analysis of fragrance volatility levels.', + topNotes: ['Italian Bergamot', 'Citrus Peel'], + heartNotes: ['Jasmine Absolute', 'French Lavender'], + baseNotes: ['Sandalwood', 'Clean Musk'] + }, + renderPreview: () => ( +
+
+

Scent Signature Breakdown

+

Detailed analysis of fragrance volatility levels

+
+
+
+ Top +

Italian Bergamot, Citrus Peel

+
+
+ Heart +

Jasmine Absolute, French Lavender

+
+
+ Base +

Sandalwood, Clean Musk

+
+
+
+ ) + }, + // 13. QUOTE_CALLOUT + { + type: 'QUOTE_CALLOUT', + category: 'editorial', + description: 'Elegant text banner presenting callout quotations, ideal for customer testimonials.', + payload: { + quote: 'Fragrance is the invisible, unforgettable fashion accessory.', + author: 'Coco Chanel', + role: 'Fashion Designer' + }, + renderPreview: () => ( +
+ “ +

+ "Fragrance is the invisible, unforgettable fashion accessory." +

+
+ — Coco Chanel | Fashion Designer +
+
+ ) + }, + // 14. TEXT + { + type: 'TEXT', + category: 'editorial', + description: 'Plain text component for headers, centered paragraphs, or brand catchphrases.', + payload: { + title: 'Natural & Ecological Policy', + content: 'We use 100% natural, botanically derived ingredients in all formulations. Cruelty-free.', + textAlign: 'center' + }, + renderPreview: () => ( +
+

Natural & Ecological Policy

+

+ We use 100% natural, botanically derived ingredients in all formulations. Cruelty-free. +

+
+ ) + }, + // 15. RICH_TEXT + { + type: 'RICH_TEXT', + category: 'editorial', + description: 'Styled markdown block designed for longer stories or HTML embedding.', + payload: { + title: 'Corporate Social Responsibility', + content: '

Our commitments extend beyond scent creation. HomAroma proudly partners with rural farming families in Northern Thailand to source organic lemongrass oil.

  • Sustainable development goals
  • Fair-trade wages
' + }, + renderPreview: () => ( +
+

Corporate Social Responsibility

+
+

Our commitments extend beyond scent creation. HomAroma proudly partners with rural farming families in Northern Thailand to source organic lemongrass oil.

+
    +
  • Sustainable development goals
  • +
  • Fair-trade wages
  • +
+
+
+ ) + }, + // 16. HERO_ARTICLE + { + type: 'HERO_ARTICLE', + category: 'articles', + description: 'Editorial article header with background blur, date, author name, and title overlays.', + payload: { + title: 'Fragrance & Mindfulness Journey', + subtitle: 'How ambient scent transforms focus and breathing cycles.', + author: 'Dr. Aromatherapist', + category: 'Insights', + publishDate: '2026-07-27', + imageUrl: 'https://images.unsplash.com/photo-1547887537-6158d64c35b3?auto=format&fit=crop&w=600&q=80' + }, + renderPreview: () => ( +
+
+
+ Insights + • + 2026-07-27 +
+

Fragrance & Mindfulness Journey

+

How ambient scent transforms focus and breathing cycles.

+

Written by Dr. Aromatherapist

+
+
+ ) + }, + // 17. ARTICLE_HEADER + { + type: 'ARTICLE_HEADER', + category: 'articles', + description: 'Introductory page header for standard text articles.', + payload: { + title: 'The Chemistry of Volatility', + subtitle: 'Understanding high, middle, and low evaporation rates of pure aromatherapy elements.' + }, + renderPreview: () => ( +
+

The Chemistry of Volatility

+

Understanding high, middle, and low evaporation rates of pure aromatherapy elements.

+
+ ) + }, + // 18. ARTICLE_MEDIA + { + type: 'ARTICLE_MEDIA', + category: 'articles', + description: 'Article paragraph paired with a side image (left/right alignment).', + payload: { + heading: 'Sourcing Lavender Extract', + body: 'Our lavender is harvested at dawn in Provence when oil concentrations are highest, then steam-distilled locally within 4 hours.', + imagePosition: 'right', + imageUrl: 'https://images.unsplash.com/photo-1547887537-6158d64c35b3?auto=format&fit=crop&w=100&q=80' + }, + renderPreview: () => ( +
+
+
Sourcing Lavender Extract
+

+ Our lavender is harvested at dawn in Provence when oil concentrations are highest, then steam-distilled locally within 4 hours. +

+
+
+ mock +
+
+ ) + }, + // 19. ARTICLE_GALLERY + { + type: 'ARTICLE_GALLERY', + category: 'articles', + description: 'Photo collection grids to show off multiple illustrations or steps.', + payload: { + layout: 'grid', + images: [ + { imageUrl: 'https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?auto=format&fit=crop&w=200&q=80' }, + { imageUrl: 'https://images.unsplash.com/photo-1547887537-6158d64c35b3?auto=format&fit=crop&w=200&q=80' } + ] + }, + renderPreview: () => ( +
+
+ mock +
+
+ mock +
+
+ ) + }, + // 20. ARTICLE_FACTS + { + type: 'ARTICLE_FACTS', + category: 'articles', + description: 'Quick summaries or key takeaway bullets for editorial pieces.', + payload: { + title: 'Key Scent Benefits', + items: [ + { title: 'Relaxation', description: 'Proven scent to lower blood pressure and cortisol levels.' }, + { title: 'Focus Boost', description: 'Peppermint elements trigger sensory wakefulness.' } + ] + }, + renderPreview: () => ( +
+

+ Key Scent Benefits +

+
+
+ Relaxation: Proven scent to lower blood pressure and cortisol levels. +
+
+ Focus Boost: Peppermint elements trigger sensory wakefulness. +
+
+
+ ) + }, + // 21. ARTICLE_VIDEO + { + type: 'ARTICLE_VIDEO', + category: 'articles', + description: 'Embedded video player wrapper for articles.', + payload: { + videoUrl: 'https://www.youtube.com/watch?v=mock', + aspectRatio: '16:9' + }, + renderPreview: () => ( +
+
+
+ +
+ Watch Youtube Video Preview +
+ ) + }, + // 22. ARTICLE_REFERENCES + { + type: 'ARTICLE_REFERENCES', + category: 'articles', + description: 'Citation list or scientific reference cards.', + payload: { + title: 'Study Sources', + items: [ + { title: 'Journal of Aromatherapy Research (Vol. 12)', link: 'https://example.com' }, + { title: 'Clinical Scent Testing & Sleep Induction (2024)', link: 'https://example.com' } + ] + }, + renderPreview: () => ( + + ) + }, + // 23. ARTICLE_TABLE + { + type: 'ARTICLE_TABLE', + category: 'articles', + description: 'Comparison table displaying technical parameters or specs.', + payload: { + title: 'Volatility Comparison', + columns: ['Fragrance Type', 'Top Note Evaporation', 'Average Life'], + rows: [ + { label: 'Essential Oil', cells: ['High (Fast)', '4-6 hours'] }, + { label: 'Carrier Blend', cells: ['Low (Slow)', '24-48 hours'] } + ] + }, + renderPreview: () => ( +
+ + + + + + + + + + + + + + + + + + + + +
Fragrance TypeTop Note EvaporationAverage Life
Essential OilHigh (Fast)4-6 hours
Carrier BlendLow (Slow)24-48 hours
+
+ ) + }, + // 24. ARTICLE_DOWNLOAD + { + type: 'ARTICLE_DOWNLOAD', + category: 'articles', + description: 'File attachments download cards (PDF guides, catalogues).', + payload: { + title: 'Available Resources', + files: [ + { title: 'Organic Certification PDF', fileType: 'PDF', url: '#' }, + { title: 'Aromatherapy Scent Catalogue', fileType: 'PDF', url: '#' } + ] + }, + renderPreview: () => ( +
+
Available Resources
+
+ {[ + { title: 'Organic Certification PDF', size: '2.4 MB' }, + { title: 'Scent Catalogue', size: '4.8 MB' } + ].map((f, i) => ( +
+
+

{f.title}

+

{f.size} • PDF

+
+ +
+ ))} +
+
+ ) + }, + // 25. ARTICLE_CTA + { + type: 'ARTICLE_CTA', + category: 'articles', + description: 'Call-to-action blocks prompting next-step user clicks.', + payload: { + title: 'Ready to start your wellness journey?', + primaryText: 'Explore Collection', + primaryLink: '/shop', + alignment: 'center' + }, + renderPreview: () => ( +
+

Ready to start your wellness journey?

+ +
+ ) + }, + // 26. NEWSLETTER + { + type: 'NEWSLETTER', + category: 'forms', + description: 'Email subscription block to attract newsletter sign-ups.', + payload: { + title: 'Begin Your Wellness Journey', + description: 'Subscribe to receive 10% off your first purchase and exclusive updates.', + buttonText: 'Subscribe', + placeholderText: 'Enter your email address', + showPrivacyCheckbox: true, + privacyText: 'I agree to the privacy policy.' + }, + renderPreview: () => ( +
+
+

Begin Your Wellness Journey

+

Subscribe to receive 10% off your first purchase and exclusive updates.

+
+
+ + +
+
+ + I agree to the privacy policy. +
+
+ ) + }, + // 27. ACCORDION + { + type: 'ACCORDION', + category: 'forms', + description: 'Question-answer accordions for FAQs, divided by custom categories.', + payload: { + title: 'Frequently Asked Questions', + categories: [ + { + name: 'General Policy', + icon: 'HelpCircle', + questions: [ + { q: 'How long do Reed Diffusers last?', a: 'Normally, they evaporate gradually over 4 to 6 weeks depending on temperature levels.' }, + { q: 'Are essential oils safe for pets?', a: 'Some oils are toxic to dogs/cats. Please consult a veterinarian before diffusing.' } + ] + } + ] + }, + renderPreview: () => ( +
+

+ Frequently Asked Questions (General Policy) +

+
+ {[ + { q: 'How long do Reed Diffusers last?', a: 'Normally, they evaporate gradually over 4 to 6 weeks depending on temperature levels.' }, + { q: 'Are essential oils safe for pets?', a: 'Some oils are toxic to dogs/cats. Please consult a veterinarian before diffusing.' } + ].map((faq, i) => ( +
+
+ {faq.q} + +
+

{faq.a}

+
+ ))} +
+
+ ) + }, + // 28. CONTACT_INFO + { + type: 'CONTACT_INFO', + category: 'forms', + description: 'Beautiful information box showing address, telephone, and social details.', + payload: { + title: 'Contact Details', + address: '123 Sukhumvit Rd, Bangkok, Thailand', + email: 'hello@homaroma.com', + line: '@homaroma', + hours: 'Mon-Fri: 9AM - 6PM' + }, + renderPreview: () => ( +
+

Contact Details

+
+
+ + 123 Sukhumvit Rd, Bangkok +
+
+ + hello@homaroma.com +
+
+ + Line: @homaroma +
+
+ + Mon-Fri: 9AM - 6PM +
+
+
+ ) + }, + // 29. CONTACT_FORM + { + type: 'CONTACT_FORM', + category: 'forms', + description: 'General support email inquiry contact form.', + payload: { + title: 'Get In Touch', + subtitle: 'We would love to hear from you.' + }, + renderPreview: () => ( +
+
+

Get In Touch

+

We would love to hear from you.

+
+
+ + + + +
+
+ ) + } + ]; + + const filteredBlocks = activeCategory === 'all' + ? mockBlocks + : mockBlocks.filter(b => b.category === activeCategory); + + return ( +
+ {/* Page Header */} +
+
+

+ + CMS Blocks Showcase +

+

+ Visual preview and template schema payloads for all available Page Builder blocks. +

+
+
+ + {/* Categories Filter Tabs */} +
+ {CATEGORIES.map((cat) => ( + + ))} +
+ + {/* Blocks Listing Grid */} +
+ {filteredBlocks.map((block) => ( +
+ {/* Block Header Info */} +
+
+
+ + {block.type} + + + {block.category} + +
+

{block.description}

+
+ + {/* Action buttons */} +
+ + +
+
+ + {/* JSON Code view */} + {showJsonFor === block.type && ( +
+
{JSON.stringify(block.payload, null, 2)}
+
+ )} + + {/* Live Visual Preview Frame */} +
+
+ {block.renderPreview()} +
+
+
+ ))} +
+
+ ); +} diff --git a/apps/admin/src/pages/PageBuilderPage.tsx b/apps/admin/src/pages/PageBuilderPage.tsx index 3283a6a..fce1033 100644 --- a/apps/admin/src/pages/PageBuilderPage.tsx +++ b/apps/admin/src/pages/PageBuilderPage.tsx @@ -13,7 +13,9 @@ import { X, Settings2, CheckCircle2, - ExternalLink + ExternalLink, + Copy, + Check } from 'lucide-react'; import { API_BASE, type Page, type PageSection, type Category } from '@homaroma/types'; import { getAdminDisplayString } from '../utils/localization'; @@ -84,6 +86,7 @@ export default function PageBuilderPage() { const [uploadTarget, setUploadTarget] = useState<{ type: 'main' | 'slide' | 'gallery'; index?: number } | null>(null); const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle'); const [isSavingPageSettings, setIsSavingPageSettings] = useState(false); + const [isCopied, setIsCopied] = useState(false); const [isReorderModalOpen, setIsReorderModalOpen] = useState(false); const [isMediaModalOpen, setIsMediaModalOpen] = useState(false); @@ -943,6 +946,35 @@ export default function PageBuilderPage() { />
+
+ +
+ (e.target as HTMLInputElement).select()} + /> + +
+
+