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:
Binary file not shown.
@@ -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),
|
||||
]
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user