bx-1028-policy #20
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
17
error.vue
17
error.vue
|
@ -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>
|
||||
|
|
|
@ -42,6 +42,11 @@ export default defineNuxtConfig({
|
|||
},
|
||||
},
|
||||
},
|
||||
vue: {
|
||||
compilerOptions: {
|
||||
isCustomElement: (tag) => ['nobr'].includes(tag),
|
||||
}
|
||||
},
|
||||
ssr: true,
|
||||
modules: [
|
||||
'@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'
|
||||
|
||||
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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue