Newsletter - Subscribe (CMS website)

Below you can find an implementation example how to subscribe to a Deploytec(Clang) newsletter in your Nuxt 3 app. Usually this is a website connected the Snakeware CMS.

Example: Logeren Bij De Boswachter - Footer

  • server: folder: server/api/newsletter/subscribe.ts
  • client: folder: components/items/deployteqsubscribtion.ts
Client
<template>
  <div class="newsletter">
    <span>{{ content.title.value }}</span>
    <form-input
      v-model="email"
      class="input"
      label="Email"
      autocomplete="email"
      :placeholder="placeholder"
      :error="error"
      :data-attrs="dataAttrs"
      @keydown.enter="submit"
      @submit="submit"
      @blur="clear"
      @focus="clear"
    />
  </div>
</template>

<script lang="ts" setup>
import { IItem } from '@snakeware/snakeware.api.site.typescript'
import { PropType } from 'vue'

const { t: translate } = useI18n()

const loading = ref(false)
const email = ref<string>()
const error = ref<string>()

const props = defineProps({
  item: {
    type: Object as PropType<IItem>,
    default: null
  }
})

const dataAttrs = {
  location: 'footer',
  itemType: props.item.alias,
  item: 'form-x-sent',
  style: 'form'
}

const content = useSwGetControls(props.item)

const placeholder = computed(
  () => `${translate('testing.newsletter.placeholder')}`
)

const clear = () => (error.value = '')

const submit = async () => {
  if (
    (email.value && email.value.length < 4 && !email.value.includes('@')) ||
    (email.value && !email.value.includes('.'))
  ) {
    error.value = translate('testing.newsletter.error')
    return
  }

  loading.value = true

  if (typeof window !== 'undefined') {
    const res = await $fetch<{ url: string }>('/api/newsletter/subscribe', {
      method: 'POST',
      body: {
        email: email.value?.toString()
      }
    })

    if (res.url) {
      await navigateTo(
        {
          path: res.url,
          query: {
            'newsletter-success': 'true'
          }
        },
        { external: true }
      )
    } else {
      error.value = translate('testing.error.general')
      loading.value = false
    }
  }
}
</script>

<style lang="scss" scoped>
.newsletter {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: flex-start;
  width: 100%;
  gap: 1.6rem;

  @include breakpoint(st) {
    flex-direction: row;
    justify-content: flex-end;
    align-items: center;
    gap: 0;
  }

  span {
    display: inline-block;
    margin-right: 1.6rem;
  }

  .input {
    width: 100%;
    border-bottom: 1px solid var(--grey-2-color);

    @include breakpoint(st) {
      width: 24rem;
    }
  }
}
</style>
Server
export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()
  const body = await readBody(event)
  const url = new URL(config.server.newsletterUrl)

  body.email.value &&
    url.searchParams.append('email', body.email.value.toString())

  const location = { url: '' }

  // need to call 'onResponse' and set our own location.url, if we straightaway return the fetch call we are getting text/html back
  await $fetch(url.toString(), {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body,
    onResponse({ response }) {
      if (response.url) {
        const redirectUrl = new URL(response.url)
        location.url = redirectUrl.pathname
      }
    }
  })

  return location
})