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 4ed0ca0..02e669e 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(). @@ -128,3 +142,38 @@ function _doljak_theme_preprocess_footer(array &$variables): void { $variables['footer_newsletter_copy'] = $newsletter->get('field_cta_subtext')->value ?? ''; } } + +/** + * 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..d49670d --- /dev/null +++ b/web/themes/custom/doljak_theme/templates/contact-site-page.html.twig @@ -0,0 +1,86 @@ +
+
+
+ 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.form_token }} + {{ form.form_build_id }} + {{ form.form_id }} + +
+
+