bx-1028-policy #20
|
@ -174,6 +174,9 @@ label {
|
||||||
input {
|
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;
|
@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 {
|
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;
|
@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;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { apiFetch } from '~/utils/apiFetch';
|
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()
|
const route = useRoute()
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -30,12 +30,15 @@ type modalDataType = {
|
||||||
phone?: string
|
phone?: string
|
||||||
name?: string
|
name?: string
|
||||||
email?: string
|
email?: string
|
||||||
|
policy: boolean
|
||||||
}
|
}
|
||||||
const modal_data = reactive<modalDataType>({
|
const modal_data = reactive<modalDataType>({
|
||||||
email: undefined,
|
email: undefined,
|
||||||
phone: undefined,
|
phone: undefined,
|
||||||
name: undefined
|
name: undefined,
|
||||||
|
policy: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const modal_form = reactive({
|
const modal_form = reactive({
|
||||||
disabled: true,
|
disabled: true,
|
||||||
errors: [],
|
errors: [],
|
||||||
|
@ -62,13 +65,17 @@ const validate = () => {
|
||||||
|
|
||||||
modal_form.disabled = true
|
modal_form.disabled = true
|
||||||
if (
|
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
|
modal_form.disabled = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
watch(modal_data, validate)
|
||||||
|
|
||||||
const submit = async (e: any) => {
|
const submit = async (e: any) => {
|
||||||
goal('submit_form', modal_data)
|
goal('submit_form', modal_data)
|
||||||
modal_state.show_status = 'loading'
|
modal_state.show_status = 'loading'
|
||||||
|
@ -178,6 +185,14 @@ const modalStatus = {
|
||||||
'success': ["mdi:check-circle-outline", 'Данные отправлены'],
|
'success': ["mdi:check-circle-outline", 'Данные отправлены'],
|
||||||
'error': ["mdi:close-circle-outline", 'Ошибка отправки'],
|
'error': ["mdi:close-circle-outline", 'Ошибка отправки'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const policy = () => {
|
||||||
|
navigateTo('/policy', {
|
||||||
|
open: {
|
||||||
|
target: "_blank",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-if="isModalOpen" class="modal-backdrop" @click.self="toggleModal">
|
<div v-if="isModalOpen" class="modal-backdrop" @click.self="toggleModal">
|
||||||
|
@ -194,11 +209,17 @@ const modalStatus = {
|
||||||
<template v-else-if="modal_state.show_form">
|
<template v-else-if="modal_state.show_form">
|
||||||
<h2>Оставьте контакты для связи </h2>
|
<h2>Оставьте контакты для связи </h2>
|
||||||
<form @submit.prevent="submit" ref="form">
|
<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"
|
<input type="phone" placeholder="Ваш номер телефона" v-model="modal_data.phone"
|
||||||
@keypress="validateInput" @keyup="validate" />
|
@keypress="validateInput" />
|
||||||
<input type="e-mail" placeholder="Ваш e-mail" v-model="modal_data.email" @keypress="validateInput"
|
<input type="e-mail" placeholder="Ваш e-mail" v-model="modal_data.email"
|
||||||
@keyup="validate" />
|
@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] }}
|
{{ total_txt && total_txt.total[0] }}
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<button class="not-prose" :disabled="modal_form.disabled" type="submit">Отправить</button>
|
<button class="not-prose" :disabled="modal_form.disabled" type="submit">Отправить</button>
|
||||||
|
|
17
error.vue
17
error.vue
|
@ -1,13 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const config = useRuntimeConfig()
|
|
||||||
const apiBase = config.public.apiBase
|
|
||||||
import '@/assets/main.scss'
|
import '@/assets/main.scss'
|
||||||
|
const config = useRuntimeConfig()
|
||||||
import { apiFetch } from './utils/apiFetch';
|
import { apiFetch } from './utils/apiFetch';
|
||||||
import type { NuxtError } from '#app'
|
import type { NuxtError } from '#app'
|
||||||
import og_img from '/og_img.png'
|
import og_img from '/og_img.png'
|
||||||
|
|
||||||
const { data: seoData } = await apiFetch<ApiKpType>(`kp/2`)
|
const { data: seoData } = await apiFetch<ApiKpType>(`kp/2/`)
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
title: seoData.value?.title,
|
title: seoData.value?.title,
|
||||||
ogTitle: seoData.value?.title,
|
ogTitle: seoData.value?.title,
|
||||||
|
@ -20,9 +18,11 @@ useSeoMeta({
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
error: Object as () => NuxtError
|
error: Object as () => NuxtError
|
||||||
})
|
})
|
||||||
|
const route = useRoute()
|
||||||
|
if(route.path !== '/404') {
|
||||||
|
// navigateTo('/404')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<Header />
|
<Header />
|
||||||
|
@ -32,6 +32,11 @@ const props = defineProps({
|
||||||
<h1>Вы ищете страницу, которой не существует. Вернитесь на главную страницу сайта</h1>
|
<h1>Вы ищете страницу, которой не существует. Вернитесь на главную страницу сайта</h1>
|
||||||
<p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с
|
<p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с
|
||||||
покупкой забора здесь.</p>
|
покупкой забора здесь.</p>
|
||||||
|
<p class="hidden">
|
||||||
|
<code>
|
||||||
|
{{ error?.message }}
|
||||||
|
</code>
|
||||||
|
</p>
|
||||||
<button @click="navigateTo('/')" class="not-prose">Вернуться на главную</button>
|
<button @click="navigateTo('/')" class="not-prose">Вернуться на главную</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -42,6 +42,11 @@ export default defineNuxtConfig({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
vue: {
|
||||||
|
compilerOptions: {
|
||||||
|
isCustomElement: (tag) => ['nobr'].includes(tag),
|
||||||
|
}
|
||||||
|
},
|
||||||
ssr: true,
|
ssr: true,
|
||||||
modules: [
|
modules: [
|
||||||
'@nuxtjs/tailwindcss',
|
'@nuxtjs/tailwindcss',
|
||||||
|
|
|
@ -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>
|
|
@ -7,7 +7,7 @@ import { marked } from 'marked';
|
||||||
|
|
||||||
import og_img from '/og_img.png'
|
import og_img from '/og_img.png'
|
||||||
|
|
||||||
const { data: seoData } = await apiFetch<ApiKpType>(`kp/1`)
|
const { data: seoData } = await apiFetch<ApiKpType>(`kp/1/`)
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
title: seoData.value?.title,
|
title: seoData.value?.title,
|
||||||
ogTitle: seoData.value?.title,
|
ogTitle: seoData.value?.title,
|
||||||
|
@ -17,15 +17,16 @@ useSeoMeta({
|
||||||
// twitterCard: 'summary_large_image',
|
// 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: reviewsData } = await apiFetch<ApiReviewsType[]>(`review/`)
|
||||||
const { data: calculatorData } = await apiFetch(`calculator/5/`)
|
const { data: calculatorData } = await apiFetch(`calculator/5/`)
|
||||||
|
|
||||||
const about = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'about')
|
const about = pagesData.find(el => el.slug == 'about')
|
||||||
const reviews = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'clients')
|
const reviews = pagesData.find(el => el.slug == 'clients')
|
||||||
const delivery = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'how_to')
|
const delivery = pagesData.find(el => el.slug == 'how_to')
|
||||||
const advantages = (pagesData.value as ApiPagesType[]).find(el => el.slug == 'advantages')
|
const advantages = pagesData.find(el => el.slug == 'advantages')
|
||||||
|
|
||||||
const roubleSign = new Intl.NumberFormat('ru-RU', {
|
const roubleSign = new Intl.NumberFormat('ru-RU', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
|
|
|
@ -14,8 +14,15 @@ type ApiKpType = {
|
||||||
is_indexed: boolean
|
is_indexed: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ApiMenuType = {
|
||||||
|
id: number
|
||||||
|
type: string
|
||||||
|
pages: ApiPagesType[]
|
||||||
|
}
|
||||||
|
|
||||||
type ApiPagesType = {
|
type ApiPagesType = {
|
||||||
id: number
|
id: number
|
||||||
|
order: number
|
||||||
title: string
|
title: string
|
||||||
menu_title: string
|
menu_title: string
|
||||||
slug: string
|
slug: string
|
||||||
|
|
|
@ -5,6 +5,15 @@ export async function apiFetch<T>(path: string) {
|
||||||
headers.set('Referer', config.public.baseUrl)
|
headers.set('Referer', config.public.baseUrl)
|
||||||
return useFetch<T>(`${apiBase}/${path}`, {
|
return useFetch<T>(`${apiBase}/${path}`, {
|
||||||
baseURL: config.public.baseUrl,
|
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
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
Loading…
Reference in New Issue