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
@@ -0,0 +1,107 @@
<template>
<section class="max-w-prose w-full text-center">
<!-- Контактная информация -->
<div class="flex items-start bg-gray-100 border rounded-lg p-4">
<!-- Аватарка Gravatar -->
<div v-if="contactInfo.email" class="flex-shrink-0 mr-4">
<img
:src="gravatarUrl"
alt="Аватар"
class="w-16 h-16 rounded-full"
title="Аватар владельца"
/>
</div>
<!-- Контакты -->
<div class="text-left flex-grow">
<p v-if="contactInfo.name" class="text-lg font-semibold">{{ contactInfo.name }}</p>
<p v-if="contactInfo.phone" class="text-gray-700">
Телефон:
<a :href="`tel:${formattedPhone}`" class="underline text-blue-600">
{{ contactInfo.phone }}
</a>
</p>
<p v-if="contactInfo.email" class="text-gray-700">
Email:
<a :href="`mailto:${contactInfo.email}`" class="underline text-blue-600">
{{ contactInfo.email }}
</a>
</p>
<Button
v-if="hasContactInfo"
label="Добавить в адресную книгу"
icon="pi pi-user-plus"
class="p-button-sm mt-4 p-button-outlined"
@click="addToAddressBook"
/>
</div>
</div>
<p v-if="success" class="text-green-500 mt-4">Контакт успешно добавлен в адресную книгу!</p>
<p v-if="error" class="text-red-500 mt-4">Ошибка добавления контакта: {{ error }}</p>
</section>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import md5 from 'md5'
import type { ContactInfo } from '@/types/DBDocumentTypes'
import { sanitizeFileName } from '@/utils/sanitizeFileName'
const { contactInfo } = defineProps<{ contactInfo: ContactInfo }>()
const success = ref(false)
const error = ref<string | null>(null)
// Форматирование номера телефона для протокола `tel:`
const formattedPhone = computed(() => {
if (!contactInfo.phone) return ''
return contactInfo.phone.replace(/[^0-9+]/g, '') // Удаляем все, кроме цифр и "+"
})
// Проверка наличия контактной информации
const hasContactInfo = computed(() => {
return !!contactInfo.name || !!contactInfo.phone || !!contactInfo.email
})
// Ссылка на аватар Gravatar
const gravatarUrl = computed(() => {
if (!contactInfo.email) return ''
const hash = md5(contactInfo.email.trim().toLowerCase())
return `https://www.gravatar.com/avatar/${hash}?d=identicon&s=128`
})
// Добавление в адресную книгу
function addToAddressBook() {
try {
const vCard = [
'BEGIN:VCARD',
'VERSION:3.0',
contactInfo.name ? `FN:${contactInfo.name}` : '',
contactInfo.phone ? `TEL;TYPE=VOICE:${formattedPhone.value}` : '',
contactInfo.email ? `EMAIL:${contactInfo.email}` : '',
'END:VCARD',
]
.filter(Boolean) // Убираем пустые строки
.join('\n')
const blob = new Blob([vCard], { type: 'text/vcard;charset=utf-8' })
const link = document.createElement('a')
const sanitized = contactInfo.name && sanitizeFileName(contactInfo.name)
link.href = URL.createObjectURL(blob)
link.download = `${sanitized || 'contact'}.vcf`
link.click()
success.value = true
error.value = null
} catch {
error.value = 'Ошибка при создании vCard'
success.value = false
}
}
</script>
@@ -0,0 +1,108 @@
<template>
<section class="max-w-prose w-full">
<p class="text-gray-600 mb-4 text-center" v-if="doc.predefinedMessages?.length">
Выберите сообщение из списка или напишите своё:
</p>
<!-- Предустановленные сообщения -->
<div v-if="doc.predefinedMessages?.length" class="mb-6">
<div class="flex flex-col gap-3">
<Button
v-for="(message, index) in doc.predefinedMessages"
:key="index"
class="p-button-outlined w-full text-left"
:disabled="loading"
@click="sendPresetMessage(message)"
>
{{ message }}
</Button>
</div>
</div>
<!-- Текстовое поле ввода с кнопкой -->
<form @submit.prevent="sendCustomMessage" class="flex gap-2 items-center">
<InputText
id="customMessage"
v-model="customMessage"
class="flex-1"
placeholder="Напишите сообщение"
:disabled="loading"
/>
<Button
type="submit"
icon="pi pi-send"
class="p-button-md"
:disabled="!customMessage.trim() || loading"
/>
</form>
<p v-if="success" class="text-green-500 mt-4 text-center">Сообщение отправлено!</p>
<p v-if="error" class="text-red-500 mt-4 text-center">Ошибка: {{ error }}</p>
</section>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { messagesDbLocal as db } from '@/api/dbs'
import type { MessageDocument, QRCodeDocument } from '@/types/DBDocumentTypes'
import { ROUTE_NAMES } from '@/constants/routes'
const { doc } = defineProps<{
doc: QRCodeDocument
}>()
const router = useRouter()
// Локальные состояния
const customMessage = ref('')
const success = ref(false)
const error = ref<string | null>(null)
const loading = ref(false)
// Генерация идентификатора чата
// Функция для отправки сообщения
async function sendMessage(content: string) {
try {
loading.value = true
const chat = (new Date().getTime() - new Date(doc.created_at).getTime()).toString(36)
const message: MessageDocument = {
_id: `message:${doc.uri}:${chat}:${new Date().toISOString()}`,
type: 'message',
qr_code_uri: doc.uri,
chat,
created_at: new Date().toISOString(),
from: 'guest',
body: content.trim(),
}
// Сохраняем сообщение в локальную базу данных
await db.put(message)
success.value = true
error.value = null
customMessage.value = ''
// Перенаправляем на страницу чата
await router.push({ name: ROUTE_NAMES.PUBLIC_CHAT, params: { qr_code_uri: doc.uri, chat } })
} catch {
success.value = false
error.value = 'Ошибка отправки сообщения'
} finally {
loading.value = false
}
}
// Функция для отправки предустановленного сообщения
async function sendPresetMessage(message: string) {
await sendMessage(message)
}
// Функция для отправки пользовательского сообщения
async function sendCustomMessage() {
await sendMessage(customMessage.value)
}
</script>