feat: Add photo galleries to services, resource types management, and UI improvements

Major features:
- Add drag-and-drop photo gallery to Service create/edit modals
- Add Resource Types management section to Settings (CRUD for custom types)
- Add edit icon consistency to Resources table (pencil icon in actions)
- Improve Services page with drag-to-reorder and customer preview mockup

Backend changes:
- Add photos JSONField to Service model with migration
- Add ResourceType model with category (STAFF/OTHER), description fields
- Add ResourceTypeViewSet with CRUD operations
- Add service reorder endpoint for display order

Frontend changes:
- Services page: two-column layout, drag-reorder, photo upload
- Settings page: Resource Types tab with full CRUD modal
- Resources page: Edit icon in actions column instead of row click
- Sidebar: Payments link visibility based on role and paymentsEnabled
- Update types.ts with Service.photos and ResourceTypeDefinition

Note: Removed photos from ResourceType (kept only for Service)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-11-28 01:11:53 -05:00
parent a7c756a8ec
commit b10426fbdb
52 changed files with 4259 additions and 356 deletions

View File

@@ -34,7 +34,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
? location.pathname === path
: location.pathname.startsWith(path);
const baseClasses = `flex items-center gap-3 py-3 text-sm font-medium rounded-lg transition-colors`;
const baseClasses = `flex items-center gap-3 py-3 text-base font-medium rounded-lg transition-colors`;
const collapsedClasses = isCollapsed ? 'px-3 justify-center' : 'px-4';
const activeClasses = 'bg-white/10 text-white';
const inactiveClasses = 'text-white/70 hover:text-white hover:bg-white/5';
@@ -70,14 +70,40 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
className={`flex items-center gap-3 w-full text-left px-6 py-8 ${isCollapsed ? 'justify-center' : ''} hover:bg-white/5 transition-colors focus:outline-none`}
aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
>
<div className="flex items-center justify-center w-10 h-10 bg-white rounded-lg text-brand-600 font-bold text-xl shrink-0" style={{ color: business.primaryColor }}>
{business.name.substring(0, 2).toUpperCase()}
</div>
{!isCollapsed && (
<div className="overflow-hidden">
<h1 className="font-bold leading-tight truncate">{business.name}</h1>
<p className="text-xs text-white/60 truncate">{business.subdomain}.smoothschedule.com</p>
{/* Logo-only mode: full width */}
{business.logoDisplayMode === 'logo-only' && business.logoUrl ? (
<div className="flex items-center justify-center w-full">
<img
src={business.logoUrl}
alt={business.name}
className="max-w-full max-h-16 object-contain"
/>
</div>
) : (
<>
{/* Logo/Icon display */}
{business.logoUrl && business.logoDisplayMode !== 'text-only' ? (
<div className="flex items-center justify-center w-10 h-10 shrink-0">
<img
src={business.logoUrl}
alt={business.name}
className="w-full h-full object-contain"
/>
</div>
) : business.logoDisplayMode !== 'logo-only' && (
<div className="flex items-center justify-center w-10 h-10 bg-white rounded-lg text-brand-600 font-bold text-xl shrink-0" style={{ color: business.primaryColor }}>
{business.name.substring(0, 2).toUpperCase()}
</div>
)}
{/* Text display */}
{!isCollapsed && business.logoDisplayMode !== 'logo-only' && (
<div className="overflow-hidden">
<h1 className="font-bold leading-tight truncate">{business.name}</h1>
<p className="text-xs text-white/60 truncate">{business.subdomain}.smoothschedule.com</p>
</div>
)}
</>
)}
</button>
@@ -111,19 +137,22 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
{canViewAdminPages && (
<>
{business.paymentsEnabled ? (
<Link to="/payments" className={getNavClass('/payments')} title={t('nav.payments')}>
<CreditCard size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.payments')}</span>}
</Link>
) : (
<div
className={getNavClass('/payments', false, true)}
title={t('nav.paymentsDisabledTooltip')}
>
<CreditCard size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.payments')}</span>}
</div>
{/* Payments link: always visible for owners, only visible for others if enabled */}
{(role === 'owner' || business.paymentsEnabled) && (
business.paymentsEnabled ? (
<Link to="/payments" className={getNavClass('/payments')} title={t('nav.payments')}>
<CreditCard size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.payments')}</span>}
</Link>
) : (
<div
className={getNavClass('/payments', false, true)}
title={t('nav.paymentsDisabledTooltip')}
>
<CreditCard size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.payments')}</span>}
</div>
)
)}
<Link to="/messages" className={getNavClass('/messages')} title={t('nav.messages')}>
<MessageSquare size={20} className="shrink-0" />
@@ -149,7 +178,12 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
</nav>
<div className="p-4 border-t border-white/10">
<div className={`flex items-center gap-2 text-xs text-white/60 mb-4 ${isCollapsed ? 'justify-center' : ''}`}>
<a
href={`${window.location.protocol}//${window.location.host.split('.').slice(-2).join('.')}`}
target="_blank"
rel="noopener noreferrer"
className={`flex items-center gap-2 text-xs text-white/60 mb-4 hover:text-white/80 transition-colors ${isCollapsed ? 'justify-center' : ''}`}
>
<SmoothScheduleLogo className="w-6 h-6 text-white" />
{!isCollapsed && (
<div>
@@ -157,7 +191,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
<span className="font-semibold text-white/80">Smooth Schedule</span>
</div>
)}
</div>
</a>
<button
onClick={handleSignOut}
disabled={logoutMutation.isPending}