# Nuxt

> Um componente Nuxt que envia como um formulário comum antes da hidratação e com $fetch depois, com o id do formulário na runtime config. Funciona com nuxt generate.

O Nuxt renderiza o formulário no servidor, então o HTML que chega ao navegador já é um formulário funcional. Até o app hidratar, ele envia como qualquer formulário HTML; depois disso, o componente assume e mostra o resultado no lugar. Nada roda no seu servidor, então também funciona como site estático gerado com `nuxt generate`.

Para enviar a partir de uma server route do Nuxt com uma chave secreta, veja [Vue](https://ikiform.com/pt-br/docs/guides/vue.md#nuxt).

## Configuração

### Passo 1: Coloque o id do formulário na runtime config

`nuxt.config.ts`:

```ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      ikiformFormId: "frm_xxxxxxxxxxxxxxxx",
    },
  },
});
```

Sobrescreva por ambiente com `NUXT_PUBLIC_IKIFORM_FORM_ID`. Com `nuxt generate`, o valor é fixado no build, então defina a variável no ambiente de build.

### Passo 2: Adicione o componente

`app/components/ContactForm.vue`:

```vue
<script setup lang="ts">
type Result =
  | { ok: true; id: string }
  | { ok: false; error: { message: string; fields?: Record<string, string[]> } };

const config = useRuntimeConfig();
const endpoint = `https://ikiform.com/f/${config.public.ikiformFormId}`;

const status = ref<"idle" | "sending" | "sent" | "error">("idle");
const errors = ref<Record<string, string[]>>({});
const message = ref("");

async function onSubmit(event: Event) {
  status.value = "sending";
  const result = await $fetch<Result>(endpoint, {
    method: "POST",
    headers: { Accept: "application/json" },
    body: new FormData(event.currentTarget as HTMLFormElement),
    ignoreResponseError: true,
  }).catch(() => null);
  if (result?.ok) {
    status.value = "sent";
    return;
  }
  errors.value = result?.error.fields ?? {};
  message.value = result?.error.message ?? "Couldn't reach the server. Try again.";
  status.value = "error";
}
</script>

<template>
  <p v-if="status === 'sent'">Thanks, we got it.</p>
  <form v-else :action="endpoint" method="post" @submit.prevent="onSubmit">
    <label>
      Email
      <input name="email" type="email" autocomplete="email" required />
    </label>
    <p v-if="errors.email">{{ errors.email[0] }}</p>
    <label>
      Message
      <textarea name="message" rows="5" />
    </label>
    <input type="hidden" name="_redirect" value="https://example.com/thanks" />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    <p v-if="status === 'error' && !errors.email" role="alert">{{ message }}</p>
    <button :disabled="status === 'sending'">{{ status === "sending" ? "Sending" : "Send" }}</button>
  </form>
</template>
```

- No Nuxt 3, coloque em `components/` em vez de `app/components/`.
- `ignoreResponseError: true` impede que o `$fetch` lance exceção em `4xx`, então o envelope de erro volta como dado. Só falhas de rede chegam ao `catch`.
- `@submit.prevent` só existe depois que o app hidrata. Antes disso, o navegador envia para o `action` e segue o `_redirect`.

### Passo 3: Use em uma página

`app/pages/contact.vue`:

```vue
<template>
  <ContactForm />
</template>
```

O fallback precisa de renderização no servidor ou pré-renderização. Com `ssr: false`, a página fica vazia até o JavaScript rodar, então não existe formulário para enviar sem ele.

## Domínios permitidos

Liste a produção mais os hosts de preview de onde você faz deploy.

| Onde o formulário roda | Adicione em Allowed domains |
| --- | --- |
| `nuxt dev` | `localhost`. Ele corresponde a `http://localhost:3000`: o Ikiform compara só o nome do host, nunca a porta. |
| Seu domínio | `example.com`, que também cobre `www.example.com` |
| Vercel | `your-project.vercel.app`. URLs de preview como `your-project-git-branch-team.vercel.app` não correspondem a ele; adicione o host exato do preview enquanto testa um. |
| Netlify | `your-site.netlify.app`. Deploy previews (`deploy-preview-12--your-site.netlify.app`) e branch deploys (`branch--your-site.netlify.app`) não são subdomínios dele, então adicione o host exato do preview enquanto testa um. |
| Cloudflare Pages | `your-project.pages.dev`. URLs de preview como `abc123.your-project.pages.dev` são subdomínios dele, então já correspondem. |

- Nunca adicione `vercel.app`, `netlify.app` ou `pages.dev` sozinhos. Todo site naquele host corresponderia, inclusive os de outras pessoas.

## Checklist de teste

1. Rode `nuxt dev` com `localhost` nos domínios permitidos e envie. A mensagem de agradecimento deve substituir o formulário.
2. Desative o JavaScript nas ferramentas de desenvolvedor do navegador, recarregue e envie. Você deve chegar à página de agradecimento do Ikiform localmente, ou a `/thanks` no seu domínio `https:`.
3. Envie um e-mail inválido com os campos definidos e confirme que a mensagem aparece abaixo do campo.
4. Rode `nuxt generate`, faça deploy do resultado e envie a partir do host de preview.

## Solução de problemas

| O que você vê | Por quê | Correção |
| --- | --- | --- |
| `FetchError: [POST] "https://ikiform.com/f/...": 422` | Falta `ignoreResponseError`, então o `$fetch` lança exceção em erros | Adicione `ignoreResponseError: true`. |
| `404 not_found`, e o action termina em `/f/undefined` | O id do formulário não estava definido quando o site foi gerado | Defina `NUXT_PUBLIC_IKIFORM_FORM_ID` no ambiente de build, ou o padrão em `nuxt.config.ts`. |
| Sem JavaScript, nada acontece | A página é renderizada no cliente com `ssr: false` | Ative a renderização no servidor ou pré-renderize a página. |
| Os envios chegam no filtro de spam com `origin_mismatch` | A página que enviou não está nos domínios permitidos do formulário | Adicione o host da tabela acima. Com **Reject** escolhido, os mesmos envios recebem `403 origin_not_allowed`. |

## Relacionados

- [Vue](https://ikiform.com/pt-br/docs/guides/vue.md): Um single-file component.
- [JavaScript puro](https://ikiform.com/pt-br/docs/guides/javascript.md): `fetch` com erros inline.
- [Domínios permitidos](https://ikiform.com/pt-br/docs/spam.md#allowed-domains): Como as origens são comparadas e o que acontece quando não correspondem.
- [Erros](https://ikiform.com/pt-br/docs/errors.md): Todos os códigos de erro, com causa e correção.
