Frontend:
- Add comprehensive Puck component library (Layout, Content, Booking, Contact)
- Add Services component with usePublicServices hook integration
- Add 150+ icons to IconList component organized by category
- Add preview modal with viewport toggles (desktop/tablet/mobile)
- Add draft save/discard functionality with localStorage persistence
- Add draft status indicator in PageEditor toolbar
- Fix useSites hooks to use correct API URLs (/pages/{id}/)
Backend:
- Add SiteConfig model for theme, header, footer configuration
- Add Page SEO fields (meta_title, meta_description, og_image, etc.)
- Add puck_data validation for component structure
- Add create_missing_sites management command
- Fix PageViewSet to use EntitlementService for permissions
- Add comprehensive tests for site builder functionality
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
import React from 'react';
|
|
import type { ComponentConfig } from '@measured/puck';
|
|
import type { BusinessHoursProps } from '../../types';
|
|
import { Clock, CheckCircle, XCircle } from 'lucide-react';
|
|
|
|
const DEFAULT_HOURS = [
|
|
{ day: 'Monday', hours: '9:00 AM - 5:00 PM', isOpen: true },
|
|
{ day: 'Tuesday', hours: '9:00 AM - 5:00 PM', isOpen: true },
|
|
{ day: 'Wednesday', hours: '9:00 AM - 5:00 PM', isOpen: true },
|
|
{ day: 'Thursday', hours: '9:00 AM - 5:00 PM', isOpen: true },
|
|
{ day: 'Friday', hours: '9:00 AM - 5:00 PM', isOpen: true },
|
|
{ day: 'Saturday', hours: '10:00 AM - 2:00 PM', isOpen: true },
|
|
{ day: 'Sunday', hours: 'Closed', isOpen: false },
|
|
];
|
|
|
|
export const BusinessHours: ComponentConfig<BusinessHoursProps> = {
|
|
label: 'Business Hours',
|
|
fields: {
|
|
showCurrent: {
|
|
type: 'radio',
|
|
label: 'Highlight Current Day',
|
|
options: [
|
|
{ label: 'Yes', value: true },
|
|
{ label: 'No', value: false },
|
|
],
|
|
},
|
|
title: {
|
|
type: 'text',
|
|
label: 'Title',
|
|
},
|
|
},
|
|
defaultProps: {
|
|
showCurrent: true,
|
|
title: 'Business Hours',
|
|
},
|
|
render: ({ showCurrent, title }) => {
|
|
const today = new Date().toLocaleDateString('en-US', { weekday: 'long' });
|
|
|
|
return (
|
|
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
|
|
{title && (
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<Clock className="w-6 h-6 text-indigo-600 dark:text-indigo-400" />
|
|
<h3 className="text-xl font-semibold text-gray-900 dark:text-white">
|
|
{title}
|
|
</h3>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-3">
|
|
{DEFAULT_HOURS.map(({ day, hours, isOpen }) => {
|
|
const isToday = showCurrent && day === today;
|
|
|
|
return (
|
|
<div
|
|
key={day}
|
|
className={`flex items-center justify-between py-2 px-3 rounded-lg ${
|
|
isToday
|
|
? 'bg-indigo-50 dark:bg-indigo-900/20'
|
|
: ''
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
{isOpen ? (
|
|
<CheckCircle className="w-4 h-4 text-green-500" />
|
|
) : (
|
|
<XCircle className="w-4 h-4 text-red-400" />
|
|
)}
|
|
<span
|
|
className={`font-medium ${
|
|
isToday
|
|
? 'text-indigo-600 dark:text-indigo-400'
|
|
: 'text-gray-700 dark:text-gray-300'
|
|
}`}
|
|
>
|
|
{day}
|
|
{isToday && (
|
|
<span className="ml-2 text-xs bg-indigo-600 text-white px-2 py-0.5 rounded-full">
|
|
Today
|
|
</span>
|
|
)}
|
|
</span>
|
|
</div>
|
|
<span
|
|
className={`${
|
|
isOpen
|
|
? 'text-gray-600 dark:text-gray-400'
|
|
: 'text-red-500 dark:text-red-400'
|
|
}`}
|
|
>
|
|
{hours}
|
|
</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
};
|
|
|
|
export default BusinessHours;
|