Files
smoothschedule/smoothschedule/schedule/api_views.py
poduck b10426fbdb 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>
2025-11-28 01:11:53 -05:00

169 lines
6.4 KiB
Python

"""
API views for business/tenant management
"""
import base64
import uuid
from django.core.files.base import ContentFile
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def current_business_view(request):
"""
Get current business (tenant) for the authenticated user
GET /api/business/current/
Returns null if user is a platform user without a tenant
"""
user = request.user
tenant = user.tenant
# Platform users don't have a tenant
if not tenant:
return Response(None, status=status.HTTP_200_OK)
# Get subdomain from primary domain
subdomain = None
primary_domain = tenant.domains.filter(is_primary=True).first()
if primary_domain:
# Extract subdomain from domain (e.g., "mybusiness.lvh.me" -> "mybusiness")
domain_parts = primary_domain.domain.split('.')
if len(domain_parts) > 0:
subdomain = domain_parts[0]
business_data = {
'id': tenant.id,
'name': tenant.name,
'subdomain': subdomain or tenant.schema_name,
'tier': tenant.subscription_tier,
'status': 'active' if tenant.is_active else 'inactive',
'created_at': tenant.created_on.isoformat() if tenant.created_on else None,
# Branding fields from Tenant model
'primary_color': tenant.primary_color,
'secondary_color': tenant.secondary_color,
'logo_url': request.build_absolute_uri(tenant.logo.url) if tenant.logo else None,
'email_logo_url': request.build_absolute_uri(tenant.email_logo.url) if tenant.email_logo else None,
'logo_display_mode': tenant.logo_display_mode,
# Other optional fields with defaults
'whitelabel_enabled': False,
'resources_can_reschedule': False,
'require_payment_method_to_book': False,
'cancellation_window_hours': 24,
'late_cancellation_fee_percent': 0,
'initial_setup_complete': False,
'website_pages': {},
'customer_dashboard_content': [],
}
return Response(business_data, status=status.HTTP_200_OK)
@api_view(['PATCH'])
@permission_classes([IsAuthenticated])
def update_business_view(request):
"""
Update business (tenant) settings for the authenticated user
PATCH /api/business/current/update/
Only business owners can update settings
"""
user = request.user
tenant = user.tenant
# Platform users don't have a tenant
if not tenant:
return Response({'error': 'No business found'}, status=status.HTTP_404_NOT_FOUND)
# Only owners can update business settings
if user.role.lower() != 'tenant_owner':
return Response({'error': 'Only business owners can update settings'}, status=status.HTTP_403_FORBIDDEN)
# Update fields if provided in request
if 'name' in request.data:
tenant.name = request.data['name']
if 'primary_color' in request.data:
tenant.primary_color = request.data['primary_color']
if 'secondary_color' in request.data:
tenant.secondary_color = request.data['secondary_color']
if 'logo_display_mode' in request.data:
tenant.logo_display_mode = request.data['logo_display_mode']
# Handle logo uploads (base64 data URLs)
if 'logo_url' in request.data:
logo_data = request.data['logo_url']
if logo_data and logo_data.startswith('data:image'):
# Extract base64 data and file extension
format_str, imgstr = logo_data.split(';base64,')
ext = format_str.split('/')[-1]
# Decode base64 and create Django file
data = ContentFile(base64.b64decode(imgstr), name=f'logo_{uuid.uuid4()}.{ext}')
# Delete old logo if exists
if tenant.logo:
tenant.logo.delete(save=False)
tenant.logo = data
elif logo_data is None or logo_data == '':
# Remove logo if set to None or empty string
if tenant.logo:
tenant.logo.delete(save=False)
tenant.logo = None
if 'email_logo_url' in request.data:
email_logo_data = request.data['email_logo_url']
if email_logo_data and email_logo_data.startswith('data:image'):
# Extract base64 data and file extension
format_str, imgstr = email_logo_data.split(';base64,')
ext = format_str.split('/')[-1]
# Decode base64 and create Django file
data = ContentFile(base64.b64decode(imgstr), name=f'email_logo_{uuid.uuid4()}.{ext}')
# Delete old email logo if exists
if tenant.email_logo:
tenant.email_logo.delete(save=False)
tenant.email_logo = data
elif email_logo_data is None or email_logo_data == '':
# Remove email logo if set to None or empty string
if tenant.email_logo:
tenant.email_logo.delete(save=False)
tenant.email_logo = None
# Save the tenant
tenant.save()
# Return updated business data
subdomain = None
primary_domain = tenant.domains.filter(is_primary=True).first()
if primary_domain:
domain_parts = primary_domain.domain.split('.')
if len(domain_parts) > 0:
subdomain = domain_parts[0]
business_data = {
'id': tenant.id,
'name': tenant.name,
'subdomain': subdomain or tenant.schema_name,
'tier': tenant.subscription_tier,
'status': 'active' if tenant.is_active else 'inactive',
'created_at': tenant.created_on.isoformat() if tenant.created_on else None,
'primary_color': tenant.primary_color,
'secondary_color': tenant.secondary_color,
'logo_url': request.build_absolute_uri(tenant.logo.url) if tenant.logo else None,
'email_logo_url': request.build_absolute_uri(tenant.email_logo.url) if tenant.email_logo else None,
'logo_display_mode': tenant.logo_display_mode,
'whitelabel_enabled': False,
'resources_can_reschedule': False,
'require_payment_method_to_book': False,
'cancellation_window_hours': 24,
'late_cancellation_fee_percent': 0,
'initial_setup_complete': False,
'website_pages': {},
'customer_dashboard_content': [],
}
return Response(business_data, status=status.HTTP_200_OK)