bx-1028-policy #20

Merged
ksenia_mikhailova merged 8 commits from bx-1028-policy into dev 2024-07-10 15:23:19 +03:00
9 changed files with 123 additions and 21 deletions

View File

@ -174,6 +174,9 @@ label {
input {
@apply bg-neutral-200 border border-gray-300 text-gray-900 rounded focus:ring-blue-500 focus:border-blue-500 text-lg p-2.5 disabled:cursor-not-allowed;
}
input[type=checkbox] {
@apply w-4 h-4;
}
textarea {
@apply block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 max-w-full min-h-10 max-h-40;

View File

@ -1,7 +1,8 @@
<script setup lang="ts">
import { apiFetch } from '~/utils/apiFetch';
const { data: pagesData } = await apiFetch<ApiPagesType[]>(`pages/?ordering=order`)
const { data: menuData } = await apiFetch<ApiMenuType>(`menu/1/`)
const pagesData = (menuData.value ? menuData.value.pages : []).sort((a, b) => a.order - b.order)
const route = useRoute()
</script>
<template>

View File

@ -30,12 +30,15 @@ type modalDataType = {
phone?: string
name?: string
email?: string
policy: boolean
}
const modal_data = reactive<modalDataType>({
email: undefined,
phone: undefined,
name: undefined
name: undefined,
policy: true
})
const modal_form = reactive({
disabled: true,
errors: [],
@ -62,13 +65,17 @@ const validate = () => {
modal_form.disabled = true
if (
(modal_data.phone && phone_regexp.test(modal_data.phone))
|| (modal_data.email && email_regex.test(modal_data.email))
(
(modal_data.phone && phone_regexp.test(modal_data.phone))
|| (modal_data.email && email_regex.test(modal_data.email))
) && modal_data.policy == true
) {
modal_form.disabled = false
return
}
}
watch(modal_data, validate)
const submit = async (e: any) => {
goal('submit_form', modal_data)
modal_state.show_status = 'loading'
@ -178,6 +185,14 @@ const modalStatus = {
'success': ["mdi:check-circle-outline", 'Данные отправлены'],
'error': ["mdi:close-circle-outline", 'Ошибка отправки'],
}
const policy = () => {
navigateTo('/policy', {
open: {
target: "_blank",
},
});
}
</script>
<template>
<div v-if="isModalOpen" class="modal-backdrop" @click.self="toggleModal">
@ -194,11 +209,17 @@ const modalStatus = {
<template v-else-if="modal_state.show_form">
<h2>Оставьте контакты для связи </h2>
<form @submit.prevent="submit" ref="form">
<input type="text" placeholder="Ваше имя" v-model="modal_data.name" @keyup="validate" />
<input type="text" placeholder="Ваше имя" v-model="modal_data.name" />
<input type="phone" placeholder="Ваш номер телефона" v-model="modal_data.phone"
@keypress="validateInput" @keyup="validate" />
<input type="e-mail" placeholder="Ваш e-mail" v-model="modal_data.email" @keypress="validateInput"
@keyup="validate" />
@keypress="validateInput" />
<input type="e-mail" placeholder="Ваш e-mail" v-model="modal_data.email"
@keypress="validateInput" />
<div class="flex gap-4 justify-between items-center">
<input type="checkbox" id="policy" v-model="modal_data.policy" />
<label for="policy">Нажимая кнопку "Отправить" Вы также даете согласие на <NuxtLink to="policy" @click="policy">обработку персональных
данных</NuxtLink>.
</label>
</div>
{{ total_txt && total_txt.total[0] }}
<div class="flex gap-4">
<button class="not-prose" :disabled="modal_form.disabled" type="submit">Отправить</button>

View File

@ -1,13 +1,11 @@
<script setup lang="ts">
const config = useRuntimeConfig()
const apiBase = config.public.apiBase
import '@/assets/main.scss'
const config = useRuntimeConfig()
import { apiFetch } from './utils/apiFetch';
import type { NuxtError } from '#app'
import og_img from '/og_img.png'
const { data: seoData } = await apiFetch<ApiKpType>(`kp/2`)
const { data: seoData } = await apiFetch<ApiKpType>(`kp/2/`)
useSeoMeta({
title: seoData.value?.title,
ogTitle: seoData.value?.title,
@ -20,9 +18,11 @@ useSeoMeta({
const props = defineProps({
error: Object as () => NuxtError
})
const route = useRoute()
if(route.path !== '/404') {
// navigateTo('/404')
}
</script>
<template>
<div>
<Header />
@ -32,6 +32,11 @@ const props = defineProps({
<h1>Вы ищете страницу, которой не существует. Вернитесь на главную страницу сайта</h1>
<p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с
покупкой забора здесь.</p>
<p class="hidden">
<code>
{{ error?.message }}
</code>
</p>
<button @click="navigateTo('/')" class="not-prose">Вернуться на главную</button>
</div>
</div>

View File

@ -42,6 +42,11 @@ export default defineNuxtConfig({
},
},
},
vue: {
compilerOptions: {
isCustomElement: (tag) => ['nobr'].includes(tag),
}
},
ssr: true,
modules: [
'@nuxtjs/tailwindcss',

50
pages/[slug].vue Normal file
View File

@ -0,0 +1,50 @@
<script setup lang="ts">
const config = useRuntimeConfig()
const imgBase = config.public.imgBase
import { apiFetch } from '~/utils/apiFetch';
import { marked } from 'marked';
import og_img from '/og_img.png'
const { data: seoData } = await apiFetch<ApiKpType>(`kp/1/`)
useSeoMeta({
title: seoData.value?.title,
ogTitle: seoData.value?.title,
description: seoData.value?.content,
ogDescription: seoData.value?.content,
ogImage: config.public.baseUrl + og_img,
// twitterCard: 'summary_large_image',
})
const route = useRoute()
const { data } = await apiFetch<ApiPagesType>(`pages/${route.params.slug}/`)
if (!data.value) {
throw createError({
statusCode: 404,
})
}
const policyText = computed(() => {
if (!data?.value) return ''
let c = data?.value.content || ''
return marked.parse(c)
})
</script>
<template>
<div>
<div class="siteblock bg-white">
<div class="container">
<h1 class="siteblock-title">{{ data?.title || '404' }}</h1>
<div class="col-span-full prose max-w-full" v-html="policyText" />
</div>
</div>
<div class="siteblock-image" v-if="data?.image">
<NuxtImg :src="[imgBase, data?.image].join('/')" :alt="data.title || 'фоновая картинка'" title="" format="webp" />
</div>
<div class="siteblock siteblock_calc bg-white">
<Suspense>
<CalcModels />
</Suspense>
</div>
</div>
</template>

View File

@ -7,7 +7,7 @@ import { marked } from 'marked';
import og_img from '/og_img.png'
const { data: seoData } = await apiFetch<ApiKpType>(`kp/1`)
const { data: seoData } = await apiFetch<ApiKpType>(`kp/1/`)
useSeoMeta({
title: seoData.value?.title,
ogTitle: seoData.value?.title,
@ -17,15 +17,16 @@ useSeoMeta({
// twitterCard: 'summary_large_image',
})
const { data: pagesData } = await apiFetch<ApiPagesType[]>(`pages/?ordering=order`)
const { data: menuData } = await apiFetch<ApiMenuType>(`menu/1/?ordering=order`)
const pagesData = menuData.value ? menuData.value.pages : []
const { data: reviewsData } = await apiFetch<ApiReviewsType[]>(`review/`)
const { data: calculatorData } = await apiFetch(`calculator/5/`)
const about = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'about')
const reviews = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'clients')
const delivery = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'how_to')
const advantages = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'advantages')
const about = pagesData.find(el => el.slug == 'about')
const reviews = pagesData.find(el => el.slug == 'clients')
const delivery = pagesData.find(el => el.slug == 'how_to')
const advantages = pagesData.find(el => el.slug == 'advantages')
const roubleSign = new Intl.NumberFormat('ru-RU', {
style: 'currency',

7
types/index.d.ts vendored
View File

@ -14,8 +14,15 @@ type ApiKpType = {
is_indexed: boolean
}
type ApiMenuType = {
id: number
type: string
pages: ApiPagesType[]
}
type ApiPagesType = {
id: number
order: number
title: string
menu_title: string
slug: string

View File

@ -5,6 +5,15 @@ export async function apiFetch<T>(path: string) {
headers.set('Referer', config.public.baseUrl)
return useFetch<T>(`${apiBase}/${path}`, {
baseURL: config.public.baseUrl,
headers
headers,
onResponseError({ response }) {
console.log(response.status)
console.log(response.url)
window.location.pathname = '/404'
throw createError({
statusCode: 404,
fatal: true
})
},
})
}