From 0f7d62095ec5ac1df2024bb9e4f8b64166afb5b0 Mon Sep 17 00:00:00 2001 From: Doljak Date: Mon, 20 Apr 2026 19:15:00 -0300 Subject: [PATCH 1/6] feat: add Waggy contact page theme override --- docs/issues-plans/issue-79-contact-page.md | 43 ++ .../doljak_theme/css/base/contact-styling.css | 454 ++++++++++++++++++ .../doljak_theme/doljak_theme.libraries.yml | 8 + .../custom/doljak_theme/doljak_theme.theme | 50 +- .../templates/contact-message-form.html.twig | 6 + .../templates/contact-site-page.html.twig | 84 ++++ ...ntact-message-waggy-contact-form.html.twig | 37 ++ 7 files changed, 681 insertions(+), 1 deletion(-) create mode 100644 docs/issues-plans/issue-79-contact-page.md create mode 100644 web/themes/custom/doljak_theme/css/base/contact-styling.css create mode 100644 web/themes/custom/doljak_theme/templates/contact-message-form.html.twig create mode 100644 web/themes/custom/doljak_theme/templates/contact-site-page.html.twig create mode 100644 web/themes/custom/doljak_theme/templates/form--contact-message-waggy-contact-form.html.twig diff --git a/docs/issues-plans/issue-79-contact-page.md b/docs/issues-plans/issue-79-contact-page.md new file mode 100644 index 0000000..74457fa --- /dev/null +++ b/docs/issues-plans/issue-79-contact-page.md @@ -0,0 +1,43 @@ +--- +issue: 79 +title: "[Theme + Form API] Contact page — Twig template, CSS layout and Drupal form integration" +branch: feat/contact-page-79-twig-template-css-layout-and-drupal-form-integration +status: in-progress +last_updated: 04-20-2026 +--- + +# Issue #79 — [Theme + Form API] Contact page — Twig template, CSS layout and Drupal form integration + +## Objective +Implement the Waggy Contact page at /contact by creating a custom Twig template for the contact form, applying CSS layout aligned with the Waggy Paper design, and wiring Drupal's core contact form fields into the styled markup. + +## Scope +- Create contact-site-page.html.twig in doljak_theme overriding the /contact page +- Create contact-message-form.html.twig rendering individual form fields +- Apply CSS for the contact page layout (form card, fields, labels, submit button) +- Cover desktop, tablet and mobile breakpoints +- Register the contact library in doljak_theme.libraries.yml and attach via template +- Validate the form submits correctly and Mailpit captures the email + +## Status +> Atualizado em: 04-20-2026 + +- [x] contact-site-page.html.twig criado +- [x] contact-message-form.html.twig criado +- [x] CSS do layout de contato aplicado (desktop, tablet, mobile) +- [x] Biblioteca registrada em libraries.yml e anexada via template +- [ ] Envio validado no Mailpit + +## Notes +- Template suggestions: contact-site-page.html.twig > contact-message-form.html.twig +- Variáveis do form: form.name, form.mail, form.subject, form.message, form.actions +- form_id gerado: contact_message_waggy_contact_form +- Usar {{ attach_library('doljak_theme/contact') }} no template +- Os templates de auth (login/register/pass) já existem como HTML estático — hook_form_alter pendente para issue futura + +## Progress log +- 04-20-2026: criados `templates/contact-site-page.html.twig`, `templates/contact-message-form.html.twig` e o fallback funcional `templates/form--contact-message-waggy-contact-form.html.twig` +- 04-20-2026: criada a library `contact` em `doljak_theme.libraries.yml` com `css/base/contact-styling.css` +- 04-20-2026: `doljak_theme.theme` atualizado com hook de tema/preprocess para o form de contato +- 04-20-2026: cache rebuild executado via DDEV com sucesso +- 04-20-2026: validação HTTP/Mailpit pendente porque o projeto DDEV ativo está ancorado na worktree `main`, não na `worktree-contact-page-79` diff --git a/web/themes/custom/doljak_theme/css/base/contact-styling.css b/web/themes/custom/doljak_theme/css/base/contact-styling.css new file mode 100644 index 0000000..433cb68 --- /dev/null +++ b/web/themes/custom/doljak_theme/css/base/contact-styling.css @@ -0,0 +1,454 @@ +/* ========================================================================== + CONTACT PAGE + ========================================================================== */ + +.contact-page { + padding: 56px var(--gutter) 72px; +} + +.contact-page__inner { + align-items: center; + display: grid; + gap: 56px; + grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); + margin: 0 auto; + max-width: var(--container-inner); +} + +.contact-page__hero { + display: flex; + flex-direction: column; + gap: var(--space-5); + max-width: 560px; +} + +.contact-page__eyebrow { + color: var(--color-accent-dark); + font-family: var(--font-body); + font-size: var(--text-xs); + font-weight: var(--font-medium); + letter-spacing: 0.2em; + text-transform: uppercase; +} + +.contact-page__headline { + color: var(--color-text-primary); + font-family: var(--font-display); + font-size: clamp(44px, 5vw, 72px); + font-weight: var(--font-regular); + line-height: 0.98; + margin: 0; + max-width: 720px; +} + +.contact-page__description, +.contact-page__note { + color: var(--color-text-secondary); + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.75; + margin: 0; +} + +.contact-page__description { + max-width: 540px; +} + +.contact-page__meta { + color: var(--color-text-primary); + font-family: var(--font-body); + font-size: var(--text-base); + font-weight: var(--font-medium); + line-height: 1.6; + margin: 0; +} + +.contact-page__stats { + display: flex; + flex-wrap: wrap; + gap: var(--space-4); +} + +.contact-page__stat { + border: 1px solid var(--color-border); + border-radius: 24px; + display: flex; + flex: 0 0 220px; + flex-direction: column; + gap: var(--space-2); + min-height: 132px; + padding: 22px 20px; +} + +.contact-page__stat--warm { + background: #F6EEDC; +} + +.contact-page__stat--soft { + background: #F3E9DE; +} + +.contact-page__stat strong { + color: var(--color-text-primary); + font-family: var(--font-display); + font-size: 30px; + font-weight: var(--font-regular); + line-height: 1.05; +} + +.contact-page__stat span { + color: var(--color-text-secondary); + font-family: var(--font-body); + font-size: var(--text-sm); + line-height: 1.55; +} + +.contact-page__card { + background: #ffffff; + border-radius: 32px; + box-shadow: var(--shadow-lg); + display: flex; + flex-direction: column; + gap: var(--space-6); + padding: 34px; +} + +.contact-page__card-header { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.contact-page__card-title { + color: var(--color-text-primary); + font-family: var(--font-display); + font-size: clamp(38px, 3vw, 56px); + font-weight: var(--font-regular); + line-height: 1.02; + margin: 0; +} + +.contact-page__card-subtitle { + color: var(--color-text-secondary); + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.7; + margin: 0; + max-width: 440px; +} + +.contact-page__form { + display: flex; + flex-direction: column; + gap: var(--space-5); +} + +.contact-page__form > .js-form-item, +.contact-page__form > .form-item, +.contact-page__field .form-item, +.contact-page__copy .form-item { + display: flex; + flex-direction: column; + gap: var(--space-2); + margin: 0; +} + +.contact-page__form > .js-form-item:not(.js-form-type-checkbox):not(.form-type-checkbox), +.contact-page__form > .form-item:not(.js-form-type-checkbox):not(.form-type-checkbox) { + width: 100%; +} + +.contact-page__form > .js-form-type-checkbox, +.contact-page__form > .form-type-checkbox { + align-items: flex-start; +} + +.contact-page__form label, +.contact-page__field label, +.contact-page__copy label { + color: var(--color-text-primary); + font-family: var(--font-body); + font-size: var(--text-sm); + font-weight: var(--font-medium); + line-height: 1.4; + margin: 0; +} + +.contact-page__form input:not([type='checkbox']):not([type='submit']), +.contact-page__form input.form-text, +.contact-page__form input.form-email, +.contact-page__form textarea, +.contact-page__field input:not([type='checkbox']), +.contact-page__field input.form-text, +.contact-page__field input.form-email, +.contact-page__field textarea { + background: var(--color-bg-hero); + border: 1px solid var(--color-border); + border-radius: 18px; + color: var(--color-text-primary); + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.5; + min-height: 56px; + outline: none; + padding: var(--space-4) var(--space-5); + transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast); + width: 100%; +} + +.contact-page__form textarea, +.contact-page__field textarea { + background: #ffffff; + border-radius: 22px; + min-height: 148px; + resize: vertical; +} + +.contact-page__form .js-form-type-item > :not(label):not(.form-item__error-message):not(.form-item__description), +.contact-page__form .form-type-item > :not(label):not(.form-item__error-message):not(.form-item__description), +.contact-page__field .js-form-type-item > :not(label):not(.form-item__error-message):not(.form-item__description), +.contact-page__field .form-type-item > :not(label):not(.form-item__error-message):not(.form-item__description) { + align-items: center; + background: var(--color-bg-hero); + border: 1px solid var(--color-border); + border-radius: 18px; + color: var(--color-text-primary); + display: flex; + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.5; + min-height: 56px; + padding: var(--space-4) var(--space-5); +} + +.contact-page__form input::placeholder, +.contact-page__form textarea::placeholder, +.contact-page__field input::placeholder, +.contact-page__field textarea::placeholder { + color: var(--color-text-light); +} + +.contact-page__form input:focus, +.contact-page__form textarea:focus, +.contact-page__field input:focus, +.contact-page__field textarea:focus { + background: #ffffff; + border-color: var(--color-accent); + box-shadow: 0 0 0 3px rgba(222, 156, 111, 0.15); +} + +.contact-page__form .form-item__error-message, +.contact-page__field .form-item__error-message, +.contact-page__copy .form-item__error-message { + color: #B5483F; + font-family: var(--font-body); + font-size: var(--text-xs); + line-height: 1.5; +} + +.contact-page__copy .form-item { + align-items: flex-start; +} + +.contact-page__form input[type='checkbox'], +.contact-page__copy input[type='checkbox'] { + accent-color: var(--color-accent); + height: 18px; + width: 18px; +} + +.contact-page__form .js-form-type-checkbox label, +.contact-page__form .form-type-checkbox label, +.contact-page__copy label { + align-items: center; + color: var(--color-text-secondary); + cursor: pointer; + display: inline-flex; + gap: var(--space-2); +} + +.contact-page__actions { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); +} + +.contact-page__form .form-actions { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); + margin: 0; +} + +.contact-page__action { + display: flex; +} + +.contact-page__action--primary { + flex: 1 1 240px; +} + +.contact-page__action--secondary { + flex: 0 0 138px; +} + +.contact-page__form .form-actions .button, +.contact-page__form .form-actions .form-submit, +.contact-page__button { + align-items: center; + appearance: none; + border: 1px solid transparent; + border-radius: var(--radius-pill); + cursor: pointer; + display: inline-flex; + font-family: var(--font-body); + font-size: var(--text-base); + font-weight: var(--font-semibold); + justify-content: center; + min-height: 56px; + padding: 0 var(--space-8); + transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast), transform var(--transition-fast); + width: 100%; +} + +.contact-page__form .form-actions .button:first-child, +.contact-page__form .form-actions .form-submit:first-child, +.contact-page__form .form-actions .button + .button, +.contact-page__form .form-actions .form-submit + .form-submit { + flex: 1 1 auto; +} + +.contact-page__form .form-actions .button:hover, +.contact-page__form .form-actions .button:focus-visible, +.contact-page__form .form-actions .form-submit:hover, +.contact-page__form .form-actions .form-submit:focus-visible, +.contact-page__button:hover, +.contact-page__button:focus-visible { + outline: none; + transform: translateY(-1px); +} + +.contact-page__form .form-actions .button:first-child, +.contact-page__form .form-actions .form-submit:first-child, +.contact-page__button--primary { + background: var(--color-accent); + border-color: var(--color-accent); + color: #ffffff; +} + +.contact-page__form .form-actions .button:hover, +.contact-page__form .form-actions .button:focus-visible, +.contact-page__form .form-actions .form-submit:hover, +.contact-page__form .form-actions .form-submit:focus-visible, +.contact-page__button--primary:hover, +.contact-page__button--primary:focus-visible { + background: var(--color-accent-dark); + border-color: var(--color-accent-dark); +} + +.contact-page__form .form-actions .button + .button, +.contact-page__form .form-actions .form-submit + .form-submit, +.contact-page__button--secondary { + background: #ffffff; + border-color: var(--color-border); + color: var(--color-text-primary); +} + +.contact-page__form .form-actions .button + .button:hover, +.contact-page__form .form-actions .button + .button:focus-visible, +.contact-page__form .form-actions .form-submit + .form-submit:hover, +.contact-page__form .form-actions .form-submit + .form-submit:focus-visible, +.contact-page__button--secondary:hover, +.contact-page__button--secondary:focus-visible { + border-color: var(--color-accent); + color: var(--color-accent-dark); +} + +.contact-page__form .preview, +.contact-page__preview { + background: #FFF8F1; + border: 1px solid var(--color-border); + border-radius: 24px; + padding: var(--space-5); +} + +.contact-page__form .preview .contact-message, +.contact-page__preview .contact-message { + margin: 0; +} + +@media (max-width: 1199px) { + .contact-page { + padding: 48px var(--space-8) 56px; + } + + .contact-page__inner { + gap: var(--space-10); + grid-template-columns: 1fr; + max-width: 704px; + } + + .contact-page__hero, + .contact-page__card { + max-width: none; + } + + .contact-page__headline { + font-size: 56px; + } +} + +@media (max-width: 767px) { + .contact-page { + padding: 28px var(--space-5) 40px; + } + + .contact-page__inner { + gap: var(--space-6); + } + + .contact-page__headline { + font-size: var(--text-3xl); + } + + .contact-page__description, + .contact-page__meta, + .contact-page__note, + .contact-page__card-subtitle { + font-size: var(--text-sm); + } + + .contact-page__stats { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .contact-page__stat { + flex: 1 1 auto; + min-height: 108px; + padding: var(--space-4); + } + + .contact-page__stat strong { + font-size: var(--text-xl); + } + + .contact-page__card { + border-radius: 28px; + padding: var(--space-8) var(--space-6); + } + + .contact-page__card-title { + font-size: var(--text-2xl); + } + + .contact-page__form .form-actions, + .contact-page__actions { + flex-direction: column; + } + + .contact-page__action--primary, + .contact-page__action--secondary { + flex: 1 1 auto; + } +} diff --git a/web/themes/custom/doljak_theme/doljak_theme.libraries.yml b/web/themes/custom/doljak_theme/doljak_theme.libraries.yml index 451f50a..edb9055 100644 --- a/web/themes/custom/doljak_theme/doljak_theme.libraries.yml +++ b/web/themes/custom/doljak_theme/doljak_theme.libraries.yml @@ -20,3 +20,11 @@ global-styling: dependencies: - core/drupal - core/once + +contact: + version: VERSION + css: + base: + css/base/contact-styling.css: {} + dependencies: + - doljak_theme/global-styling diff --git a/web/themes/custom/doljak_theme/doljak_theme.theme b/web/themes/custom/doljak_theme/doljak_theme.theme index 1b9f473..59879a4 100644 --- a/web/themes/custom/doljak_theme/doljak_theme.theme +++ b/web/themes/custom/doljak_theme/doljak_theme.theme @@ -6,6 +6,20 @@ */ use Drupal\Core\Cache\Cache; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Template\Attribute; + +/** + * Implements hook_theme(). + */ +function doljak_theme_theme(): array { + return [ + 'contact_message_form' => [ + 'render element' => 'form', + 'template' => 'contact-message-form', + ], + ]; +} /** * Implements hook_preprocess_node(). @@ -127,4 +141,38 @@ function _doljak_theme_preprocess_footer(array &$variables): void { $variables['footer_newsletter_title'] = $newsletter->get('field_cta_text')->value ?? ''; $variables['footer_newsletter_copy'] = $newsletter->get('field_cta_subtext')->value ?? ''; } -} \ No newline at end of file +} + +/** + * Preprocess do template customizado do form de contato. + */ +function doljak_theme_preprocess_contact_message_form(array &$variables): void { + $form = $variables['form']; + $variables['attributes'] = new Attribute($form['#attributes'] ?? []); + $variables['contact_form_title'] = $form['#title'] ?? t('Contact'); +} + +/** + * Implements hook_form_FORM_ID_alter() for the site-wide contact form. + */ +function doljak_theme_form_contact_message_waggy_contact_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void { + unset($form['#theme']); + $form['#theme_wrappers'] = ['contact_message_form']; + + if (!empty($form['preview']['#attributes']['class'])) { + $form['preview']['#attributes']['class'][] = 'contact-page__preview'; + } + elseif (isset($form['preview'])) { + $form['preview']['#attributes']['class'] = ['contact-page__preview']; + } + + if (isset($form['actions']['submit'])) { + $form['actions']['submit']['#attributes']['class'][] = 'contact-page__button'; + $form['actions']['submit']['#attributes']['class'][] = 'contact-page__button--primary'; + } + + if (isset($form['actions']['preview'])) { + $form['actions']['preview']['#attributes']['class'][] = 'contact-page__button'; + $form['actions']['preview']['#attributes']['class'][] = 'contact-page__button--secondary'; + } +} diff --git a/web/themes/custom/doljak_theme/templates/contact-message-form.html.twig b/web/themes/custom/doljak_theme/templates/contact-message-form.html.twig new file mode 100644 index 0000000..c202867 --- /dev/null +++ b/web/themes/custom/doljak_theme/templates/contact-message-form.html.twig @@ -0,0 +1,6 @@ +{{ attach_library('doljak_theme/contact') }} +{% include '@doljak_theme/contact-site-page.html.twig' with { + form: form, + attributes: attributes, + contact_form_title: contact_form_title +} only %} diff --git a/web/themes/custom/doljak_theme/templates/contact-site-page.html.twig b/web/themes/custom/doljak_theme/templates/contact-site-page.html.twig new file mode 100644 index 0000000..fbd0ccb --- /dev/null +++ b/web/themes/custom/doljak_theme/templates/contact-site-page.html.twig @@ -0,0 +1,84 @@ +
+
+
+ Contact Waggy +

Let us know what your pet needs.

+

+ Questions about orders, product guidance or routines. Fill the form and the team gets back within one business day. +

+ +
+
+ 1 day + average reply on business days +
+
+ Real team + orders, returns and care support +
+
+ +

Monday to Friday · 9am to 6pm

+

For urgent order issues, include your order number in the subject.

+
+ +
+
+

{{ contact_form_title }}

+

Use the same Drupal fields from the page with a layout aligned to the Waggy design system.

+
+ + + {% if form.preview is defined %} + {{ form.preview }} + {% endif %} + + {% if form.name is defined %} +
+ {{ form.name }} +
+ {% endif %} + + {% if form.mail is defined %} +
+ {{ form.mail }} +
+ {% endif %} + + {% if form.subject is defined %} +
+ {{ form.subject }} +
+ {% endif %} + + {% if form.message is defined %} +
+ {{ form.message }} +
+ {% endif %} + + {% if form.copy is defined %} +
+ {{ form.copy }} +
+ {% endif %} + +
+ {% if form.actions.submit is defined %} +
+ {{ form.actions.submit }} +
+ {% endif %} + + {% if form.actions.preview is defined %} +
+ {{ form.actions.preview }} +
+ {% endif %} +
+ + {{ form|without('preview', 'name', 'mail', 'subject', 'message', 'copy', 'actions') }} + +
+
+
diff --git a/web/themes/custom/doljak_theme/templates/form--contact-message-waggy-contact-form.html.twig b/web/themes/custom/doljak_theme/templates/form--contact-message-waggy-contact-form.html.twig new file mode 100644 index 0000000..572eb4d --- /dev/null +++ b/web/themes/custom/doljak_theme/templates/form--contact-message-waggy-contact-form.html.twig @@ -0,0 +1,37 @@ +{{ attach_library('doljak_theme/contact') }} +
+
+
+ Contact Waggy +

Let us know what your pet needs.

+

+ Questions about orders, product guidance or routines. Fill the form and the team gets back within one business day. +

+ +
+
+ 1 day + average reply on business days +
+
+ Real team + orders, returns and care support +
+
+ +

Monday to Friday · 9am to 6pm

+

For urgent order issues, include your order number in the subject.

+
+ +
+
+

Contact

+

Use the same Drupal fields from the page with a layout aligned to the Waggy design system.

+
+ + + {{ children }} + +
+
+
From bedc729a6830e63e977dea230271ace358724ba9 Mon Sep 17 00:00:00 2001 From: Doljak Date: Mon, 20 Apr 2026 19:22:49 -0300 Subject: [PATCH 2/6] feat: resolving merge conflict --- web/themes/custom/doljak_theme/doljak_theme.theme | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/web/themes/custom/doljak_theme/doljak_theme.theme b/web/themes/custom/doljak_theme/doljak_theme.theme index 59879a4..02e669e 100644 --- a/web/themes/custom/doljak_theme/doljak_theme.theme +++ b/web/themes/custom/doljak_theme/doljak_theme.theme @@ -42,7 +42,7 @@ function doljak_theme_preprocess_node(array &$variables): void { * - article_date : data formatada (ex: "April 02, 2026") * - article_reading_time : tempo de leitura (ex: "8 min read") * - article_category : label da categoria (ex: "DAILY RHYTHM") - * - article_image_url : URL absoluta da imagem hero + * - article_image_url : URL absoluta da imagem hero. */ function _doljak_theme_preprocess_article(array &$variables): void { /** @var \Drupal\node\NodeInterface $node */ @@ -88,13 +88,13 @@ function _doljak_theme_preprocess_article(array &$variables): void { * - footer_social_links : array com label, href, icon * - footer_columns : array com title e links para Quick Links e Help Centre * - footer_newsletter_title : texto do CTA - * - footer_newsletter_copy : subtexto do CTA + * - footer_newsletter_copy : subtexto do CTA. */ function _doljak_theme_preprocess_footer(array &$variables): void { /** @var \Drupal\node\NodeInterface $node */ $node = $variables['node']; - // Brand — social links + // Brand — social links. $brand = $node->get('field_footer_brand')->entity; if ($brand) { $social_links = []; @@ -109,7 +109,7 @@ function _doljak_theme_preprocess_footer(array &$variables): void { $variables['footer_social_links'] = $social_links; } - // Quick Links + // Quick Links. $quick = $node->get('field_footer_quick_links')->entity; if ($quick) { $links = []; @@ -122,7 +122,7 @@ function _doljak_theme_preprocess_footer(array &$variables): void { ]; } - // Help Centre + // Help Centre. $help = $node->get('field_footer_help_center')->entity; if ($help) { $links = []; @@ -135,7 +135,7 @@ function _doljak_theme_preprocess_footer(array &$variables): void { ]; } - // Newsletter + // Newsletter. $newsletter = $node->get('field_footer_newsletter')->entity; if ($newsletter) { $variables['footer_newsletter_title'] = $newsletter->get('field_cta_text')->value ?? ''; @@ -176,3 +176,4 @@ function doljak_theme_form_contact_message_waggy_contact_form_alter(array &$form $form['actions']['preview']['#attributes']['class'][] = 'contact-page__button--secondary'; } } + From 0536e837e0e4236802d37727850c7693638a2e57 Mon Sep 17 00:00:00 2001 From: Doljak Date: Mon, 20 Apr 2026 19:54:34 -0300 Subject: [PATCH 3/6] fix: contact form duplication --- ...ntact-message-waggy-contact-form.html.twig | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 web/themes/custom/doljak_theme/templates/form--contact-message-waggy-contact-form.html.twig diff --git a/web/themes/custom/doljak_theme/templates/form--contact-message-waggy-contact-form.html.twig b/web/themes/custom/doljak_theme/templates/form--contact-message-waggy-contact-form.html.twig deleted file mode 100644 index 572eb4d..0000000 --- a/web/themes/custom/doljak_theme/templates/form--contact-message-waggy-contact-form.html.twig +++ /dev/null @@ -1,37 +0,0 @@ -{{ attach_library('doljak_theme/contact') }} -
-
-
- Contact Waggy -

Let us know what your pet needs.

-

- Questions about orders, product guidance or routines. Fill the form and the team gets back within one business day. -

- -
-
- 1 day - average reply on business days -
-
- Real team - orders, returns and care support -
-
- -

Monday to Friday · 9am to 6pm

-

For urgent order issues, include your order number in the subject.

-
- -
-
-

Contact

-

Use the same Drupal fields from the page with a layout aligned to the Waggy design system.

-
- - - {{ children }} - -
-
-
From ab48b542cfbab508d758f4c7ab6f445d17db4819 Mon Sep 17 00:00:00 2001 From: Doljak Date: Wed, 6 May 2026 18:50:13 -0300 Subject: [PATCH 4/6] fix: render Drupal hidden form fields explicitly in contact page template --- .../custom/doljak_theme/templates/contact-site-page.html.twig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/themes/custom/doljak_theme/templates/contact-site-page.html.twig b/web/themes/custom/doljak_theme/templates/contact-site-page.html.twig index fbd0ccb..d49670d 100644 --- a/web/themes/custom/doljak_theme/templates/contact-site-page.html.twig +++ b/web/themes/custom/doljak_theme/templates/contact-site-page.html.twig @@ -77,7 +77,9 @@ {% endif %} - {{ form|without('preview', 'name', 'mail', 'subject', 'message', 'copy', 'actions') }} + {{ form.form_token }} + {{ form.form_build_id }} + {{ form.form_id }} From 361a884f0d0917dba2c3e8a7554f6a6de1876225 Mon Sep 17 00:00:00 2001 From: Doljak Date: Wed, 6 May 2026 18:54:49 -0300 Subject: [PATCH 5/6] feat: add waggy_forms module with newsletter form and block --- .../src/Form/NewsletterSubscribeForm.php | 61 +++++++++++++++++++ .../src/Plugin/Block/NewsletterBlock.php | 54 ++++++++++++++++ .../custom/waggy_forms/waggy_forms.info.yml | 5 ++ .../waggy_forms/waggy_forms.routing.yml | 7 +++ 4 files changed, 127 insertions(+) create mode 100644 web/modules/custom/waggy_forms/src/Form/NewsletterSubscribeForm.php create mode 100644 web/modules/custom/waggy_forms/src/Plugin/Block/NewsletterBlock.php create mode 100644 web/modules/custom/waggy_forms/waggy_forms.info.yml create mode 100644 web/modules/custom/waggy_forms/waggy_forms.routing.yml diff --git a/web/modules/custom/waggy_forms/src/Form/NewsletterSubscribeForm.php b/web/modules/custom/waggy_forms/src/Form/NewsletterSubscribeForm.php new file mode 100644 index 0000000..cd039b5 --- /dev/null +++ b/web/modules/custom/waggy_forms/src/Form/NewsletterSubscribeForm.php @@ -0,0 +1,61 @@ + 'email', + '#title' => $this->t('Email address'), + '#placeholder' => $this->t('you@example.com'), + '#required' => TRUE, + ]; + + $form['actions'] = [ + '#type' => 'actions', + ]; + + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Subscribe'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state): void { + $email = $form_state->getValue('email'); + if (!\Drupal::service('email.validator')->isValid($email)) { + $form_state->setErrorByName('email', $this->t('Please enter a valid email address.')); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + $email = $form_state->getValue('email'); + $this->messenger()->addStatus($this->t('Thank you! @email has been subscribed.', ['@email' => $email])); + } + +} diff --git a/web/modules/custom/waggy_forms/src/Plugin/Block/NewsletterBlock.php b/web/modules/custom/waggy_forms/src/Plugin/Block/NewsletterBlock.php new file mode 100644 index 0000000..1cb9403 --- /dev/null +++ b/web/modules/custom/waggy_forms/src/Plugin/Block/NewsletterBlock.php @@ -0,0 +1,54 @@ +formBuilder = $form_builder; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('form_builder'), + ); + } + + /** + * {@inheritdoc} + */ + public function build(): array { + return $this->formBuilder->getForm(NewsletterSubscribeForm::class); + } + +} diff --git a/web/modules/custom/waggy_forms/waggy_forms.info.yml b/web/modules/custom/waggy_forms/waggy_forms.info.yml new file mode 100644 index 0000000..61101ee --- /dev/null +++ b/web/modules/custom/waggy_forms/waggy_forms.info.yml @@ -0,0 +1,5 @@ +name: Waggy Forms +type: module +description: Custom forms for Waggy Pet Shop — newsletter, contact and auth integrations. +core_version_requirement: ^10 || ^11 +package: Waggy diff --git a/web/modules/custom/waggy_forms/waggy_forms.routing.yml b/web/modules/custom/waggy_forms/waggy_forms.routing.yml new file mode 100644 index 0000000..cceac7b --- /dev/null +++ b/web/modules/custom/waggy_forms/waggy_forms.routing.yml @@ -0,0 +1,7 @@ +waggy_forms.newsletter: + path: '/newsletter' + defaults: + _form: '\Drupal\waggy_forms\Form\NewsletterSubscribeForm' + _title: 'Subscribe to Waggy Newsletter' + requirements: + _permission: 'access content' From 84b3fe5e5c15c175c9ff186506ecccb5425bb555 Mon Sep 17 00:00:00 2001 From: Doljak Date: Thu, 7 May 2026 09:43:36 -0300 Subject: [PATCH 6/6] fix: add action and method attributes to contact form preprocess Closes #79 --- docs/issues-plans/issue-79-contact-page.md | 6 +++--- web/themes/custom/doljak_theme/doljak_theme.theme | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/issues-plans/issue-79-contact-page.md b/docs/issues-plans/issue-79-contact-page.md index 74457fa..030f5dc 100644 --- a/docs/issues-plans/issue-79-contact-page.md +++ b/docs/issues-plans/issue-79-contact-page.md @@ -2,8 +2,8 @@ issue: 79 title: "[Theme + Form API] Contact page — Twig template, CSS layout and Drupal form integration" branch: feat/contact-page-79-twig-template-css-layout-and-drupal-form-integration -status: in-progress -last_updated: 04-20-2026 +status: closed +last_updated: 05-07-2026 --- # Issue #79 — [Theme + Form API] Contact page — Twig template, CSS layout and Drupal form integration @@ -26,7 +26,7 @@ Implement the Waggy Contact page at /contact by creating a custom Twig template - [x] contact-message-form.html.twig criado - [x] CSS do layout de contato aplicado (desktop, tablet, mobile) - [x] Biblioteca registrada em libraries.yml e anexada via template -- [ ] Envio validado no Mailpit +- [x] Envio validado no Mailpit ## Notes - Template suggestions: contact-site-page.html.twig > contact-message-form.html.twig diff --git a/web/themes/custom/doljak_theme/doljak_theme.theme b/web/themes/custom/doljak_theme/doljak_theme.theme index 02e669e..ac85b2c 100644 --- a/web/themes/custom/doljak_theme/doljak_theme.theme +++ b/web/themes/custom/doljak_theme/doljak_theme.theme @@ -148,7 +148,12 @@ function _doljak_theme_preprocess_footer(array &$variables): void { */ function doljak_theme_preprocess_contact_message_form(array &$variables): void { $form = $variables['form']; - $variables['attributes'] = new Attribute($form['#attributes'] ?? []); + $attributes = new Attribute($form['#attributes'] ?? []); + if (!empty($form['#action'])) { + $attributes->setAttribute('action', $form['#action']); + } + $attributes->setAttribute('method', $form['#method'] ?? 'post'); + $variables['attributes'] = $attributes; $variables['contact_form_title'] = $form['#title'] ?? t('Contact'); }