@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="max-w-prose mx-auto py-6 px-4 md:px-6">
|
||||
<section class="content">
|
||||
<div class="max-w-prose mx-auto py-8">
|
||||
<CreateSectionManager
|
||||
:placement="placement"
|
||||
:selectedActions="selectedActions"
|
||||
:messageDeliveryMethod="messageDeliveryMethod"
|
||||
>
|
||||
<template #placement>
|
||||
<CreatePlacementSection v-model="placement" />
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<CreateActionsSection v-model="selectedActions" />
|
||||
</template>
|
||||
|
||||
<template #delivery>
|
||||
<CreateMessageDeliveryMethodSection v-model="messageDeliveryMethod" />
|
||||
</template>
|
||||
|
||||
<template #messages>
|
||||
<CreatePredefinedMessagesSection :placement="placement" v-model="messages" />
|
||||
</template>
|
||||
|
||||
<template #summary>
|
||||
<CreateSummarySection
|
||||
v-model:name="name"
|
||||
buttonLabel="Создать QR‑код"
|
||||
@nextStep="createQrCode"
|
||||
/>
|
||||
</template>
|
||||
</CreateSectionManager>
|
||||
|
||||
<p v-if="error" class="text-red-500 mt-4">Ошибка создания QR‑кода: {{ error }}</p>
|
||||
|
||||
<div class="h-[30dvh] md:h-[10dvh] overflow-hidden"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
≈
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { createQrCodeDocument } from '@/api/qrCode'
|
||||
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { ROUTE_NAMES } from '@/constants/routes'
|
||||
|
||||
const placement = ref<null | QRCodeDocument['placement']>(null)
|
||||
const selectedActions = ref<QRCodeDocument['actions']>([])
|
||||
const messageDeliveryMethod = ref<null | QRCodeDocument['messageDeliveryMethod']>(null)
|
||||
const messages = ref<Exclude<QRCodeDocument['predefinedMessages'], undefined>>([])
|
||||
const name = ref<QRCodeDocument['name']>('')
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
async function createQrCode() {
|
||||
try {
|
||||
const qrCodeDocument = await createQrCodeDocument({
|
||||
name: name.value,
|
||||
placement: placement.value!,
|
||||
actions: selectedActions.value,
|
||||
messageDeliveryMethod: messageDeliveryMethod.value!,
|
||||
predefinedMessages: messages.value.length > 0 ? messages.value : undefined,
|
||||
})
|
||||
|
||||
error.value = null
|
||||
await router.push({
|
||||
name: ROUTE_NAMES.MANAGE_QR_CODE_READY,
|
||||
params: { qr_code_uri: qrCodeDocument.uri },
|
||||
})
|
||||
} catch (err) {
|
||||
error.value = (err as Error).message || 'Неизвестная ошибка'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<section class="max-w-2xl mx-auto py-8 px-4 text-center">
|
||||
<h1 class="text-2xl font-bold mb-4">Поддержите развитие НаСвязи</h1>
|
||||
<p class="text-gray-600 mb-6">
|
||||
Мы делаем всё, чтобы НаСвязи оставался удобным и полезным. Вы можете помочь нам, поддержав
|
||||
проект на Бусти.
|
||||
</p>
|
||||
|
||||
<!-- Ссылка на Бусти -->
|
||||
<div class="mb-8">
|
||||
<a
|
||||
href="https://boosty.to/hereconnect/donate"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-block bg-orange-500 text-white font-semibold py-3 px-6 rounded-lg shadow hover:bg-orange-600 transition duration-300"
|
||||
>
|
||||
Поддержать через Бусти
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Рассказать друзьям -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">Рассказать друзьям</h2>
|
||||
<p class="text-gray-600 mb-4">Поделитесь ссылкой на НаСвязи:</p>
|
||||
<div class="flex justify-center gap-4">
|
||||
<!-- Telegram -->
|
||||
<a
|
||||
:href="telegramShareUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="p-button-rounded p-button-text p-button-lg inline-flex items-center justify-center"
|
||||
>
|
||||
<i class="pi pi-telegram text-xl text-blue-500"></i>
|
||||
</a>
|
||||
|
||||
<!-- WhatsApp -->
|
||||
<a
|
||||
:href="whatsappShareUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="p-button-rounded p-button-text p-button-lg inline-flex items-center justify-center"
|
||||
>
|
||||
<i class="pi pi-whatsapp text-xl text-green-500"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Обратная связь -->
|
||||
<!-- <div>-->
|
||||
<!-- <h2 class="text-xl font-semibold mb-4">Оставить отзыв или идею</h2>-->
|
||||
<!-- <p class="text-gray-600 mb-4">Мы рады любым предложениям и обратной связи.</p>-->
|
||||
<!-- <form @submit.prevent="sendFeedback">-->
|
||||
<!-- <div class="mb-4">-->
|
||||
<!-- <InputText v-model="email" class="w-full" placeholder="Ваш email" />-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mb-4">-->
|
||||
<!-- <Textarea-->
|
||||
<!-- v-model="message"-->
|
||||
<!-- rows="4"-->
|
||||
<!-- class="w-full"-->
|
||||
<!-- placeholder="Ваше сообщение"-->
|
||||
<!-- ></Textarea>-->
|
||||
<!-- </div>-->
|
||||
<!-- <Button-->
|
||||
<!-- label="Отправить"-->
|
||||
<!-- class="p-button-md"-->
|
||||
<!-- :disabled="!email.trim() || !message.trim()"-->
|
||||
<!-- />-->
|
||||
<!-- </form>-->
|
||||
<!-- <p v-if="success" class="text-green-500 mt-4">Спасибо за ваше сообщение!</p>-->
|
||||
<!-- </div>-->
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import { ref } from 'vue'
|
||||
|
||||
// Локальные состояния
|
||||
// const email = ref('')
|
||||
// const message = ref('')
|
||||
// const success = ref(false)
|
||||
|
||||
// Конфигурация для шаринга
|
||||
const siteUrl = encodeURIComponent('https://hereconnect.condev.ru')
|
||||
const shareText = encodeURIComponent('Попробуйте НаСвязи — удобный способ связаться через QR-код!')
|
||||
// const shareImage = encodeURIComponent(
|
||||
// 'https://hereconnect.condev.ru/images/qr-code-logo-200x200.jpg',
|
||||
// )
|
||||
|
||||
// Генерация URL для Telegram
|
||||
const telegramShareUrl = `https://t.me/share/url?url=${siteUrl}&text=${shareText}` // &photo=${shareImage}`
|
||||
|
||||
// Генерация URL для WhatsApp
|
||||
const whatsappShareUrl = `https://api.whatsapp.com/send?text=${shareText}%0A${siteUrl}`
|
||||
|
||||
// // Обработка отправки отзыва
|
||||
// function sendFeedback() {
|
||||
// console.log('Отзыв отправлен', { email: email.value, message: message.value })
|
||||
// // Имитация отправки данных
|
||||
// email.value = ''
|
||||
// message.value = ''
|
||||
// success.value = true
|
||||
// setTimeout(() => (success.value = false), 5000) // Убираем сообщение через 5 секунд
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Стили для textarea */
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<TheWelcome />
|
||||
</main>
|
||||
</template>
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div class="flex flex-col min-h-full">
|
||||
<main class="flex-grow flex items-center justify-center py-6 px-4 md:px-6">
|
||||
<section class="max-w-prose w-full flex flex-col gap-1">
|
||||
<h2 class="text-xl font-semibold mb-4">Ваш QR‑код готов</h2>
|
||||
<p class="text-gray-600 mb-4">Сохраните его и ознакомьтесь с инструкциями по размещению.</p>
|
||||
|
||||
<!-- Рендер QR‑кода -->
|
||||
<div v-if="!svgQueryIsLoading && svgQueryData" class="border p-4 bg-white rounded-lg mb-6">
|
||||
<img :src="svgQueryData" alt="QR‑код" />
|
||||
</div>
|
||||
<p v-else-if="svgQueryIsError" class="text-red-500 mt-4">
|
||||
Ошибка генерации QR‑кода: {{ svgQueryError?.message }}
|
||||
</p>
|
||||
|
||||
<!-- Сообщение об ошибке -->
|
||||
<p v-if="qrCodeQueryIsError" class="text-red-500 mt-4">
|
||||
Ошибка загрузки данных QR‑кода: {{ qrCodeQueryError!.message }}
|
||||
</p>
|
||||
<p v-if="svgQueryIsLoading" class="text-gray-500 mt-4">Загрузка...</p>
|
||||
|
||||
<!-- Кнопки для скачивания -->
|
||||
<div
|
||||
v-if="!svgQueryIsLoading && !qrCodeQueryIsError && svgQueryData"
|
||||
class="flex flex-row gap-4 justify-center"
|
||||
>
|
||||
<Button
|
||||
label="Скачать SVG"
|
||||
icon="pi pi-download"
|
||||
class="p-button-sm"
|
||||
:href="svgQueryData"
|
||||
:download="`${sanitizedFileName}.svg`"
|
||||
as="a"
|
||||
/>
|
||||
|
||||
<DownloadImageButton
|
||||
:svgUrl="svgQueryData"
|
||||
:filename="`${sanitizedFileName}.png`"
|
||||
label="Скачать PNG"
|
||||
type="image/png"
|
||||
:width="width"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Инструкции по размещению -->
|
||||
<ManagePlacementInstructions
|
||||
:placement="qrCodeDoc?.placement"
|
||||
v-if="qrCodeDoc?.placement"
|
||||
/>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="bg-gray-100 text-center py-4 border-t text-sm text-gray-700">
|
||||
<p>
|
||||
Нужен еще один?
|
||||
<a href="/create" class="underline">Создайте QR‑код</a>.
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
Сделайте НаСвязи удобнее. Будем рады вашей <a href="/donate" class="underline"
|
||||
>поддержке</a
|
||||
>.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import QRCodeStyling from 'qr-code-styling'
|
||||
import { optimizeSvg } from '@/utils/images/svg/optimizeSvg'
|
||||
import { replaceFillInSvg } from '@/utils/images/svg/replaceFillInSvg'
|
||||
import { getQrCodeDocument } from '@/api/qrCode'
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { addLabelToSvg } from '@/utils/images/svg/addLabelToSvg'
|
||||
import logo from '@/assets/logo.svg?raw'
|
||||
import { sanitizeFileName } from '@/utils/sanitizeFileName'
|
||||
|
||||
const { qr_code_uri } = defineProps<{ qr_code_uri: QRCodeDocument['uri'] }>()
|
||||
const color = ref('#000') // Цвет для текста QR-кода
|
||||
const logoColor = ref(color.value) // Цвет логотипа // #FB2500
|
||||
const label = ref('Сообщите о проблеме или задайте вопрос')
|
||||
const width = ref(256 * 4)
|
||||
const height = ref(width.value)
|
||||
|
||||
// Хук для загрузки данных QR‑кода
|
||||
const {
|
||||
data: qrCodeDoc,
|
||||
isError: qrCodeQueryIsError,
|
||||
error: qrCodeQueryError,
|
||||
} = useQuery({
|
||||
queryKey: ['qrCode', computed(() => qr_code_uri)],
|
||||
queryFn: () => getQrCodeDocument(qr_code_uri),
|
||||
})
|
||||
|
||||
const sanitizedFileName = computed(() => {
|
||||
const sanitized = qrCodeDoc.value?.name && sanitizeFileName(qrCodeDoc.value?.name)
|
||||
|
||||
return sanitized ? `${sanitized} QR-Code - НаСвязи` : 'QR-Code - НаСвязи'
|
||||
})
|
||||
|
||||
// Генерация и оптимизация SVG
|
||||
const {
|
||||
isLoading: svgQueryIsLoading,
|
||||
isError: svgQueryIsError,
|
||||
data: svgQueryData,
|
||||
error: svgQueryError,
|
||||
} = useQuery({
|
||||
queryKey: ['svg', computed(() => qr_code_uri)],
|
||||
enabled: computed(() => !!qrCodeDoc.value?.url),
|
||||
retry: false,
|
||||
queryFn: async () => {
|
||||
// Заменяем цвет `fill` в логотипе
|
||||
const updatedLogo = replaceFillInSvg(logo, logoColor.value)
|
||||
|
||||
// Добавляем текст в логотип (если нужно)
|
||||
const logoWithText = addLabelToSvg(updatedLogo, 'НаСвязи', logoColor.value, 0.01)
|
||||
|
||||
const image = URL.createObjectURL(new Blob([logoWithText], { type: 'image/svg+xml' }))
|
||||
|
||||
// Инициализация QRCodeStyling
|
||||
const qrCode = new QRCodeStyling({
|
||||
width: width.value,
|
||||
height: height.value,
|
||||
data: qrCodeDoc.value!.url,
|
||||
image,
|
||||
imageOptions: {
|
||||
margin: 20,
|
||||
imageSize: 0.45,
|
||||
},
|
||||
type: 'svg',
|
||||
backgroundOptions: {
|
||||
color: 'transparent',
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: 'extra-rounded',
|
||||
},
|
||||
cornersDotOptions: {
|
||||
type: 'dot',
|
||||
},
|
||||
dotsOptions: {
|
||||
color: color.value,
|
||||
type: 'classy-rounded',
|
||||
},
|
||||
})
|
||||
|
||||
const blob = await qrCode.getRawData('svg')
|
||||
if (blob && blob instanceof Blob) {
|
||||
const text = await blob.text()
|
||||
let resultSvg = optimizeSvg(text)
|
||||
if (!resultSvg) {
|
||||
throw new Error('Ошибка оптимизации SVG')
|
||||
}
|
||||
if (label.value) {
|
||||
resultSvg = addLabelToSvg(resultSvg, label.value, color.value)
|
||||
}
|
||||
return URL.createObjectURL(new Blob([resultSvg], { type: 'image/svg+xml' }))
|
||||
} else {
|
||||
throw new Error('Ошибка генерации QR‑кода')
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="flex flex-col min-h-full">
|
||||
<!-- Основной контент -->
|
||||
<main class="flex-grow flex flex-col">
|
||||
<section class="flex flex-col flex-grow max-w-prose w-full mx-auto">
|
||||
<ChatComponent userRole="guest" :qr_code_uri="qr_code_uri" :chat="chat">
|
||||
<template #endOfMessages="{ count }">
|
||||
<!-- Уведомление, если гость отправил только одно сообщение -->
|
||||
<div
|
||||
v-if="count === 1"
|
||||
class="flex flex-col items-center justify-center text-center bg-gray-50 border border-gray-200 rounded-lg shadow-md p-6 mt-6"
|
||||
>
|
||||
<p class="text-gray-700 font-medium text-lg">Ваше сообщение отправлено.</p>
|
||||
<p class="text-gray-600 mt-2">
|
||||
Вы можете написать ещё одно сообщение или подождать ответа.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="mt-10 text-sm text-gray-500 text-center">
|
||||
Хотите создать собственный QR‑код?
|
||||
<a href="/create" class="underline">Создайте его здесь</a>.
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500 text-center">
|
||||
Сделайте НаСвязи удобнее. Мы будем рады вашей <RouterLink
|
||||
to="/donate"
|
||||
class="underline"
|
||||
>поддержке</RouterLink
|
||||
>.
|
||||
</p>
|
||||
</template>
|
||||
</ChatComponent>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Футер -->
|
||||
<!-- <footer class="bg-gray-100 text-center py-4 border-t text-sm text-gray-700">-->
|
||||
<!-- <p><a href="/create" class="underline">Создайте свой QR‑код</a>.</p>-->
|
||||
<!-- <p class="mt-1">-->
|
||||
<!-- Сделайте НаСвязи удобнее. Будем рады вашей <a href="/donate" class="underline"-->
|
||||
<!-- >поддержке</a-->
|
||||
<!-- >.-->
|
||||
<!-- </p>-->
|
||||
<!-- </footer>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { MessageDocument } from '@/types/DBDocumentTypes'
|
||||
import ChatComponent from '@/components/ChatComponent.vue'
|
||||
defineProps<{ qr_code_uri: MessageDocument['qr_code_uri']; chat: MessageDocument['chat'] }>()
|
||||
</script>
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="flex flex-col min-h-full">
|
||||
<main class="flex-grow flex items-center justify-center py-6 px-4 md:px-6">
|
||||
<section class="max-w-prose w-full flex flex-col gap-10">
|
||||
<QueryRender :query #default="{ data: doc }">
|
||||
<template v-for="action in doc.actions" :key="action">
|
||||
<ReadContactInfo
|
||||
v-if="action === 'viewContactInfo' && doc.contactInfo"
|
||||
:contactInfo="doc.contactInfo"
|
||||
/>
|
||||
<ReadMessageSender v-else-if="action === 'sendMessage'" :doc />
|
||||
</template>
|
||||
</QueryRender>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="bg-gray-100 text-center py-4 border-t text-sm text-gray-700">
|
||||
<p>
|
||||
Нужен такой же?
|
||||
<a href="/create" class="underline">Создайте свой QR‑код</a>.
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
Сделайте НаСвязи удобнее. Будем рады вашей <a href="/donate" class="underline"
|
||||
>поддержке</a
|
||||
>.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { getQrCodeDocument } from '@/api/qrCode'
|
||||
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import QueryRender from '@/components/QueryRender.vue'
|
||||
|
||||
const { qr_code_uri } = defineProps<{ qr_code_uri: string }>()
|
||||
|
||||
// Хук для загрузки данных QR-кода
|
||||
const query = useQuery({
|
||||
queryKey: ['qrCodeData', qr_code_uri],
|
||||
queryFn: (): Promise<QRCodeDocument> => getQrCodeDocument(qr_code_uri),
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user