Implement Site Builder Metadata and Enhance Wizard Functionality

- Introduced new models for Site Builder options, including BusinessType, AudienceProfile, BrandPersonality, and HeroImageryDirection.
- Added serializers and views to handle metadata for dropdowns in the Site Builder wizard.
- Updated the SiteBuilderWizard component to load and display metadata, improving user experience with dynamic options.
- Enhanced BusinessDetailsStep and StyleStep components to utilize new metadata for business types and brand personalities.
- Refactored state management in builderStore to include metadata loading and error handling.
- Updated API service to fetch Site Builder metadata, ensuring seamless integration with the frontend.
This commit is contained in:
IGNY8 VPS (Salman)
2025-11-18 12:31:59 +00:00
parent 5d97ab6e49
commit 26ec2ae03e
13 changed files with 1062 additions and 96 deletions

Binary file not shown.

View File

@@ -0,0 +1,159 @@
from django.db import migrations, models
def seed_site_builder_metadata(apps, schema_editor):
BusinessType = apps.get_model('site_building', 'BusinessType')
AudienceProfile = apps.get_model('site_building', 'AudienceProfile')
BrandPersonality = apps.get_model('site_building', 'BrandPersonality')
HeroImageryDirection = apps.get_model('site_building', 'HeroImageryDirection')
business_types = [
("Productized Services", "Standardized service offering with clear deliverables."),
("B2B SaaS Platform", "Subscription software platform targeting business teams."),
("eCommerce Brand", "Direct-to-consumer catalog with premium merchandising."),
("Marketplace / Platform", "Two-sided marketplace connecting buyers and sellers."),
("Advisory / Consulting", "Expert advisory firm or boutique consultancy."),
("Education / Training", "Learning platform, cohort, or academy."),
("Community / Membership", "Member-driven experience with gated content."),
("Mission-Driven / Nonprofit", "Impact-focused organization or foundation."),
]
audience_profiles = [
("Enterprise Operations Leaders", "COO / Ops executives at scale-ups."),
("Marketing Directors & CMOs", "Growth and brand owners across industries."),
("Founders & Executive Teams", "Visionaries leading fast-moving companies."),
("Revenue & Sales Leaders", "CROs, VPs of Sales, and GTM owners."),
("Product & Innovation Teams", "Product managers and innovation leaders."),
("IT & Engineering Teams", "Technical buyers evaluating new platforms."),
("HR & People Leaders", "People ops and talent professionals."),
("Healthcare Administrators", "Clinical and operational healthcare leads."),
("Financial Services Professionals", "Banking, fintech, and investment teams."),
("Consumers / Prospects", "End-user or prospect-focused experience."),
]
brand_personalities = [
("Bold Visionary", "Decisive, future-forward, and thought-leading."),
("Trusted Advisor", "Calm, credible, and risk-aware guidance."),
("Analytical Expert", "Data-backed, precise, and rigorous."),
("Friendly Guide", "Welcoming, warm, and supportive tone."),
("Luxe & Premium", "High-touch, elevated, and detail-obsessed."),
("Playful Creative", "Vibrant, unexpected, and energetic."),
("Minimalist Modern", "Clean, refined, and confident."),
("Fearless Innovator", "Experimental, edgy, and fast-moving."),
]
hero_imagery = [
("Real team collaboration photography", "Documentary-style shots of real teams."),
("Product close-ups with UI overlays", "Focus on interfaces and feature highlights."),
("Lifestyle scenes featuring customers", "Story-driven photography of real scenarios."),
("Abstract gradients & motion graphics", "Modern, colorful abstract compositions."),
("Illustration + iconography blend", "Custom illustrations paired with icon systems."),
]
for order, (name, description) in enumerate(business_types):
BusinessType.objects.update_or_create(
name=name,
defaults={'description': description, 'order': order, 'is_active': True},
)
for order, (name, description) in enumerate(audience_profiles):
AudienceProfile.objects.update_or_create(
name=name,
defaults={'description': description, 'order': order, 'is_active': True},
)
for order, (name, description) in enumerate(brand_personalities):
BrandPersonality.objects.update_or_create(
name=name,
defaults={'description': description, 'order': order, 'is_active': True},
)
for order, (name, description) in enumerate(hero_imagery):
HeroImageryDirection.objects.update_or_create(
name=name,
defaults={'description': description, 'order': order, 'is_active': True},
)
class Migration(migrations.Migration):
dependencies = [
('site_building', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='AudienceProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=120, unique=True)),
('description', models.CharField(blank=True, max_length=255)),
('is_active', models.BooleanField(default=True)),
('order', models.PositiveIntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Audience Profile',
'verbose_name_plural': 'Audience Profiles',
'db_table': 'igny8_site_builder_audience_profiles',
'ordering': ['order', 'name'],
},
),
migrations.CreateModel(
name='BrandPersonality',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=120, unique=True)),
('description', models.CharField(blank=True, max_length=255)),
('is_active', models.BooleanField(default=True)),
('order', models.PositiveIntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Brand Personality',
'verbose_name_plural': 'Brand Personalities',
'db_table': 'igny8_site_builder_brand_personalities',
'ordering': ['order', 'name'],
},
),
migrations.CreateModel(
name='BusinessType',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=120, unique=True)),
('description', models.CharField(blank=True, max_length=255)),
('is_active', models.BooleanField(default=True)),
('order', models.PositiveIntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Business Type',
'verbose_name_plural': 'Business Types',
'db_table': 'igny8_site_builder_business_types',
'ordering': ['order', 'name'],
},
),
migrations.CreateModel(
name='HeroImageryDirection',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=120, unique=True)),
('description', models.CharField(blank=True, max_length=255)),
('is_active', models.BooleanField(default=True)),
('order', models.PositiveIntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Hero Imagery Direction',
'verbose_name_plural': 'Hero Imagery Directions',
'db_table': 'igny8_site_builder_hero_imagery',
'ordering': ['order', 'name'],
},
),
migrations.RunPython(seed_site_builder_metadata, migrations.RunPython.noop),
]

View File

@@ -166,3 +166,55 @@ class PageBlueprint(SiteSectorBaseModel):
return f"{self.title} ({self.site_blueprint.name})"
class SiteBuilderOption(models.Model):
"""
Base model for Site Builder dropdown metadata.
"""
name = models.CharField(max_length=120, unique=True)
description = models.CharField(max_length=255, blank=True)
is_active = models.BooleanField(default=True)
order = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
ordering = ['order', 'name']
def __str__(self):
return self.name
class BusinessType(SiteBuilderOption):
class Meta(SiteBuilderOption.Meta):
app_label = 'site_building'
db_table = 'igny8_site_builder_business_types'
verbose_name = 'Business Type'
verbose_name_plural = 'Business Types'
class AudienceProfile(SiteBuilderOption):
class Meta(SiteBuilderOption.Meta):
app_label = 'site_building'
db_table = 'igny8_site_builder_audience_profiles'
verbose_name = 'Audience Profile'
verbose_name_plural = 'Audience Profiles'
class BrandPersonality(SiteBuilderOption):
class Meta(SiteBuilderOption.Meta):
app_label = 'site_building'
db_table = 'igny8_site_builder_brand_personalities'
verbose_name = 'Brand Personality'
verbose_name_plural = 'Brand Personalities'
class HeroImageryDirection(SiteBuilderOption):
class Meta(SiteBuilderOption.Meta):
app_label = 'site_building'
db_table = 'igny8_site_builder_hero_imagery'
verbose_name = 'Hero Imagery Direction'
verbose_name_plural = 'Hero Imagery Directions'

View File

@@ -1,6 +1,13 @@
from rest_framework import serializers
from igny8_core.business.site_building.models import SiteBlueprint, PageBlueprint
from igny8_core.business.site_building.models import (
AudienceProfile,
BrandPersonality,
BusinessType,
HeroImageryDirection,
PageBlueprint,
SiteBlueprint,
)
class PageBlueprintSerializer(serializers.ModelSerializer):
@@ -76,3 +83,16 @@ class SiteBlueprintSerializer(serializers.ModelSerializer):
attrs['sector_id'] = sector_id
return attrs
class MetadataOptionSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
description = serializers.CharField(required=False, allow_blank=True)
class SiteBuilderMetadataSerializer(serializers.Serializer):
business_types = MetadataOptionSerializer(many=True)
audience_profiles = MetadataOptionSerializer(many=True)
brand_personalities = MetadataOptionSerializer(many=True)
hero_imagery_directions = MetadataOptionSerializer(many=True)

View File

@@ -5,6 +5,7 @@ from igny8_core.modules.site_builder.views import (
PageBlueprintViewSet,
SiteAssetView,
SiteBlueprintViewSet,
SiteBuilderMetadataView,
)
router = DefaultRouter()
@@ -14,5 +15,6 @@ router.register(r'pages', PageBlueprintViewSet, basename='page_blueprint')
urlpatterns = [
path('', include(router.urls)),
path('assets/', SiteAssetView.as_view(), name='site_builder_assets'),
path('metadata/', SiteBuilderMetadataView.as_view(), name='site_builder_metadata'),
]

View File

@@ -9,7 +9,14 @@ from igny8_core.api.base import SiteSectorModelViewSet
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
from igny8_core.api.response import success_response, error_response
from igny8_core.api.throttles import DebugScopedRateThrottle
from igny8_core.business.site_building.models import SiteBlueprint, PageBlueprint
from igny8_core.business.site_building.models import (
AudienceProfile,
BrandPersonality,
BusinessType,
HeroImageryDirection,
PageBlueprint,
SiteBlueprint,
)
from igny8_core.business.site_building.services import (
PageGenerationService,
SiteBuilderFileService,
@@ -18,6 +25,7 @@ from igny8_core.business.site_building.services import (
from igny8_core.modules.site_builder.serializers import (
PageBlueprintSerializer,
SiteBlueprintSerializer,
SiteBuilderMetadataSerializer,
)
@@ -205,3 +213,39 @@ class SiteAssetView(APIView):
return error_response('File not found', status.HTTP_404_NOT_FOUND, request)
class SiteBuilderMetadataView(APIView):
"""
Read-only metadata for Site Builder dropdowns.
"""
permission_classes = [IsAuthenticatedAndActive, IsEditorOrAbove]
def get(self, request, *args, **kwargs):
def serialize_queryset(qs):
return [
{
'id': item.id,
'name': item.name,
'description': item.description or '',
}
for item in qs
]
data = {
'business_types': serialize_queryset(
BusinessType.objects.filter(is_active=True).order_by('order', 'name')
),
'audience_profiles': serialize_queryset(
AudienceProfile.objects.filter(is_active=True).order_by('order', 'name')
),
'brand_personalities': serialize_queryset(
BrandPersonality.objects.filter(is_active=True).order_by('order', 'name')
),
'hero_imagery_directions': serialize_queryset(
HeroImageryDirection.objects.filter(is_active=True).order_by('order', 'name')
),
}
serializer = SiteBuilderMetadataSerializer(data)
return Response(serializer.data)