feat: Redesign bakery and user settings pages with improved UX
Implemented a comprehensive redesign of the settings pages using Jobs To Be Done (JTBD) methodology to improve user experience, visual appeal, and discoverability. ## New Components - **SettingRow**: Reusable component for consistent setting layouts with support for toggles, inputs, selects, and custom content - **SettingSection**: Collapsible section component for grouping related settings with consistent styling ## Page Redesigns ### BakerySettingsPage - Redesigned information tab with better visual hierarchy using SettingSection components - Improved business hours UI with clearer day-by-day layout - Enhanced header with gradient bakery icon and status indicators - Consistent spacing and responsive design improvements - Better visual feedback for unsaved changes ### NewProfileSettingsPage - Unified design with bakery settings page - Improved personal information section with SettingSection - Better security section layout with collapsible password change form - Enhanced privacy & data management UI - Consistent icon usage and visual hierarchy ### InventorySettingsCard - Replaced checkbox with toggle switch for temperature monitoring - Progressive disclosure: temperature settings only shown when enabled - Better visual separation between setting groups - Improved responsive grid layouts - Added helpful descriptions and tooltips ## Key Improvements 1. **Visual Consistency**: Both bakery and user settings now use the same design patterns and components 2. **Scannability**: Icons, badges, and clear visual hierarchy make settings easier to scan 3. **Progressive Disclosure**: Complex settings (like temperature monitoring) only show when relevant 4. **Toggle Switches**: Binary settings use toggles instead of checkboxes for better visual feedback 5. **Responsive Design**: Improved mobile and desktop layouts with better touch targets 6. **Accessibility**: Proper ARIA labels, help text, and keyboard navigation support ## JTBD Analysis Applied - Main job: "Quickly find, understand, and change settings without mistakes" - Sub-jobs addressed: - Discovery & navigation (visual grouping, icons, clear labels) - Configuration & adjustment (toggles, inline editing, validation) - Validation & confidence (help text, descriptions, visual feedback) This redesign maintains backward compatibility while significantly improving the user experience for managing bakery and personal settings.
This commit is contained in:
180
frontend/src/components/ui/SettingRow/SettingRow.tsx
Normal file
180
frontend/src/components/ui/SettingRow/SettingRow.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import React from 'react';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
import { Toggle } from '../Toggle';
|
||||
import { Input } from '../Input';
|
||||
import { Select } from '../Select';
|
||||
import { Badge } from '../Badge';
|
||||
|
||||
export interface SettingRowProps {
|
||||
label: string;
|
||||
description?: string;
|
||||
helpText?: string;
|
||||
icon?: React.ReactNode;
|
||||
badge?: {
|
||||
text: string;
|
||||
variant?: 'default' | 'success' | 'warning' | 'danger' | 'info';
|
||||
};
|
||||
|
||||
// For toggle type
|
||||
type?: 'toggle' | 'input' | 'select' | 'custom';
|
||||
checked?: boolean;
|
||||
onToggle?: (checked: boolean) => void;
|
||||
|
||||
// For input type
|
||||
inputType?: 'text' | 'number' | 'email' | 'tel' | 'password';
|
||||
value?: string | number;
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
placeholder?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
error?: string;
|
||||
|
||||
// For select type
|
||||
options?: Array<{ value: string; label: string }>;
|
||||
selectValue?: string;
|
||||
onSelectChange?: (value: string) => void;
|
||||
|
||||
// For custom content
|
||||
children?: React.ReactNode;
|
||||
|
||||
// Common props
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
const SettingRow: React.FC<SettingRowProps> = ({
|
||||
label,
|
||||
description,
|
||||
helpText,
|
||||
icon,
|
||||
badge,
|
||||
type = 'toggle',
|
||||
checked,
|
||||
onToggle,
|
||||
inputType = 'text',
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
error,
|
||||
options = [],
|
||||
selectValue,
|
||||
onSelectChange,
|
||||
children,
|
||||
disabled = false,
|
||||
className = '',
|
||||
required = false,
|
||||
}) => {
|
||||
const renderControl = () => {
|
||||
switch (type) {
|
||||
case 'toggle':
|
||||
return (
|
||||
<Toggle
|
||||
checked={checked || false}
|
||||
onChange={onToggle || (() => {})}
|
||||
disabled={disabled}
|
||||
size="md"
|
||||
/>
|
||||
);
|
||||
|
||||
case 'input':
|
||||
return (
|
||||
<div className="w-full max-w-xs">
|
||||
<Input
|
||||
type={inputType}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
error={error}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'select':
|
||||
return (
|
||||
<div className="w-full max-w-xs">
|
||||
<Select
|
||||
options={options}
|
||||
value={selectValue}
|
||||
onChange={onSelectChange}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'custom':
|
||||
return children;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
flex flex-col sm:flex-row sm:items-center sm:justify-between
|
||||
gap-3 sm:gap-6 py-4 px-4 sm:px-6
|
||||
border-b border-[var(--border-primary)] last:border-b-0
|
||||
hover:bg-[var(--bg-secondary)] transition-colors
|
||||
${className}
|
||||
`}>
|
||||
{/* Left side - Label and Description */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{icon && (
|
||||
<div className="flex-shrink-0 text-[var(--text-secondary)]">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="text-sm font-medium text-[var(--text-primary)] flex items-center gap-2">
|
||||
{label}
|
||||
{required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
|
||||
{badge && (
|
||||
<Badge variant={badge.variant || 'default'} size="sm">
|
||||
{badge.text}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{helpText && (
|
||||
<Tooltip content={helpText}>
|
||||
<HelpCircle className="w-4 h-4 text-[var(--text-tertiary)] cursor-help" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{description && (
|
||||
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<p className="text-xs text-red-500 mt-1">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right side - Control */}
|
||||
<div className="flex-shrink-0">
|
||||
{renderControl()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingRow;
|
||||
2
frontend/src/components/ui/SettingRow/index.ts
Normal file
2
frontend/src/components/ui/SettingRow/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as SettingRow } from './SettingRow';
|
||||
export type { SettingRowProps } from './SettingRow';
|
||||
108
frontend/src/components/ui/SettingSection/SettingSection.tsx
Normal file
108
frontend/src/components/ui/SettingSection/SettingSection.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { Card } from '../Card';
|
||||
import { Badge } from '../Badge';
|
||||
|
||||
export interface SettingSectionProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: React.ReactNode;
|
||||
badge?: {
|
||||
text: string;
|
||||
variant?: 'default' | 'success' | 'warning' | 'danger' | 'info';
|
||||
};
|
||||
collapsible?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
headerAction?: React.ReactNode;
|
||||
}
|
||||
|
||||
const SettingSection: React.FC<SettingSectionProps> = ({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
badge,
|
||||
collapsible = false,
|
||||
defaultOpen = true,
|
||||
children,
|
||||
className = '',
|
||||
headerAction,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
|
||||
const handleToggle = () => {
|
||||
if (collapsible) {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={`overflow-hidden ${className}`}>
|
||||
{/* Header */}
|
||||
<div
|
||||
className={`
|
||||
px-4 sm:px-6 py-4
|
||||
${collapsible ? 'cursor-pointer hover:bg-[var(--bg-secondary)]' : ''}
|
||||
transition-colors
|
||||
`}
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex items-start gap-3 flex-1 min-w-0">
|
||||
{icon && (
|
||||
<div className="flex-shrink-0 mt-0.5 text-[var(--color-primary)]">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="text-base sm:text-lg font-semibold text-[var(--text-primary)]">
|
||||
{title}
|
||||
</h3>
|
||||
|
||||
{badge && (
|
||||
<Badge variant={badge.variant || 'default'} size="sm">
|
||||
{badge.text}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{collapsible && (
|
||||
<div className="ml-auto flex-shrink-0">
|
||||
{isOpen ? (
|
||||
<ChevronUp className="w-5 h-5 text-[var(--text-secondary)]" />
|
||||
) : (
|
||||
<ChevronDown className="w-5 h-5 text-[var(--text-secondary)]" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{description && (
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-1">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{headerAction && !collapsible && (
|
||||
<div className="flex-shrink-0">
|
||||
{headerAction}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
{(!collapsible || isOpen) && (
|
||||
<div className="border-t border-[var(--border-primary)]">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingSection;
|
||||
2
frontend/src/components/ui/SettingSection/index.ts
Normal file
2
frontend/src/components/ui/SettingSection/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as SettingSection } from './SettingSection';
|
||||
export type { SettingSectionProps } from './SettingSection';
|
||||
@@ -40,6 +40,8 @@ export { TableOfContents } from './TableOfContents';
|
||||
export { SavingsCalculator } from './SavingsCalculator';
|
||||
export { StepTimeline } from './StepTimeline';
|
||||
export { FAQAccordion } from './FAQAccordion';
|
||||
export { SettingRow } from './SettingRow';
|
||||
export { SettingSection } from './SettingSection';
|
||||
|
||||
// Export types
|
||||
export type { ButtonProps } from './Button';
|
||||
@@ -77,4 +79,6 @@ export type { FloatingCTAProps } from './FloatingCTA';
|
||||
export type { TableOfContentsProps, TOCSection } from './TableOfContents';
|
||||
export type { SavingsCalculatorProps } from './SavingsCalculator';
|
||||
export type { StepTimelineProps, TimelineStep } from './StepTimeline';
|
||||
export type { FAQAccordionProps, FAQItem } from './FAQAccordion';
|
||||
export type { FAQAccordionProps, FAQItem } from './FAQAccordion';
|
||||
export type { SettingRowProps } from './SettingRow';
|
||||
export type { SettingSectionProps } from './SettingSection';
|
||||
Reference in New Issue
Block a user