Refactor Services page UI, disable full test coverage, and add WIP badges
This commit is contained in:
148
frontend/src/components/services/CustomerPreview.tsx
Normal file
148
frontend/src/components/services/CustomerPreview.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Clock,
|
||||
MapPin,
|
||||
User,
|
||||
Calendar,
|
||||
CheckCircle2,
|
||||
AlertCircle
|
||||
} from 'lucide-react';
|
||||
import { Service, Business } from '../../types';
|
||||
import Card from '../ui/Card';
|
||||
import Badge from '../ui/Badge';
|
||||
|
||||
interface CustomerPreviewProps {
|
||||
service: Service | null; // Null when creating new
|
||||
business: Business;
|
||||
previewData?: Partial<Service>; // Live form data
|
||||
}
|
||||
|
||||
export const CustomerPreview: React.FC<CustomerPreviewProps> = ({
|
||||
service,
|
||||
business,
|
||||
previewData
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Merge existing service data with live form preview
|
||||
const data = {
|
||||
...service,
|
||||
...previewData,
|
||||
price: previewData?.price ?? service?.price ?? 0,
|
||||
name: previewData?.name || service?.name || 'New Service',
|
||||
description: previewData?.description || service?.description || 'Service description will appear here...',
|
||||
durationMinutes: previewData?.durationMinutes ?? service?.durationMinutes ?? 30,
|
||||
};
|
||||
|
||||
const formatPrice = (price: number | string) => {
|
||||
const numPrice = typeof price === 'string' ? parseFloat(price) : price;
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(numPrice);
|
||||
};
|
||||
|
||||
const formatDuration = (minutes: number) => {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
if (hours > 0) return `${hours}h ${mins > 0 ? `${mins}m` : ''}`;
|
||||
return `${mins}m`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Customer Preview
|
||||
</h3>
|
||||
<Badge variant="info" size="sm">Live Preview</Badge>
|
||||
</div>
|
||||
|
||||
{/* Booking Page Card Simulation */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-100 dark:border-gray-700 overflow-hidden transform transition-all hover:scale-[1.02]">
|
||||
{/* Cover Image Placeholder */}
|
||||
<div
|
||||
className="h-32 w-full bg-cover bg-center relative"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, var(--color-brand-600, ${business.primaryColor}), var(--color-brand-400, ${business.secondaryColor}))`,
|
||||
opacity: 0.9
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-white/20 font-bold text-4xl select-none">
|
||||
{data.name.charAt(0)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-start gap-4 mb-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white leading-tight mb-1">
|
||||
{data.name}
|
||||
</h2>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<Clock size={14} />
|
||||
<span>{formatDuration(data.durationMinutes)}</span>
|
||||
<span>•</span>
|
||||
<span>{data.category?.name || 'General'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-lg font-bold text-brand-600 dark:text-brand-400">
|
||||
{data.variable_pricing ? (
|
||||
'Variable'
|
||||
) : (
|
||||
formatPrice(data.price)
|
||||
)}
|
||||
</div>
|
||||
{data.deposit_amount && data.deposit_amount > 0 && (
|
||||
<div className="text-xs text-gray-500">
|
||||
{formatPrice(data.deposit_amount)} deposit
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm leading-relaxed mb-6">
|
||||
{data.description}
|
||||
</p>
|
||||
|
||||
<div className="space-y-3 pt-4 border-t border-gray-100 dark:border-gray-700">
|
||||
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
<div className="p-1.5 rounded-full bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400">
|
||||
<CheckCircle2 size={14} />
|
||||
</div>
|
||||
<span>Online booking available</span>
|
||||
</div>
|
||||
|
||||
{(data.resource_ids?.length || 0) > 0 && !data.all_resources && (
|
||||
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
<div className="p-1.5 rounded-full bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400">
|
||||
<User size={14} />
|
||||
</div>
|
||||
<span>Specific staff only</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<button className="w-full py-2.5 px-4 bg-brand-600 hover:bg-brand-700 text-white font-medium rounded-xl transition-colors shadow-sm shadow-brand-200 dark:shadow-none">
|
||||
Book Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 flex gap-3 items-start">
|
||||
<AlertCircle size={20} className="text-blue-600 dark:text-blue-400 shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-blue-800 dark:text-blue-300">
|
||||
This is how your service will appear to customers on your booking page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomerPreview;
|
||||
Reference in New Issue
Block a user