Initial commit: SmoothSchedule multi-tenant scheduling platform

This commit includes:
- Django backend with multi-tenancy (django-tenants)
- React + TypeScript frontend with Vite
- Platform administration API with role-based access control
- Authentication system with token-based auth
- Quick login dev tools for testing different user roles
- CORS and CSRF configuration for local development
- Docker development environment setup

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-11-27 01:43:20 -05:00
commit 2e111364a2
567 changed files with 96410 additions and 0 deletions

152
users/admin.py Normal file
View File

@@ -0,0 +1,152 @@
"""
Smooth Schedule Users App Admin Configuration
"""
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.html import format_html
from hijack.contrib.admin import HijackUserAdminMixin
from .models import User
@admin.register(User)
class UserAdmin(HijackUserAdminMixin, BaseUserAdmin):
"""
Custom User admin with role-based filtering and masquerade button.
"""
list_display = [
'email',
'first_name',
'last_name',
'role_badge',
'tenant',
'is_active',
'is_temporary',
'created_at',
'hijack_field', # Adds the masquerade button
]
list_filter = [
'role',
'is_active',
'is_staff',
'is_superuser',
'is_temporary',
'created_at',
]
search_fields = [
'email',
'first_name',
'last_name',
'tenant__name',
]
ordering = ['-created_at']
fieldsets = (
('Authentication', {
'fields': ('email', 'password')
}),
('Personal Information', {
'fields': ('first_name', 'last_name', 'phone', 'job_title')
}),
('Permissions & Role', {
'fields': ('role', 'tenant', 'is_temporary', 'is_active', 'is_staff', 'is_superuser')
}),
('Groups & Permissions', {
'fields': ('groups', 'user_permissions'),
'classes': ('collapse',),
}),
('Important Dates', {
'fields': ('last_login', 'date_joined', 'created_at', 'updated_at'),
'classes': ('collapse',),
}),
('Security', {
'fields': ('last_login_ip',),
'classes': ('collapse',),
}),
)
readonly_fields = [
'created_at',
'updated_at',
'date_joined',
'last_login',
'last_login_ip',
]
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': (
'email',
'password1',
'password2',
'role',
'tenant',
'first_name',
'last_name',
'is_temporary',
),
}),
)
def role_badge(self, obj):
"""Display role with color-coded badge"""
role_colors = {
'SUPERUSER': '#d32f2f', # Dark red
'PLATFORM_MANAGER': '#f57c00', # Orange
'PLATFORM_SALES': '#fbc02d', # Yellow
'PLATFORM_SUPPORT': '#7cb342', # Light green
'TENANT_OWNER': '#1976d2', # Blue
'TENANT_MANAGER': '#0288d1', # Light blue
'TENANT_STAFF': '#0097a7', # Cyan
'CUSTOMER': '#5e35b1', # Purple
}
color = role_colors.get(obj.role, '#777')
badge_html = f'''
<span style="
background-color: {color};
color: white;
padding: 3px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: bold;
white-space: nowrap;
">{obj.get_role_display()}</span>
'''
if obj.is_temporary:
badge_html += ' <span style="color: orange; font-size: 11px;">🎭 DEMO</span>'
return format_html(badge_html)
role_badge.short_description = 'Role'
def get_queryset(self, request):
"""
Filter users based on who is viewing.
Platform users see all, tenant users only see their tenant.
"""
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
# If user is a tenant-level admin, only show users from their tenant
if request.user.tenant:
return qs.filter(tenant=request.user.tenant)
return qs
def formfield_for_foreignkey(self, db_field, request, **kwargs):
"""
Limit tenant choices based on who is editing.
"""
if db_field.name == "tenant":
# If editing user is tenant-scoped, they can only assign to their own tenant
if request.user.tenant and not request.user.is_superuser:
kwargs["queryset"] = request.user.get_accessible_tenants()
return super().formfield_for_foreignkey(db_field, request, **kwargs)