move * to apps/frontend
Main daploy / deploy (push) Successful in 43s

This commit is contained in:
2024-11-21 01:15:06 +02:00
parent 3ac451e5f3
commit 11bba7a275
89 changed files with 35 additions and 23 deletions
+79
View File
@@ -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>
+112
View File
@@ -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>
+7
View File
@@ -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">
Сделайте НаСвязи удобнее. Будем рады вашей&nbsp;<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">
Сделайте НаСвязи удобнее. Мы будем рады вашей&nbsp;<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">-->
<!-- Сделайте НаСвязи удобнее. Будем рады вашей&nbsp;<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">
Сделайте НаСвязи удобнее. Будем рады вашей&nbsp;<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>