From 668f5128e5e3bc0b1d9531209637795ce5905f0d Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Wed, 11 Feb 2026 16:19:51 +0000 Subject: [PATCH] dynamic GID --- app/GirlsInDigitalPage.php | 14 +++ app/Nova/GirlsInDigitalPage.php | 175 +++++++++++++++++++------------- 2 files changed, 119 insertions(+), 70 deletions(-) diff --git a/app/GirlsInDigitalPage.php b/app/GirlsInDigitalPage.php index 1008befd8..0c1760d93 100644 --- a/app/GirlsInDigitalPage.php +++ b/app/GirlsInDigitalPage.php @@ -63,6 +63,20 @@ class GirlsInDigitalPage extends Model 'locale_overrides' => 'array', ]; + /** + * Prevent relationship / virtual attributes from being persisted (e.g. from Nova form). + */ + public function setAttribute($key, $value) + { + if (in_array($key, ['faqItems'], true)) { + return $this; + } + if (str_starts_with($key, 'button_') || str_starts_with($key, 'locale_')) { + return $this; + } + return parent::setAttribute($key, $value); + } + public function buttons() { return $this->hasMany(GirlsInDigitalButton::class, 'page_id')->orderBy('position'); diff --git a/app/Nova/GirlsInDigitalPage.php b/app/Nova/GirlsInDigitalPage.php index 82b5f5505..3778dd9f4 100644 --- a/app/Nova/GirlsInDigitalPage.php +++ b/app/Nova/GirlsInDigitalPage.php @@ -72,6 +72,14 @@ public static function uriKey(): string return 'girls-in-digital-page'; } + /** + * Permissive update rules so form submission does not fail on strict validation. + */ + public static function rulesForUpdate(NovaRequest $request, $resource = null): array + { + return []; + } + public static function indexQuery(NovaRequest $request, $query) { return $query->where('id', 1); @@ -156,113 +164,140 @@ public function fields(Request $request): array } } + /** + * Build locale fields for "Other languages" (non-English). English is always first in each section. + */ + private function otherLocaleFields(array $locales, array $specs): array + { + $fields = []; + foreach ($locales as $locale) { + if ($locale === 'en') { + continue; + } + foreach ($specs as $spec) { + $fields[] = $this->localeField( + $spec['label'] . ' (' . strtoupper($locale) . ')', + $locale, + $spec['key'], + $spec['textarea'] ?? false, + $spec['rich'] ?? false + ); + } + } + return $fields; + } + private function buildFields(Request $request): array { $locales = self::localesSorted(); + // — Hero: English first, then other languages, then buttons $heroFields = [ Boolean::make('Use dynamic content for this section', 'hero_dynamic') ->help('When on, hero uses the content below.'), - Trix::make('Hero intro (default)', 'hero_intro')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), - Text::make('Hero video URL (default)', 'hero_video_url')->nullable()->help('YouTube embed URL.'), - Text::make('Hero image (default)', 'hero_image')->nullable(), + \Laravel\Nova\Fields\Heading::make('English'), + Trix::make('Hero intro (English)', 'hero_intro')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), + Text::make('Hero video URL (English)', 'hero_video_url')->nullable()->help('YouTube embed URL.'), + Text::make('Hero image (English)', 'hero_image')->nullable(), ]; - foreach ($locales as $locale) { - if ($locale === 'en') { - continue; - } - $heroFields[] = $this->localeField('Hero intro (' . strtoupper($locale) . ')', $locale, 'hero_intro', true, true); - $heroFields[] = $this->localeField('Hero video URL (' . strtoupper($locale) . ')', $locale, 'hero_video_url', false); - } + $heroFields[] = \Laravel\Nova\Fields\Heading::make('Other languages'); + $heroFields = array_merge($heroFields, $this->otherLocaleFields($locales, [ + ['label' => 'Hero intro', 'key' => 'hero_intro', 'textarea' => true, 'rich' => true], + ['label' => 'Hero video URL', 'key' => 'hero_video_url'], + ])); foreach (self::buttonKeysBySection()['hero'] as $key => $title) { $heroFields = array_merge($heroFields, $this->buttonFields($key, $title)); } + // — About: English first, then other languages, then buttons $aboutFields = [ Boolean::make('Use dynamic content for this section', 'about_dynamic'), - Text::make('About title (default)', 'about_title')->nullable(), - Trix::make('About description 1 (default)', 'about_description_1')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), - Trix::make('About description 2 (default)', 'about_description_2')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), - Text::make('About image (default)', 'about_image')->nullable(), + \Laravel\Nova\Fields\Heading::make('English'), + Text::make('About title (English)', 'about_title')->nullable(), + Trix::make('About description 1 (English)', 'about_description_1')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), + Trix::make('About description 2 (English)', 'about_description_2')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), + Text::make('About image (English)', 'about_image')->nullable(), ]; - foreach ($locales as $locale) { - if ($locale === 'en') { - continue; - } - $aboutFields[] = $this->localeField('About title (' . strtoupper($locale) . ')', $locale, 'about_title', false); - $aboutFields[] = $this->localeField('About description 1 (' . strtoupper($locale) . ')', $locale, 'about_description_1', true, true); - $aboutFields[] = $this->localeField('About description 2 (' . strtoupper($locale) . ')', $locale, 'about_description_2', true, true); - } + $aboutFields[] = \Laravel\Nova\Fields\Heading::make('Other languages'); + $aboutFields = array_merge($aboutFields, $this->otherLocaleFields($locales, [ + ['label' => 'About title', 'key' => 'about_title'], + ['label' => 'About description 1', 'key' => 'about_description_1', 'textarea' => true, 'rich' => true], + ['label' => 'About description 2', 'key' => 'about_description_2', 'textarea' => true, 'rich' => true], + ])); foreach (self::buttonKeysBySection()['about'] as $key => $title) { $aboutFields = array_merge($aboutFields, $this->buttonFields($key, $title)); } + // — Resources: English first, then other languages, then buttons $resourcesFields = [ Boolean::make('Use dynamic content for this section', 'resources_dynamic'), - Text::make('Resources title (default)', 'resource_title')->nullable(), - Text::make('Young person / parent title (default)', 'resource_person_title')->nullable(), - Trix::make('Young person / parent description 1 (default)', 'resource_person_description_1')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), - Trix::make('Young person / parent description 2 (default)', 'resource_person_description_2')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), - Text::make('Young person / parent image (default)', 'resource_young_image')->nullable(), - Text::make('Educator title (default)', 'resource_educator_title')->nullable(), - Trix::make('Educator description (default)', 'resource_educator_description')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), - Text::make('Educator image (default)', 'resource_educator_image')->nullable(), + \Laravel\Nova\Fields\Heading::make('English'), + Text::make('Resources title (English)', 'resource_title')->nullable(), + Text::make('Young person / parent title (English)', 'resource_person_title')->nullable(), + Trix::make('Young person / parent description 1 (English)', 'resource_person_description_1')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), + Trix::make('Young person / parent description 2 (English)', 'resource_person_description_2')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), + Text::make('Young person / parent image (English)', 'resource_young_image')->nullable(), + Text::make('Educator title (English)', 'resource_educator_title')->nullable(), + Trix::make('Educator description (English)', 'resource_educator_description')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), + Text::make('Educator image (English)', 'resource_educator_image')->nullable(), ]; - foreach ($locales as $locale) { - if ($locale === 'en') { - continue; - } - $resourcesFields[] = $this->localeField('Resource title (' . strtoupper($locale) . ')', $locale, 'resource_title', false); - $resourcesFields[] = $this->localeField('Young person title (' . strtoupper($locale) . ')', $locale, 'resource_person_title', false); - $resourcesFields[] = $this->localeField('Young person description 1 (' . strtoupper($locale) . ')', $locale, 'resource_person_description_1', true, true); - $resourcesFields[] = $this->localeField('Young person description 2 (' . strtoupper($locale) . ')', $locale, 'resource_person_description_2', true, true); - $resourcesFields[] = $this->localeField('Educator title (' . strtoupper($locale) . ')', $locale, 'resource_educator_title', false); - $resourcesFields[] = $this->localeField('Educator description (' . strtoupper($locale) . ')', $locale, 'resource_educator_description', true, true); - } + $resourcesFields[] = \Laravel\Nova\Fields\Heading::make('Other languages'); + $resourcesFields = array_merge($resourcesFields, $this->otherLocaleFields($locales, [ + ['label' => 'Resource title', 'key' => 'resource_title'], + ['label' => 'Young person title', 'key' => 'resource_person_title'], + ['label' => 'Young person description 1', 'key' => 'resource_person_description_1', 'textarea' => true, 'rich' => true], + ['label' => 'Young person description 2', 'key' => 'resource_person_description_2', 'textarea' => true, 'rich' => true], + ['label' => 'Educator title', 'key' => 'resource_educator_title'], + ['label' => 'Educator description', 'key' => 'resource_educator_description', 'textarea' => true, 'rich' => true], + ])); foreach (self::buttonKeysBySection()['resources'] as $key => $title) { $resourcesFields = array_merge($resourcesFields, $this->buttonFields($key, $title)); } + // — Why Matters: English first, then other languages $mattersFields = [ Boolean::make('Use dynamic content for this section', 'matters_dynamic'), - Text::make('Section title (default)', 'matters_title')->nullable(), - Text::make('Graph 1 image (default)', 'matters_graph1_image')->nullable(), - Text::make('Graph 1 link (default)', 'matters_graph1_link')->nullable(), - Trix::make('Graph 1 caption (default)', 'matters_graph1_caption')->nullable(), - Text::make('Graph 2 image (default)', 'matters_graph2_image')->nullable(), - Text::make('Graph 2 link (default)', 'matters_graph2_link')->nullable(), - Trix::make('Graph 2 caption (default)', 'matters_graph2_caption')->nullable(), - Text::make('Graph 3 image (default)', 'matters_graph3_image')->nullable(), - Text::make('Graph 3 link (default)', 'matters_graph3_link')->nullable(), - Trix::make('Graph 3 caption (default)', 'matters_graph3_caption')->nullable(), - Trix::make('Paragraph 1 (default)', 'matters_paragraph_1')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), - Trix::make('Paragraph 2 (default)', 'matters_paragraph_2')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), + \Laravel\Nova\Fields\Heading::make('English'), + Text::make('Section title (English)', 'matters_title')->nullable(), + Text::make('Graph 1 image (English)', 'matters_graph1_image')->nullable(), + Text::make('Graph 1 link (English)', 'matters_graph1_link')->nullable(), + Trix::make('Graph 1 caption (English)', 'matters_graph1_caption')->nullable(), + Text::make('Graph 2 image (English)', 'matters_graph2_image')->nullable(), + Text::make('Graph 2 link (English)', 'matters_graph2_link')->nullable(), + Trix::make('Graph 2 caption (English)', 'matters_graph2_caption')->nullable(), + Text::make('Graph 3 image (English)', 'matters_graph3_image')->nullable(), + Text::make('Graph 3 link (English)', 'matters_graph3_link')->nullable(), + Trix::make('Graph 3 caption (English)', 'matters_graph3_caption')->nullable(), + Trix::make('Paragraph 1 (English)', 'matters_paragraph_1')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), + Trix::make('Paragraph 2 (English)', 'matters_paragraph_2')->nullable()->help('Use the toolbar for bold, italic, links, lists.'), ]; - foreach ($locales as $locale) { - if ($locale === 'en') { - continue; - } - $mattersFields[] = $this->localeField('Matters title (' . strtoupper($locale) . ')', $locale, 'matters_title', false); - $mattersFields[] = $this->localeField('Matters paragraph 1 (' . strtoupper($locale) . ')', $locale, 'matters_paragraph_1', true, true); - $mattersFields[] = $this->localeField('Matters paragraph 2 (' . strtoupper($locale) . ')', $locale, 'matters_paragraph_2', true, true); - } - + $mattersFields[] = \Laravel\Nova\Fields\Heading::make('Other languages'); + $mattersFields = array_merge($mattersFields, $this->otherLocaleFields($locales, [ + ['label' => 'Matters title', 'key' => 'matters_title'], + ['label' => 'Matters paragraph 1', 'key' => 'matters_paragraph_1', 'textarea' => true, 'rich' => true], + ['label' => 'Matters paragraph 2', 'key' => 'matters_paragraph_2', 'textarea' => true, 'rich' => true], + ])); + + // — FAQ: English first, then other languages, then FAQ items $faqFields = [ Boolean::make('Use dynamic content for this section', 'faq_dynamic'), - Text::make('FAQ section title (default)', 'faq_title')->nullable(), + \Laravel\Nova\Fields\Heading::make('English'), + Text::make('FAQ section title (English)', 'faq_title')->nullable(), ]; - foreach ($locales as $locale) { - if ($locale === 'en') { - continue; - } - $faqFields[] = $this->localeField('FAQ title (' . strtoupper($locale) . ')', $locale, 'faq_title', false); - } + $faqFields[] = \Laravel\Nova\Fields\Heading::make('Other languages'); + $faqFields = array_merge($faqFields, $this->otherLocaleFields($locales, [ + ['label' => 'FAQ title', 'key' => 'faq_title'], + ])); if (Schema::hasTable('girls_in_digital_faq_items')) { - $faqFields[] = HasMany::make('FAQ items', 'faqItems', GirlsInDigitalFaqItem::class); + $faqFields[] = HasMany::make('FAQ items', 'faqItems', GirlsInDigitalFaqItem::class) + ->fillUsing(function () { + // Do not set faqItems on the parent; children are managed separately. + }); } else { $faqFields[] = \Laravel\Nova\Fields\Heading::make('Run migration to enable FAQ items: php artisan migrate --path=database/migrations/2026_02_12_100000_create_girls_in_digital_faq_items_table.php'); } + // All sections are collapsible accordions, closed by default return [ ID::make()->onlyOnForms(),