before liquid glass

This commit is contained in:
2026-01-03 18:14:30 +03:00
parent 4cad91440c
commit cbaa1cda9d
64 changed files with 1927 additions and 1174 deletions

View File

@@ -1,5 +1,8 @@
<template>
<doc-block title="subscription_guide__title" document-name="Subscription_guide"/>
<doc-block
title="subscription_guide__title"
document-name="Subscription_guide"
/>
</template>
<script setup lang="ts">

View File

@@ -38,39 +38,47 @@
/>
</pn-item-btm-dialog>
<q-item-label class="q-mt-md" header>{{ $t('settings__bot_title') }}</q-item-label>
<template v-if="authStore.isAuth">
<q-item-label
class="q-mt-md"
header
>
{{ $t('settings__bot_title') }}
</q-item-label>
<pn-item-btm-dialog
title="settings__language"
caption="settings__bot_title"
icon="mdi-translate"
iconColor="primary"
>
<template #value>
{{ localeOptions.find(el => el.value === localeBot)?.label }}
</template>
<pn-list-selector
v-model="localeBot"
:options="localeOptions"
/>
</pn-item-btm-dialog>
<pn-item-btm-dialog
title="settings__timezone"
caption="settings__bot_title"
icon="mdi-map-clock-outline"
iconColor="primary"
v-if="timeZoneBot"
>
<template #value>
{{ timeZoneBot.tz }}
{{ timeZoneBot.offsetString }}
</template>
<pn-item-btm-dialog
title="settings__language"
caption="settings__bot_title"
icon="mdi-translate"
iconColor="primary"
>
<template #value>
{{ localeOptions.find(el => el.value === localeBot)?.label }}
</template>
<pn-list-selector
v-model="localeBot"
:options="localeOptions"
/>
</pn-item-btm-dialog>
<pn-time-zone-selector
v-model="timeZoneBot"
:locale
/>
</pn-item-btm-dialog>
<pn-item-btm-dialog
title="settings__timezone"
caption="settings__bot_title"
icon="mdi-map-clock-outline"
iconColor="primary"
v-if="timeZoneBot"
>
<template #value>
{{ timeZoneBot.tz }}
{{ timeZoneBot.offsetString }}
</template>
<pn-time-zone-selector
v-model="timeZoneBot"
:locale
/>
</pn-item-btm-dialog>
</template>
</q-list>
</pn-scroll-list>
</pn-page-card>
@@ -79,12 +87,14 @@
<script setup lang="ts">
import { ref, watch, computed, onMounted } from 'vue'
import { useSettingsStore } from 'stores/settings'
import { useAuthStore } from 'stores/auth'
import pnItemBtmDialog from 'components/pnItemBtmDialog.vue'
import pnListSelector from 'components/pnListSelector.vue'
import pnTimeZoneSelector from 'components/pnTimeZoneSelector.vue'
const settingsStore = useSettingsStore()
const authStore = useAuthStore()
const localeOptions = settingsStore.supportLocale
const locale = ref('')

View File

@@ -1,313 +1,162 @@
<!-- eslint-disable @typescript-eslint/ban-ts-comment -->
<!-- eslint-disable @typescript-eslint/ban-ts-comment -->
<!-- eslint-disable @typescript-eslint/ban-ts-comment -->
<template>
<pn-page-card>
<template #title>
{{$t('subscribe__title')}}
</template>
<pn-scroll-list >
<pn-scroll-list>
<template #card-body-header>
<div
v-if="tariffs && currentTariff"
id="subscribe-current-balance"
class="flex w100 q-px-md q-py-md row"
>
<div class="flex w100 justify-between items-center no-wrap">
<div class="flex items-center text-grey">
{{ $t('subscribe__current_plan') }}
{{ $t('subscribe__current_tariff_title') }}
</div>
<div class="flex items-center column text-right">
<span class="text-bold">
{{ currentPlanData?.name }}
{{ currentTariff?.tariffInfo.name }}
</span>
</div>
</div>
<div
class="row w100 no-wrap items-center text-caption justify-between"
v-if="currentPlanData.name !== 'TEST'"
v-if="!isCurrentTariffTest"
>
<div class="text-grey text-no-wrap ellipsis">
{{ $t('subscribe__plan_date') }}
{{ $t('subscribe__current_tariff_date') }}
</div>
<span class="text-no-wrap text-right">
{{ date.formatDate(currentPlanData.exp * 1000, 'DD.MM.YYYY') }}
<span class="text-no-wrap text-right" v-if="currentTariff.end_date">
{{ date.formatDate(currentTariff.end_date * 1000, 'DD.MM.YYYY') }}
</span>
</div>
<div class="row w100 no-wrap items-center text-caption text-grey justify-between">
<div>
{{ $t('subscribe__plan_active_chats') }}
{{ $t('subscribe__current_tariff_chat_limit') }}
</div>
<div class="text-right">
<span class="text-brand2 text-bold q-pr-xs">{{ currentPlanData?.active_chats }}</span>
<span>/{{ currentPlanData?.chatsQty }}</span>
<span
class="text-bold q-pr-xs"
:class="currentTariff.tariffInfo.chat_limit && (currentTariff.tariffInfo.chat_limit > currentTariff?.used_chats) ? 'negative' : ''"
>
{{ currentTariff.used_chats }}
</span>
<span v-if="currentTariff.tariffInfo.chat_limit">
/{{ currentTariff.tariffInfo.chat_limit }}
</span>
</div>
</div>
</div>
</template>
<div class="w100 column items-center text-center text-grey q-px-md">
{{ $t('subscribe__plans')}}
<div class="text-caption">
{{ $t('subscribe__plans_description') }}
</div>
</div>
<q-list separator>
<q-item
v-for="item in plans"
:key="item.id"
clickable
v-ripple
tag="label"
:disable="item.name === 'TEST' && currentPlanData.name !== 'TEST'"
>
<q-item-section avatar>
<q-radio
v-model="newPlan"
:val="item"
:disable="item.name === 'TEST' && currentPlanData.name != 'TEST'"
:keep-color="item.name === 'TEST'&& currentPlanData.name !== 'TEST'"
:color="item.name === 'TEST' && currentPlanData.name !== 'TEST' ? 'grey-5' : ''"
/>
</q-item-section>
<q-item-section>
<div class="column">
<span class="text-bold">
{{ item.name }}
</span>
<div class="flex no-wrap items-center text-caption" style="line-height: 1em;">
<span v-if="item.chatsQty">
{{ $t('subscribe__chats_max') + ' ' + item.chatsQty }}
</span>
<q-icon v-else name="mdi-all-inclusive" size="sm"/>
<span class="q-pl-xs">
{{ $t('subscribe__chats')}}
</span>
</div>
<span
v-if="item.name === 'TEST' && currentPlanData.name !== 'TEST'"
class="text-caption text-grey-7"
>
{{ $t('subscribe__TEST_via_support')}}
</span>
</div>
</q-item-section>
<q-item-section
class="text-left"
:style="{ width: `${maxWidthStarsSection}px` }"
>
<q-resize-observer @resize="updateMaxWidthStarsSection" />
<div v-if="item.price" class="flex column justify-start">
<div class="flex no-wrap items-center">
<telegram-star color="gold" size="18px" class="q-mr-xs"/>
<span class="text-h6">{{ formatNumber(item.price) }}</span>
<span class="text-caption q-pl-xs">{{ $t('subscribe__per_month') }}</span>
</div>
</div>
<div v-else class="text-bold self-center">
{{ $t('subscribe__free_tax') }}
</div>
</q-item-section>
</q-item>
</q-list>
<div v-if="tariffs && currentTariff">
<subscribe-upgrade
v-if="isCurrentTariffTest"
:tariffs
:periods
@tariff-upgrade="prepareTgBtn"
/>
<div class="q-px-md">
<div class="w100 text-grey text-center q-pt-sm">
{{ $t('subscribe__plans_interval') }}
</div>
<q-btn-group spread flat class="w100 q-py-sm">
<q-btn
v-for="period in periods"
:key="period.id"
:outline="selectPeriod === period.value"
@click="selectPeriod = period.value"
:color="selectPeriod === period.value ? 'primary' : ''"
no-caps dense
>
<div class="column items-center w100 self-end">
<span
class="text-h6"
:class="selectPeriod === period.value ? 'text-primary' : 'text-black'"
>
{{ $t(period.name) }}
</span>
<div
class="row no-wrap w100 q-px-xs"
:class="period.sale ? 'justify-between' : 'justify-center'"
>
<span
class="text-caption text-grey"
>
{{ $t(period.name_in_days) }}
</span>
<q-badge v-show="period.sale" color="red">-{{ period.sale }}%</q-badge>
</div>
</div>
</q-btn>
</q-btn-group>
<div class="text-caption text-grey">
<span>{{ $t('subscribe__plans_period_notes') + ' ' }}</span>
<span
@click="router.push({ name: 'change_plan_rules' })"
class="text-info cursor-pointer"
>
{{ $t('subscribe__plans_period_notes_more_info') }}
</span>
</div>
<q-input
v-model="promocode"
dense
filled
:label="$t('subscribe__promocode')"
class="q-pt-md"
>
<template #append v-if="promocode !== ''">
<q-btn
round dense flat
color="primary"
icon="mdi-arrow-right-circle-outline"
@click="sendPromocode"
/>
</template>
</q-input>
<subscribe-change-renew
v-else
:tariffs
:periods
:currentTariff
@update="prepareTgBtn"
/>
</div>
</pn-scroll-list>
</pn-page-card>
</template>
<script setup lang="ts">
import { ref, computed, inject, onUnmounted, watch } from 'vue'
import telegramStar from 'components/TelegramStar.vue'
import { useRouter } from 'vue-router'
import { date } from 'quasar'
import { useI18n } from 'vue-i18n'
import { ref, computed, inject, onMounted, onUnmounted } from 'vue'
import { date, colors } from 'quasar'
import subscribeUpgrade from 'components/subscribe/subscribeUpgrade.vue'
import subscribeChangeRenew from 'components/subscribe/subscribeChangeRenew.vue'
import {
getTariffs,
getTariffDiscount
} from 'composables/useSubscription'
import { useAuthStore } from 'stores/auth'
import type { Tariff } from 'types/Tariff'
import type { WebApp } from '@twa-dev/types'
import { colors } from 'quasar'
import { useQuasar } from 'quasar'
const router = useRouter()
const authStore = useAuthStore()
const tg = inject('tg') as WebApp
// @ts-expect-error: get hex text
tg.MainButton.color = colors.getPaletteColor('primary')
const plans = [
{ id: 1, name: 'TEST', val: 'test', price: null, chatsQty: 5 },
{ id: 2, name: 'START', val: 'start', price: 1000, chatsQty: 15 },
{ id: 3, name: 'PRO', val: 'pro', price: 5000, chatsQty: 40 },
{ id: 4, name: 'VIP', val: 'vip', price: 12000, chatsQty: null }
const tariffs = ref<Tariff[] | null>(null)
const discountTariff = ref(null)
const testTariffId = computed(() => (tariffs.value && tariffs.value.find(el => el.price === 0))?.id ?? 1)
const isCurrentTariffTest = computed(() => currentTariff.value?.id === testTariffId.value)
const storeTariff = authStore.customer?.plan
const currentTariff = computed(() => {
if (tariffs.value && storeTariff) {
const tariffInfo = tariffs.value.find((el: Tariff) => el.id === storeTariff.id)
if (tariffInfo) return { ...storeTariff, tariffInfo }
}
return null
})
onMounted(async () => {
tariffs.value = await getTariffs()
discountTariff.value = await getTariffDiscount()
})
const basePeriods = [
{ id: 30, name: 'subscribe__1month', name_in_days: 'subscribe__30days' },
{ id: 91, name: 'subscribe__3months', name_in_days: 'subscribe__91days' },
{ id: 365, name: 'subscribe__1year', name_in_days: 'subscribe__365days' }
] as const
const periods = [
{ id: 1, name: 'subscribe__1month', name_in_days: 'subscribe__30days', value: 30, sale: 0 },
{ id: 2, name: 'subscribe__3months', name_in_days: 'subscribe__91days', value: 91, sale: 5 },
{ id: 3, name: 'subscribe__1year', name_in_days: 'subscribe__365days', value: 365, sale: 15 }
]
const selectPeriod = ref(periods[1]?.value)
const newPlan = ref(plans[0])
interface CurrentPlan {
plan: string
exp: number | null
active_chats: number | null
}
const currentPlan = ref<CurrentPlan>({ // temp, this get from api
plan: plans[1].val,
active_chats: 20,
exp: Date.now() / 1000 + 500000
})
interface CurrentPlanData extends CurrentPlan {
price: number | null
}
const currentPlanData = computed((): CurrentPlanData => ({
active_chats: currentPlan.value.active_chats ?? null,
exp: currentPlan.value.exp ?? null,
...plans.find(el=> el?.val === currentPlan.value.plan)
const periods = computed(() => basePeriods.map(el => {
const discount = discountTariff.value ? discountTariff.value[el.id] : 0
return {
...el,
discount
}
}))
const { t } = useI18n()
const textBtn = computed(() => {
const prorata = currentPlanData.value.price
? Math.ceil(
currentPlanData.value?.price / 30 *
date.getDateDiff(new Date(currentPlan.value.exp * 1000), Date.now())
)
: 0
const k = 1 - Number(periods.find(el => el.value === selectPeriod.value)?.sale) / 100
const stars = formatNumber(
Number(selectPeriod.value) *
Number(newPlan.value?.price) *
k - prorata
)
const newDateExp = date.addToDate(Date.now(), { months: Number(selectPeriod.value) })
return t(newPlan.value.id !== currentPlanData.value.id ? 'subscribe__pay' : 'subscribe__pay_renew') +
' ⭐' + stars +
' (' + t('subscribe__plan_exp') + ' ' +
date.formatDate(newDateExp, 'DD.MM.YYYY') + ')'
})
function formatNumber (number: string | number) {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ")
interface tgBtnData {
status: 'hide' | 'show'
url?: string
textBtn?: string
}
onUnmounted(() => tg.MainButton.hide())
const currentButtonHandler = ref<(() => void) | null>(null)
function defaultTgBtn () {
tg.MainButton.setText('-')
tg.MainButton.hide()
watch(textBtn, () => tg.MainButton.setText(textBtn.value),
{ immediate: true })
watch(newPlan, () =>
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
{ newPlan.value.name === 'TEST' ? tg.MainButton.hide() : tg.MainButton.show() },
{ immediate: true })
const maxWidthStarsSection = ref(0)
interface sizeParams {
height: number,
width: number
if (currentButtonHandler.value) {
tg.MainButton.offClick(currentButtonHandler.value)
currentButtonHandler.value = null
}
}
function updateMaxWidthStarsSection (size: sizeParams) {
if (size.width > maxWidthStarsSection.value) {
maxWidthStarsSection.value = size.width
function prepareTgBtn (btn: tgBtnData) {
defaultTgBtn()
if (btn.status === 'show' && btn.textBtn && btn.url) {
tg.MainButton.setText(btn.textBtn)
tg.MainButton.show()
const handler = () => tg.openInvoice(btn.url!)
currentButtonHandler.value = handler
tg.MainButton.onClick(handler)
}
}
const promocode = ref('')
function sendPromocode () {
promocode.value = ''
showNotify('fail')
}
const $q = useQuasar()
const showNotify = (message: 'success' | 'fail') => {
$q.notify({
message: t(message === 'success' ? 'subscribe__promocode_success' : 'subscribe__promocode_fail'),
type: message === 'success' ? 'positive' : 'negative',
position: 'bottom',
timeout: 1000,
multiLine: true
})
}
onUnmounted(() => defaultTgBtn())
</script>
<style lang="scss">

View File

@@ -42,8 +42,8 @@
import type { WebApp } from '@twa-dev/types'
const tg = inject('tg') as WebApp
const supportEmailAddress = 'a-mart@ya.ru'
const supportTelegramUser = 'alexmart80'
const supportEmailAddress = 'support@tgcrew.ru'
const supportTelegramUser = 'tgCrewSupport'
const telegramUrl = `https://t.me/${supportTelegramUser}`