support dark theme closes #25
Deploy service message-delivery-method-web-push / service message-delivery-method-web-push (push) Successful in 1m17s
Deploy db-migrations / db-migrations (push) Successful in 38s
Deploy app frontend / app frontend (push) Successful in 2m14s

This commit is contained in:
2024-12-08 11:06:31 +02:00
parent 0d40aaf2da
commit ab86a3b8e1
34 changed files with 689 additions and 261 deletions
+1
View File
@@ -48,6 +48,7 @@ declare module 'vue' {
RouterView: typeof import('vue-router')['RouterView']
TabBar: typeof import('./src/components/layout/TabBar.vue')['default']
TabMenuItem: typeof import('./src/components/layout/TabMenuItem.vue')['default']
Textarea: typeof import('primevue/textarea')['default']
TheWelcome: typeof import('./src/components/TheWelcome/TheWelcome.vue')['default']
}
}
+1
View File
@@ -63,6 +63,7 @@
"postcss": "^8.4.49",
"prettier": "^3.3.3",
"rollup-plugin-manifest-json": "^1.7.0",
"sass-embedded": "^1.82.0",
"tailwindcss": "^3.4.14",
"tailwindcss-safe-area": "^0.6.0",
"typescript": "~5.6.3",
+16
View File
@@ -29,6 +29,22 @@ registerServiceWorker()
// })
// })
// .catch(console.error)
// import { usePrefersColorSchemeStore } from '@/stores/usePrefersColorSchemeStore'
// import { watch } from 'vue'
// const { isDarkMode } = usePrefersColorSchemeStore()
//
// watch(
// () => isDarkMode,
// () => {
// if (isDarkMode) {
// document.documentElement.classList.add('dark')
// } else {
// document.documentElement.classList.remove('dark')
// }
// },
// { immediate: true },
// )
</script>
<template>
+9 -4
View File
@@ -1,12 +1,17 @@
<template>
<RouterLink :to="route" class="p-4 rounded-lg bg-white shadow-md">
<RouterLink
:to="route"
class="p-4 rounded-lg bg-white dark:bg-gray-800 shadow-md text-gray-800 dark:text-gray-300"
>
<div class="flex items-center justify-between mb-2" v-if="qrCodeDoc">
<div class="flex items-center">
<div class="flex items-center gap-2">
<QRCodePlacementIcon :placement="qrCodeDoc.placement" />
<h2 class="text-lg font-semibold">{{ qrCodeDoc.name }}</h2>
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ qrCodeDoc.name }}
</h2>
</div>
</div>
<div class="text-gray-500">
<div class="text-gray-500 dark:text-gray-400">
<span class="text-nowrap">{{ formatedCreatedAt }}</span>
</div>
</RouterLink>
@@ -6,44 +6,53 @@
</div>
<Checkbox v-else v-model="enabled" inputId="webPush" name="delivery" binary />
<label for="webPush">Прислать уведомление, когда придет ответ</label>
<label for="webPush" class="text-gray-800 dark:text-gray-300">
Прислать уведомление, когда придет ответ
</label>
</div>
<!-- Инструкции для iOS -->
<div
v-if="showRequireStandaloneInstructionsForIOS"
class="bg-yellow-100 text-yellow-800 p-4 rounded my-4"
class="bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-300 p-4 rounded my-4"
>
<p class="mb-2">Чтобы включить уведомления, добавьте это приложение на главный экран:</p>
<p class="mb-2 dark:text-yellow-200">
Чтобы включить уведомления, добавьте это приложение на главный экран:
</p>
<ul class="list-disc pl-5">
<li class="mb-1">
<li class="mb-1 dark:text-yellow-200">
Нажмите
<strong class="font-semibold">
"<img
src="@/assets/icons/apple-share.svg?url"
alt="Поделиться"
class="h-7 -mt-2 inline-block"
class="h-7 -mt-2 inline-block dark:filter dark:invert"
/>"
</strong>
внизу экрана
</li>
<li class="mb-1">
<li class="mb-1 dark:text-yellow-200">
Выберите
<strong class="inline-flex items-baseline font-semibold">
<span class="me-1">"На экран «Домой»</span>
<img
src="@/assets/icons/apple-system-plus.svg?url"
alt=""
class="h-4 inline-block self-center"
/>"
class="h-4 inline-block self-center dark:filter dark:invert"
/>
"
</strong>
</li>
<li>Откройте приложение с главного экрана и повторите попытку.</li>
<li class="dark:text-yellow-200">
Откройте приложение с главного экрана и повторите попытку.
</li>
</ul>
</div>
<!-- Ошибка разрешений -->
<p v-else-if="isError" class="text-red-500 mb-2">{{ error?.message || error }}<br /></p>
<p v-else-if="isError" class="text-red-500 dark:text-red-400 mb-2">
{{ error?.message || error }}<br />
</p>
</div>
</template>
@@ -240,6 +249,6 @@ watch(
)
const showRequireStandaloneInstructionsForIOS = computed(
() => isError.value && isIOS.value && !isStandalone.value && enabled.value,
() => true, //isError.value && isIOS.value && !isStandalone.value && enabled.value,
)
</script>
@@ -1,5 +1,5 @@
<template>
<div class="max-w-screen-sm mx-auto px-4 md:px-0 font-sans text-gray-800">
<div class="max-w-screen-sm mx-auto px-4 md:px-0 font-sans text-gray-800 dark:text-gray-100">
<!-- Hero Section -->
<section class="text-center py-12">
<div class="flex flex-col md:flex-row items-center gap-8">
@@ -14,7 +14,7 @@
</RouterLink>
<div>
<h1 class="text-3xl font-bold">Создайте уникальный QRкод для связи</h1>
<p class="mt-2 text-lg text-gray-600 text-justify">
<p class="mt-2 text-lg text-gray-600 dark:text-gray-400 text-justify">
Люди смогут отправлять вам сообщения, сканируя этот QRкод, без необходимости раскрывать
ваши контактные данные.
</p>
@@ -29,12 +29,12 @@
</div>
</section>
<hr />
<hr class="dark:border-gray-700" />
<!-- Where to Use Section -->
<section id="setup" class="py-8 text-center">
<h2 class="text-2xl font-semibold">Где использовать?</h2>
<p class="mt-2 text-lg text-gray-600 text-justify">
<p class="mt-2 text-lg text-gray-600 dark:text-gray-400 text-justify">
QRкод можно будет разместить на вашем автомобиле, на входной двери или на ошейнике вашего
питомца это позволит окружающим легко связаться с вами в случае необходимости, при этом
ваши контактные данные останутся конфиденциальными.
@@ -46,7 +46,7 @@
<!-- Simple Setup Section -->
<section id="settings" class="py-8 text-center">
<h2 class="text-2xl font-semibold">Легкость настройки</h2>
<p class="mt-2 text-lg text-gray-600 text-justify">
<p class="mt-2 text-lg text-gray-600 dark:text-gray-400 text-justify">
Создайте QRкод, выберите доступные действия и начните использовать.
</p>
<div class="flex justify-center mt-6">
@@ -61,7 +61,7 @@
</section>
<!-- Footer -->
<footer class="py-4 border-t text-center">
<footer class="py-4 border-t text-center dark:border-gray-700">
<p>НаСвязи © 2024</p>
</footer>
</div>
@@ -5,10 +5,15 @@
<InputText
id="newMessage"
v-model="newMessage"
class="flex-1"
class="flex-1 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700"
placeholder="Напишите сообщение"
/>
<Button type="submit" icon="pi pi-send" class="p-button-md" :disabled="!newMessage.trim()" />
<Button
type="submit"
icon="pi pi-send"
class="p-button-md dark:bg-primary-600 dark:hover:bg-primary-700"
:disabled="!newMessage.trim()"
/>
</form>
<ChatSubscribeWebPush :qr_code_uri :chat :user_chat_role="userRole" />
@@ -12,8 +12,10 @@
<Card
class="p-card-sm"
:class="{
'bg-blue-50 border-blue-300 text-blue-500': message.from === userRole,
'bg-gray-50 border-gray-300 text-gray-700': message.from !== userRole,
'bg-blue-50 dark:bg-blue-900 border-blue-300 dark:border-blue-700 text-blue-500 dark:text-blue-200':
message.from === userRole,
'bg-gray-50 dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300':
message.from !== userRole,
}"
>
<template #content>
@@ -1,12 +1,18 @@
<template>
<section>
<h2 class="text-xl font-semibold mb-4">Выберите доступные действия</h2>
<p class="text-gray-600 mb-4">Укажите, какие действия будут доступны для сканирующих QRкод.</p>
<p class="text-sm text-gray-500 mb-6">Вы сможете изменить эти настройки позже.</p>
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">
Выберите доступные действия
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4">
Укажите, какие действия будут доступны для сканирующих QRкод.
</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
Вы сможете изменить эти настройки позже.
</p>
<div class="flex flex-col gap-3 mb-4">
<div v-for="(label, action) in actionsOptions" :key="action" class="flex items-center gap-2">
<Checkbox v-model="model" :inputId="action" :value="action" @change="next" />
<label :for="action">{{ label }}</label>
<label :for="action" class="text-gray-700 dark:text-gray-300">{{ label }}</label>
</div>
</div>
</section>
@@ -1,7 +1,9 @@
<template>
<section>
<h2 class="text-xl font-semibold mb-4">Куда отправлять сообщения?</h2>
<p class="text-gray-600 mb-4">Выберите, как вы хотите получать сообщения.</p>
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">
Куда отправлять сообщения?
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4">Выберите, как вы хотите получать сообщения.</p>
<CreateMessageDeliveryOptions>
<template #telegram="{ label, method }">
<CreateMessageDeliveryOptionTelegram
@@ -9,6 +11,7 @@
:method
v-model="localModel"
@change="onChange"
class="text-gray-700 dark:text-gray-300"
/>
</template>
<template #webPush="{ label, method }">
@@ -17,10 +20,17 @@
:method
v-model="localModel"
@change="onChange"
class="text-gray-700 dark:text-gray-300"
/>
</template>
<template #default="{ label, method }">
<CreateMessageDeliveryOption :label :method v-model="localModel" @change="onChange" />
<CreateMessageDeliveryOption
:label
:method
v-model="localModel"
@change="onChange"
class="text-gray-700 dark:text-gray-300"
/>
</template>
</CreateMessageDeliveryOptions>
</section>
@@ -2,7 +2,7 @@
<CreateMessageDeliveryOption :label :method v-model="model" />
<!-- Сообщение о недоступности -->
<p v-if="telegramNotice" class="text-red-500 mb-2">
<p v-if="telegramNotice" class="text-red-500 dark:text-red-400 my-2 outline outline-1 py-2 px-4">
Отправка в Telegram пока в разработке.<br />
Этот способ отправки сообщений можно будет выбрать позже.
</p>
@@ -4,11 +4,13 @@
<!-- Инструкции для iOS -->
<div
v-if="showRequireStandaloneInstructionsForIOS"
class="bg-yellow-100 text-yellow-800 p-4 rounded my-4"
class="bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-300 p-4 rounded my-4"
>
<p class="mb-2">Чтобы включить уведомления, добавьте это приложение на главный экран:</p>
<p class="mb-2 dark:text-yellow-200">
Чтобы включить уведомления, добавьте это приложение на главный экран:
</p>
<ul class="list-disc pl-5">
<li class="mb-1">
<li class="mb-1 dark:text-yellow-200">
Нажмите
<strong class="font-semibold">
"<img
@@ -19,7 +21,7 @@
</strong>
внизу экрана
</li>
<li class="mb-1">
<li class="mb-1 dark:text-yellow-200">
Выберите
<strong class="inline-flex items-baseline font-semibold">
<span class="me-1">"На экран «Домой»</span>
@@ -30,14 +32,16 @@
/>"
</strong>
</li>
<li>Откройте приложение с главного экрана и повторите попытку.</li>
<li class="dark:text-yellow-200">
Откройте приложение с главного экрана и повторите попытку.
</li>
</ul>
</div>
<!-- Ошибка разрешений -->
<p v-else-if="isError && model === method" class="text-red-500 mb-2">
<p v-else-if="isError && model === method" class="text-red-500 dark:text-red-400 mb-2">
{{ error }}.<br />
<button @click="retry()" class="text-blue-500 underline">Повторить</button>
<button @click="retry()" class="text-blue-500 dark:text-blue-400 underline">Повторить</button>
</p>
</template>
@@ -1,7 +1,11 @@
<template>
<section>
<h2 class="text-xl font-semibold mb-4">Где вы хотите разместить QRкод?</h2>
<p class="text-gray-600 mb-4">Этот выбор поможет настроить QRкод с подходящими действиями.</p>
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">
Где вы хотите разместить QRкод?
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4">
Этот выбор поможет настроить QRкод с подходящими действиями.
</p>
<div class="flex flex-col gap-3 mb-4">
<div v-for="{ label, type } in qrCodePlacements" :key="type" class="flex items-center gap-2">
<RadioButton
@@ -11,7 +15,7 @@
:value="type"
@change="$emit('nextStep')"
/>
<label :for="type">{{ label }}</label>
<label :for="type" class="text-gray-700 dark:text-gray-300">{{ label }}</label>
</div>
</div>
</section>
@@ -1,18 +1,26 @@
<template>
<section>
<h2 class="text-xl font-semibold mb-4">Сообщения для отправки</h2>
<p class="text-gray-600 mb-4">
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">
Сообщения для отправки
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4">
После считывания QRкода можно будет выбрать одно из этих сообщений или написать своё.
</p>
<p class="text-sm text-gray-500 mb-6">Вы сможете изменить эти сообщения позже.</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
Вы сможете изменить эти сообщения позже.
</p>
<!-- Список сообщений -->
<div class="flex flex-col gap-2 mb-4">
<div v-for="(message, index) in messages" :key="index" class="flex gap-2" ref="messagesRef">
<InputText v-model="messages[index]" class="flex-1" placeholder="Введите сообщение" />
<InputText
v-model="messages[index]"
class="flex-1 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700"
placeholder="Введите сообщение"
/>
<Button
icon="pi pi-trash"
class="p-button-danger p-button-text"
variant="text"
@click="removeMessage(index)"
title="Удалить сообщение"
/>
@@ -23,7 +31,7 @@
<Button
label="Добавить сообщение"
icon="pi pi-plus"
class="p-button-sm p-button-outlined mb-4"
class="p-button-sm p-button-outlined mb-4 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300"
@click="addMessage"
/>
</section>
@@ -1,17 +1,28 @@
<template>
<section>
<h2 class="text-xl font-semibold mb-4">Введите название для вашего QRкода</h2>
<p class="text-gray-600 mb-4">Название поможет вам организовать ваши QRкоды.</p>
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">
Введите название для вашего QRкода
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4">
Название поможет вам организовать ваши QRкоды.
</p>
<form @submit.prevent="emit('nextStep')">
<label for="name" class="block text-gray-700 font-semibold mb-2">Название</label>
<InputText id="name" v-model="name" class="w-full" placeholder="Введите название" />
<p v-if="!name" class="text-red-500 text-sm mt-1">Название обязательно.</p>
<label for="name" class="block text-gray-700 dark:text-gray-300 font-semibold mb-2">
Название
</label>
<InputText
id="name"
v-model="name"
class="w-full dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700"
placeholder="Введите название"
/>
<p v-if="!name" class="text-red-500 dark:text-red-400 text-sm mt-1">Название обязательно</p>
<Button
type="submit"
:label="buttonLabel"
class="p-button-md mt-4 mb-4"
class="p-button-md mt-4 mb-4 dark:border-orange-500 dark:hover:border-orange-600 dark:text-gray-300"
:loading="isPending"
:disabled="!name || isPending"
/>
@@ -1,9 +1,9 @@
<template>
<div
class="sticky bottom-0 bg-white shadow-md py-safe-or-4 sm:border-b sm:order-first sm:justify-center touch-none"
class="sticky bottom-0 bg-white dark:bg-gray-800 shadow-md dark:shadow-lg py-safe-or-4 sm:border-b dark:sm:border-gray-700 sm:order-first sm:justify-center touch-none"
>
<div
class="flex justify-around sm:justify-between sm:gap-8 sm:px-4 sm:flex-row sm:bg-white sm:max-w-prose mx-auto"
class="flex justify-around sm:justify-between sm:gap-8 sm:px-4 sm:flex-row sm:bg-white dark:sm:bg-gray-800 sm:max-w-prose mx-auto"
>
<TabMenuItem
v-for="{ route, label, icon, unreadCount } in tabs"
@@ -12,6 +12,7 @@
:icon
:to="{ name: route }"
:unreadCount
class="dark:text-gray-300 dark:hover:text-gray-100"
/>
</div>
</div>
@@ -6,16 +6,26 @@
@click="navigate"
class="flex flex-col items-center w-full sm:flex-row sm:items-center sm:w-auto gap-1 relative"
>
<i :class="['pi', `pi-${icon}`, 'text-2xl sm:mr-2', isActive && 'text-primary-700']"></i>
<i
:class="[
'pi',
`pi-${icon}`,
'text-2xl sm:mr-2',
isActive ? 'text-primary-700 dark:text-primary-300' : 'text-gray-700 dark:text-gray-300',
]"
></i>
<span
class="text-xs font-medium sm:text-base text-center"
:class="{ 'text-primary-700': isActive, 'text-gray-700': !isActive }"
:class="{
'text-primary-700 dark:text-primary-300': isActive,
'text-gray-700 dark:text-gray-300': !isActive,
}"
>
{{ label }}
</span>
<span
v-if="unreadCount && unreadCount > 0"
class="absolute top-0 -right-2.5 bg-red-500 text-white text-xs rounded-full px-1"
class="absolute top-0 -right-2.5 bg-red-500 dark:bg-red-600 text-white text-xs rounded-full px-1"
>
{{ unreadCount }}
</span>
@@ -1,12 +1,24 @@
<template>
<a
:href="url"
class="font-mono text-nowrap text-sm text-gray-600 underline underline-offset-4 hover:text-gray-800 hover:no-underline"
class="font-mono text-nowrap text-sm hover:no-underline"
target="_blank"
>{{ url }}</a
>
v-text="url"
/>
</template>
<style scoped lang="scss">
a {
color: var(--p-surface-500); // Очень нейтральный цвет для менее акцентной ссылки
text-decoration: none;
&:hover {
color: var(--p-surface-700); // Немного более насыщенный цвет при наведении
text-decoration: none;
}
}
</style>
<script setup lang="ts">
defineProps<{ url: string }>()
</script>
@@ -1,11 +1,13 @@
<template>
<div class="max-w-3xl mx-auto py-6 px-4 md:px-6">
<div v-if="isLoading" class="text-gray-500">Загрузка инструкции...</div>
<div v-else-if="data" class="markdown">
<div v-if="isLoading" class="text-gray-500 dark:text-gray-400">Загрузка инструкции...</div>
<div v-else-if="data" class="markdown dark:text-gray-300">
<VueMarkdownIt :source="data" />
</div>
<p v-else-if="isError" class="text-red-500">Ошибка загрузки инструкции.</p>
<p v-else class="text-gray-500">Инструкция для этого типа размещения отсутствует.</p>
<p v-else-if="isError" class="text-red-500 dark:text-red-400">Ошибка загрузки инструкции.</p>
<p v-else class="text-gray-500 dark:text-gray-400">
Инструкция для этого типа размещения отсутствует.
</p>
</div>
</template>
@@ -35,102 +37,97 @@ const { isLoading, isError, data } = useQuery({
})
</script>
<style scoped>
<style scoped lang="scss">
.markdown {
font-family: 'Inter', sans-serif;
line-height: 1.7;
color: #333;
}
color: var(--p-text-color);
.markdown ::v-deep(h1) {
font-size: 2rem;
margin-bottom: 1rem;
color: #111827;
font-weight: 700;
}
& ::v-deep(h1) {
font-size: 2rem;
margin-bottom: 1rem;
color: var(--p-primary-color);
font-weight: 700;
}
.markdown ::v-deep(h2) {
font-size: 1.75rem;
margin-top: 1.5rem;
margin-bottom: 1rem;
color: #1f2937;
font-weight: 600;
}
& ::v-deep(h2) {
font-size: 1.75rem;
margin-top: 1.5rem;
margin-bottom: 1rem;
color: var(--p-secondary-color);
font-weight: 600;
}
.markdown ::v-deep(h3) {
font-size: 1.5rem;
margin-top: 1.25rem;
margin-bottom: 0.75rem;
color: #374151;
font-weight: 500;
}
& ::v-deep(h3) {
font-size: 1.5rem;
margin-top: 1.25rem;
margin-bottom: 0.75rem;
color: var(--p-secondary-light-color);
font-weight: 500;
}
.markdown ::v-deep(p) {
margin-bottom: 1rem;
color: #4b5563;
}
& ::v-deep(p) {
margin-bottom: 1rem;
color: var(--p-text-secondary-color);
}
.markdown ::v-deep(ul) {
list-style: disc;
margin-left: 1.5rem;
margin-bottom: 1rem;
}
& ::v-deep(ul),
& ::v-deep(ol) {
margin-left: 1.5rem;
margin-bottom: 1rem;
}
.markdown ::v-deep(ol) {
list-style: decimal;
margin-left: 1.5rem;
margin-bottom: 1rem;
}
& ::v-deep(li) {
margin-bottom: 0.5rem;
}
.markdown ::v-deep(li) {
margin-bottom: 0.5rem;
}
& ::v-deep(a) {
color: var(--p-link-color);
text-decoration: underline;
}
.markdown ::v-deep(a) {
color: #2563eb;
text-decoration: underline;
}
& ::v-deep(blockquote) {
border-left: 4px solid var(--p-border-color);
padding-left: 1rem;
color: var(--p-text-secondary-color);
margin: 1rem 0;
font-style: italic;
}
.markdown ::v-deep(blockquote) {
border-left: 4px solid #d1d5db;
padding-left: 1rem;
color: #6b7280;
margin: 1rem 0;
font-style: italic;
}
& ::v-deep(code) {
background-color: var(--p-surface-50);
border-radius: 4px;
padding: 0.2rem 0.4rem;
font-family: 'Courier New', Courier, monospace;
color: var(--p-text-color);
}
.markdown ::v-deep(code) {
background-color: #f3f4f6;
border-radius: 4px;
padding: 0.2rem 0.4rem;
font-family: 'Courier New', Courier, monospace;
color: #1f2937;
}
& ::v-deep(pre) {
background-color: var(--p-surface-100);
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
color: var(--p-text-color);
font-family: 'Courier New', Courier, monospace;
}
.markdown ::v-deep(pre) {
background-color: #f9fafb;
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
color: #111827;
font-family: 'Courier New', Courier, monospace;
}
& ::v-deep(table) {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
}
.markdown ::v-deep(table) {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
}
& ::v-deep(th),
& ::v-deep(td) {
border: 1px solid var(--p-border-color);
padding: 0.5rem;
}
.markdown ::v-deep(th),
.markdown ::v-deep(td) {
border: 1px solid #d1d5db;
padding: 0.5rem;
}
.markdown ::v-deep(th) {
background-color: #f9fafb;
font-weight: 600;
text-align: left;
& ::v-deep(th) {
background-color: var(--p-surface-200);
font-weight: 600;
text-align: left;
color: var(--p-text-color);
}
}
</style>
@@ -1,18 +1,17 @@
<template>
<RouterLink :to="`/manage/qr/${qrCode.uri}`" class="p-4 rounded-lg bg-white shadow-md">
<RouterLink
:to="`/manage/qr/${qrCode.uri}`"
class="p-4 rounded-lg bg-white dark:bg-gray-800 shadow-md text-gray-800 dark:text-gray-300"
>
<div class="flex items-center justify-between mb-2">
<div class="flex items-center">
<div class="flex items-center gap-2">
<QRCodePlacementIcon :placement="qrCode.placement" />
<h2 class="text-lg font-semibold">{{ qrCode.name }}</h2>
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ qrCode.name }}
</h2>
</div>
<!-- <Button-->
<!-- label="Настроить"-->
<!-- :to="`/manage/${qrCode.uri}/settings`"-->
<!-- as="router-link"-->
<!-- class="p-button-outlined p-button-sm"-->
<!-- />-->
</div>
<div class="text-gray-500">
<div class="text-gray-500 dark:text-gray-400">
Создан: <span class="text-nowrap">{{ formatedCreatedAt }}</span>
</div>
</RouterLink>
@@ -1,5 +1,5 @@
<template>
<i :class="['pi', iconClass, 'text-gray-500', 'mr-2']" />
<i :class="['pi', iconClass, 'text-gray-500', 'dark:text-gray-300', 'mr-2']" />
</template>
<script lang="ts" setup>
@@ -1,7 +1,9 @@
<template>
<section class="max-w-prose w-full text-center">
<!-- Контактная информация -->
<div class="flex items-start bg-gray-100 border rounded-lg p-4">
<div
class="flex items-start bg-gray-100 dark:bg-gray-800 border dark:border-gray-700 rounded-lg p-4"
>
<!-- Аватарка Gravatar -->
<div v-if="contactInfo.email" class="flex-shrink-0 mr-4">
<img
@@ -14,18 +16,23 @@
<!-- Контакты -->
<div class="text-left flex-grow">
<p v-if="contactInfo.name" class="text-lg font-semibold">{{ contactInfo.name }}</p>
<p v-if="contactInfo.name" class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ contactInfo.name }}
</p>
<p v-if="contactInfo.phone" class="text-gray-700">
<p v-if="contactInfo.phone" class="text-gray-700 dark:text-gray-300">
Телефон:
<a :href="`tel:${formattedPhone}`" class="underline text-blue-600">
<a :href="`tel:${formattedPhone}`" class="underline text-blue-600 dark:text-blue-400">
{{ contactInfo.phone }}
</a>
</p>
<p v-if="contactInfo.email" class="text-gray-700">
<p v-if="contactInfo.email" class="text-gray-700 dark:text-gray-300">
Email:
<a :href="`mailto:${contactInfo.email}`" class="underline text-blue-600">
<a
:href="`mailto:${contactInfo.email}`"
class="underline text-blue-600 dark:text-blue-400"
>
{{ contactInfo.email }}
</a>
</p>
@@ -40,8 +47,12 @@
</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>
<p v-if="success" class="text-green-500 dark:text-green-400 mt-4">
Контакт успешно добавлен в адресную книгу!
</p>
<p v-if="error" class="text-red-500 dark:text-red-400 mt-4">
Ошибка добавления контакта: {{ error }}
</p>
</section>
</template>
@@ -1,6 +1,9 @@
<template>
<section class="max-w-prose w-full">
<p class="text-gray-600 mb-4 text-center" v-if="doc.predefinedMessages?.length">
<p
class="text-gray-600 dark:text-gray-400 mb-4 text-center"
v-if="doc.predefinedMessages?.length"
>
Выберите сообщение из списка или напишите своё:
</p>
@@ -10,7 +13,7 @@
<Button
v-for="(message, index) in doc.predefinedMessages"
:key="index"
class="p-button-outlined w-full text-left"
class="w-full p-button-outlined text-left dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700"
:disabled="loading"
@click="sendPresetMessage(message)"
>
@@ -24,20 +27,22 @@
<InputText
id="customMessage"
v-model="customMessage"
class="flex-1"
class="flex-1 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700"
placeholder="Напишите сообщение"
:disabled="loading"
/>
<Button
type="submit"
icon="pi pi-send"
class="p-button-md"
class="p-button-md dark:bg-primary-600 dark:hover:bg-primary-700"
: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>
<p v-if="success" class="text-green-500 dark:text-green-400 mt-4 text-center">
Сообщение отправлено!
</p>
<p v-if="error" class="text-red-500 dark:text-red-400 mt-4 text-center">Ошибка: {{ error }}</p>
</section>
</template>
+10 -2
View File
@@ -2,6 +2,7 @@
<div
class="grid h-full sm:grid-rows-[auto_1fr_auto] grid-rows-[1fr_auto_auto] overflow-hidden touch-none"
>
<!-- Content Section -->
<div
class="flex overflow-hidden overflow-y-auto pb-safe-or-4 sm:pt-safe px-4 overscroll-contain touch-manipulation"
ref="contentScrollContainer"
@@ -11,20 +12,27 @@
</div>
</div>
<!-- Footer Section -->
<slot name="footer">
<footer
v-if="!slots.footer"
class="bg-gray-100 text-center py-1.5 border-t text-xs sm:text-base text-gray-700 sm:order-last sm:pb-safe-or-1.5"
class="bg-gray-100 dark:bg-gray-800 text-center py-1.5 border-t dark:border-gray-700 text-xs sm:text-base text-gray-700 dark:text-gray-300 sm:order-last sm:pb-safe-or-1.5"
>
<span class="text-nowrap">Сделайте НаСвязи удобнее.</span>
<span> </span>
<span class="text-nowrap">
Будем рады
<a href="/donate" class="underline underline-offset-2">вашей поддержке</a>
<a
href="/donate"
class="underline underline-offset-2 text-primary-600 dark:text-primary-400"
>
вашей поддержке
</a>
</span>
</footer>
</slot>
<!-- TabBar -->
<TabBar />
</div>
</template>
+3 -22
View File
@@ -6,29 +6,10 @@ import { VueQueryPlugin, type VueQueryPluginOptions } from '@tanstack/vue-query'
import PouchDB from '@/api/PouchDB'
import PrimeVue from 'primevue/config'
import { definePreset } from '@primevue/themes'
import Aura from '@primevue/themes/aura'
import { PrimeVueHereConnectPreset } from './themes/PrimeVueHereConnectPreset'
import './style.css'
import 'primeicons/primeicons.css'
const MyPreset = definePreset(Aura, {
semantic: {
primary: {
50: '{rose.50}',
100: '{rose.100}',
200: '{rose.200}',
300: '{rose.300}',
400: '{rose.400}',
500: '{rose.500}',
600: '{rose.600}',
700: '{rose.700}',
800: '{rose.800}',
900: '{rose.900}',
950: '{rose.950}',
},
},
})
import App from './App.vue'
import router from './router'
@@ -55,9 +36,9 @@ app.use(VueQueryPlugin, vueQueryPluginOptions)
app.use(router)
app.use(PrimeVue, {
theme: {
preset: MyPreset,
preset: PrimeVueHereConnectPreset,
options: {
darkModeSelector: false,
darkModeSelector: 'system',
},
},
})
@@ -0,0 +1,13 @@
import { ref, watch } from 'vue'
import { defineStore } from 'pinia'
export const usePrefersColorSchemeStore = defineStore('prefersColorScheme', () => {
const prefersColorSchemeDarkMatchMedia = window.matchMedia('(prefers-color-scheme: dark)')
const isDarkMode = ref<boolean>(prefersColorSchemeDarkMatchMedia.matches)
prefersColorSchemeDarkMatchMedia.addEventListener('change', (e) => {
isDarkMode.value = e.matches
})
return { isDarkMode }
})
@@ -0,0 +1,45 @@
import { definePreset } from '@primevue/themes'
import Aura from '@primevue/themes/aura'
export const PrimeVueHereConnectPreset = definePreset(Aura, {
semantic: {
colorScheme: {
light: {
primary: {
50: '{rose.50}',
100: '{rose.100}',
200: '{rose.200}',
300: '{rose.300}',
400: '{rose.400}',
500: '{rose.500}',
600: '{rose.600}',
700: '{rose.700}',
800: '{rose.800}',
900: '{rose.900}',
950: '{rose.950}',
},
highlight: {
background: '{rose.100}',
focusBackground: '{rose.200}',
color: '#000000',
focusColor: '#000000',
},
},
dark: {
primary: {
50: '{orange.50}',
100: '{orange.100}',
200: '{orange.200}',
300: '{orange.300}',
400: '{orange.400}',
500: '{orange.500}',
600: '{orange.600}',
700: '{yellow.700}',
800: '{orange.800}',
900: '{orange.900}',
950: '{orange.950}',
},
},
},
},
})
+3 -1
View File
@@ -39,7 +39,9 @@
</template>
</CreateSectionManager>
<p v-if="isError" class="text-red-500 mt-4">Ошибка создания QRкода: {{ error }}</p>
<p v-if="isError" class="text-red-500 dark:text-red-400 mt-4">
Ошибка создания QRкода: {{ error }}
</p>
<div class="h-[30dvh] md:h-[10dvh] overflow-hidden"></div>
</LayoutWithTabs>
+12 -31
View File
@@ -1,7 +1,9 @@
<template>
<LayoutWithTabs>
<template #footer>
<footer class="py-4 border-t text-center text-sm hidden sm:block">
<footer
class="py-4 border-t text-center text-sm hidden sm:block border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400"
>
<p>НаСвязи © 2024</p>
</footer>
</template>
@@ -12,8 +14,10 @@
</MetaHead>
<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">
<h1 class="text-2xl font-bold mb-4 text-gray-900 dark:text-gray-100">
Поддержите развитие НаСвязи
</h1>
<p class="text-gray-600 dark:text-gray-400 mb-6">
Мы делаем всё, чтобы НаСвязи оставался удобным и полезным. Вы можете помочь нам, поддержав
проект на Бусти.
</p>
@@ -24,7 +28,7 @@
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"
class="inline-block bg-orange-500 text-white font-semibold py-3 px-6 rounded-lg shadow hover:bg-orange-600 transition duration-300 dark:hover:bg-orange-700"
>
Поддержать через Бусти
</a>
@@ -32,8 +36,10 @@
<!-- Рассказать друзьям -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-4">Рассказать друзьям</h2>
<p class="text-gray-600 mb-4">Поделитесь ссылкой на НаСвязи:</p>
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">
Рассказать друзьям
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4">Поделитесь ссылкой на НаСвязи:</p>
<div class="flex justify-center gap-6">
<!-- Telegram -->
<a
@@ -56,31 +62,6 @@
</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>
</LayoutWithTabs>
</template>
+5 -3
View File
@@ -19,10 +19,12 @@
<!-- Уведомление, если гость отправил только одно сообщение -->
<div
v-if="count === 1 && lastMessage && lastMessage.from === 'guest'"
class="flex flex-col items-center justify-center text-center bg-gray-50 border border-gray-200 rounded-lg shadow-md p-6 my-6"
class="flex flex-col items-center justify-center text-center bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-md p-6 my-6"
>
<p class="text-gray-700 font-medium text-lg">Ваше сообщение отправлено.</p>
<p class="text-gray-600 mt-2">
<p class="text-gray-700 dark:text-gray-200 font-medium text-lg">
Ваше сообщение отправлено.
</p>
<p class="text-gray-600 dark:text-gray-400 mt-2">
Вы можете написать ещё одно сообщение или подождать ответа.
</p>
</div>
+9 -3
View File
@@ -22,15 +22,21 @@
</section>
</main>
<footer class="bg-gray-100 text-center py-4 border-t text-sm text-gray-700">
<footer
class="bg-gray-100 dark:bg-gray-800 text-center py-4 border-t dark:border-gray-700 text-sm text-gray-700 dark:text-gray-300"
>
<p>
Нужен такой же?
<a href="/create" class="underline underline-offset-2">Создайте свой QRкод</a>.
<a
href="/create"
class="underline underline-offset-2 text-primary-600 dark:text-primary-400"
>Создайте свой QRкод</a
>.
</p>
<p class="mt-1">
Сделайте НаСвязи удобнее. Будем рады вашей&nbsp;<a
href="/donate"
class="underline underline-offset-2"
class="underline underline-offset-2 text-primary-600 dark:text-primary-400"
>поддержке</a
>.
</p>
@@ -18,24 +18,28 @@
<section class="max-w-prose w-full flex flex-col gap-1">
<div
v-if="svgQueryIsLoading || qrCodeQueryIsLoading"
class="text-gray-500 mt-4 text-center"
class="text-gray-500 dark:text-gray-400 mt-4 text-center"
>
<ProgressSpinner styleClass="mt-4" />
</div>
<p v-else-if="svgQueryIsError" class="text-red-500 mt-4 text-center">
<p v-else-if="svgQueryIsError" class="text-red-500 dark:text-red-400 mt-4 text-center">
Ошибка генерации QRкода: {{ svgQueryError?.message }}
</p>
<p v-else-if="qrCodeQueryIsError" class="text-red-500 mt-4 text-center">
<p v-else-if="qrCodeQueryIsError" class="text-red-500 dark:text-red-400 mt-4 text-center">
Ошибка загрузки данных QRкода: {{ qrCodeQueryError!.message }}
</p>
<template v-else>
<h2 class="text-xl font-semibold mb-4">Ваш QRкод готов</h2>
<p class="text-gray-600 mb-4">
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">
Ваш QRкод готов
</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4">
Сохраните его и ознакомьтесь с инструкциями по размещению.
</p>
<!-- Рендер QRкода -->
<div class="border p-4 bg-white rounded-lg mb-6">
<div class="border dark:border-gray-700 p-4 bg-white rounded-lg mb-6">
<!-- dark:bg-gray-800 -->
<img v-if="!svgQueryIsLoading && svgQueryData" :src="svgQueryData" alt="QR‑код" />
</div>
@@ -47,7 +51,7 @@
<Button
label="Скачать SVG"
icon="pi pi-download"
class="p-button-sm"
class="p-button-sm dark:border-orange-500 dark:hover:border-orange-600 dark:text-gray-300"
:href="svgQueryData"
:download="`${sanitizedFileName}.svg`"
as="a"
+12 -1
View File
@@ -6,7 +6,18 @@ import tailwindcssSafeArea from 'tailwindcss-safe-area'
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {},
extend: {
colors: {
// primary: {
// 400: '#FFA726', // Светлый акцентный цвет для темной темы
// 600: '#FB8C00', // Основной акцентный цвет для светлой темы
// },
// orange: {
// 600: '#FB923C',
// 700: '#F97316',
// },
},
},
},
plugins: [tailwindScrollbar, tailwindcssPrimeUI, tailwindcssSafeArea],
}