feat: align piggy, support chat and numeric input with modular app

Persian keyboards send ٫ ، and / as decimal or thousand separators, so amount
and weight fields now normalize input before parsing. Support chat reopens an
existing thread instead of starting a new one, and axios surfaces the server
message on failure.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-11 22:17:17 +03:30
co-authored by Cursor
parent d2183c9a3c
commit 5d3ca82236
16 changed files with 694 additions and 344 deletions
+11
View File
@@ -77,6 +77,17 @@ export function setupRouterGuards() {
}
}
if (to.name === 'giftCard' && getToken()) {
const { useSettingsStore } = await import('@/store/settingsStore')
const settingsStore = useSettingsStore()
if (!settingsStore.loaded) {
await settingsStore.fetchSettings()
}
if (!settingsStore.isSettingTruthy('has_page_gift_card')) {
return next({ name: (from.name as string) || 'user' })
}
}
if (isFirst) {
isFirst = false
+22 -6
View File
@@ -14,6 +14,7 @@
:type="props.type"
class=""
@change="handlerChange"
@input="handlerChange"
/>
<a-input
v-else
@@ -28,6 +29,7 @@
:pattern="props.pattern"
class=""
@change="handlerChange"
@input="handlerChange"
/>
</div>
</template>
@@ -36,7 +38,12 @@
import {usePrefix} from "@/utils/usePrefix";
import {computed} from "vue";
import {usePrice} from "@/components/BasicPacks/BasicPriceInput/src/utils/usePrice";
import {persianToEnglishDigits, shouldNormalizeInputDigits} from '@/utils/persianDigits';
import {
normalizeDecimalInput,
persianToEnglishDigits,
shouldNormalizeDecimalSeparators,
shouldNormalizeInputDigits,
} from '@/utils/persianDigits';
//Props
interface Prop {
@@ -79,18 +86,27 @@ function normalizeValue(value: unknown) {
return value
}
if (shouldNormalizeDecimalSeparators(props)) {
const isPrice = String(props.type ?? '').trim().toLowerCase() === 'price'
return normalizeDecimalInput(value as string | number, {
commaAsThousand: isPrice,
})
}
let normalized = String(value)
if (shouldNormalizeInputDigits(props)) {
normalized = persianToEnglishDigits(normalized)
}
if (props.type === 'price') {
normalized = normalized.replaceAll(',', '')
}
return normalized
}
const handlerChange = (e: any) => {
const normalized = normalizeValue(e.target.value)
const handlerChange = (e: Event | string) => {
const target = typeof e === 'string' ? null : (e.target as HTMLInputElement | null)
const raw = typeof e === 'string' ? e : target?.value
const normalized = normalizeValue(raw)
if (target && typeof normalized === 'string' && target.value !== normalized) {
target.value = normalized
}
emits('update:value', normalized)
emits('onchange', normalized)
}
@@ -10,7 +10,7 @@
import {usePrice} from './utils/usePrice'
import {computed} from "vue";
import BasicInput from "../../BasicInput.vue";
import {persianToEnglishDigits} from '@/utils/persianDigits'
import {normalizeDecimalInput} from '@/utils/persianDigits'
interface Props {
value?: string | number
@@ -24,7 +24,7 @@ const props = withDefaults(defineProps<Props>(), {})
const _val = computed({
set(val: string) {
emit('update:value', persianToEnglishDigits(val).replaceAll(',', ''))
emit('update:value', normalizeDecimalInput(val))
},
get() {
return props.value ? usePrice(props.value as number, true, '').trim() : ''
+9 -11
View File
@@ -7,7 +7,6 @@ import {
loadSupportMessengerFromSettings,
messengerNeedsClipboardCopy,
openSupportMessengerEntry,
openSupportMessengerWithMessage,
resolveDepositGuideText,
resolveDepositSupportMessage,
resolveSupportMessengerEntries,
@@ -138,23 +137,22 @@ export function useSupportMessenger() {
const messengerEntries = supportMessengerEntries.value
const entries = pickerEntries.value
// لیست خالی + چت خاموش → هیچ‌کار
if (entries.length === 0) {
return false
}
// فقط چت (لیست پیام‌رسان خالی) → مستقیم چت
if (isChatEnabled.value && messengerEntries.length === 0) {
return goToSupportChat()
}
if (entries.length === 0) {
return openSupportMessengerWithMessage(
supportMessengerSettings.value.messenger,
supportMessengerSettings.value.messengerId,
draft,
legacySupportLink.value,
)
}
// فقط یک گزینه (چت یا یک پیام‌رسان) → مستقیم همان
if (entries.length === 1) {
return openMessengerEntry(entries[0], draft)
}
// چند گزینه (چت + لینک‌ها یا چند پیام‌رسان) → منوی انتخاب
const now = Date.now()
if (supportPickerOpen.value) {
if (now - supportPickerOpenedAt < 450) return true
@@ -249,7 +247,7 @@ export function useSupportMessenger() {
}
const hasMessengerLink = computed(
() => isChatEnabled.value || supportMessengerEntries.value.length > 0 || !!messengerLink.value,
() => isChatEnabled.value || supportMessengerEntries.value.length > 0,
)
const hasMultipleMessengers = computed(() => pickerEntries.value.length > 1)
const hasSupportPhoneLink = computed(() => !!supportPhoneLink.value)
+3
View File
@@ -64,6 +64,9 @@ const secondaryItems = computed(() =>
if (item.id === 'notifyTrigger') {
return settingsStore.isSettingTruthy('has_page_price_trigger')
}
if (item.id === 'giftCard') {
return settingsStore.isSettingTruthy('has_page_gift_card')
}
return true
}),
)
+4 -1
View File
@@ -71,7 +71,7 @@
<!-- </div>-->
<!-- </BasicButton>-->
<BasicButton
v-if="authenticated"
v-if="authenticated && giftCardEnabled"
icon-size="16"
class="!font-bold w-15rem"
@click="go('giftCard')"
@@ -138,6 +138,9 @@ const settingsStore = useSettingsStore()
const priceTriggerEnabled = computed(() =>
settingsStore.isSettingTruthy('has_page_price_trigger'),
)
const giftCardEnabled = computed(() =>
settingsStore.isSettingTruthy('has_page_gift_card'),
)
function go(routeName: string) {
emit('navigate', routeName)
+56
View File
@@ -13,6 +13,49 @@ export function persianToEnglishDigits(value: string | number | null | undefined
.replace(/٫/g, '.')
}
/**
* اعداد فارسی/عربی + جداکننده‌های اعشار رایج ماشین‌حساب فارسی.
* - `commaAsThousand` true (مبلغ): `,` / `،` حذف می‌شوند
* - `commaAsThousand` false (وزن/اعشار): `,` / `،` هم → `.`
*/
export function normalizeDecimalInput(
val: string | number | null | undefined,
options?: { commaAsThousand?: boolean }
): string {
if (val == null || val === '') return ''
const commaAsThousand = options?.commaAsThousand ?? true
let normalized = persianToEnglishDigits(String(val))
if (commaAsThousand) {
normalized = normalized.replace(/[,،]/g, '')
} else {
normalized = normalized.replace(/[,،]/g, '.')
}
normalized = normalized
.replace(/\//g, '.')
.replace(/[^\d.]/g, '')
const firstDot = normalized.indexOf('.')
if (firstDot !== -1) {
normalized =
normalized.slice(0, firstDot + 1) +
normalized.slice(firstDot + 1).replace(/\./g, '')
}
return normalized
}
export function parseDecimalInput(
val: string | number | null | undefined,
options?: { commaAsThousand?: boolean }
): number {
const normalized = normalizeDecimalInput(val, options)
if (!normalized) return NaN
return Number(normalized)
}
export function shouldNormalizeInputDigits(options: {
isPassword?: boolean
type?: string
@@ -27,3 +70,16 @@ export function shouldNormalizeInputDigits(options: {
const mode = String(options.inputmode ?? '').trim().toLowerCase()
return mode === 'numeric' || mode === 'decimal' || mode === 'number'
}
/** فیلدهای وزن/مبلغ/عدد اعشاری که باید `/` را ممیز بگیرند */
export function shouldNormalizeDecimalSeparators(options: {
isPassword?: boolean
type?: string
inputmode?: string
}): boolean {
if (options.isPassword) return false
const type = String(options.type ?? '').trim().toLowerCase()
if (type === 'price' || type === 'number') return true
const mode = String(options.inputmode ?? '').trim().toLowerCase()
return mode === 'numeric' || mode === 'decimal' || mode === 'number'
}
+7 -13
View File
@@ -87,8 +87,11 @@ export function parseSupportMessengersJson(raw?: string | null): SupportMessenge
export function resolveSupportMessengersFromSettings(
settings: Array<{ key?: string; value?: unknown }>,
): SupportMessengerEntry[] {
const fromJson = parseSupportMessengersJson(getSupportSettingValue(settings, 'support_messengers'))
if (fromJson.length > 0) return fromJson
// اگر کلید جدید در تنظیمات هست (حتی خالی/null)، فقط همان لیست معتبر است — legacy بله را برنگردان
const hasMessengersKey = settings.some((row) => row?.key === 'support_messengers')
if (hasMessengersKey) {
return parseSupportMessengersJson(getSupportSettingValue(settings, 'support_messengers'))
}
const legacyMessenger = getSupportSettingValue(settings, 'support_messenger').toLowerCase()
const legacyId = getSupportSettingValue(settings, 'support_messenger_id')
@@ -107,9 +110,8 @@ export function resolveSupportMessengerEntries(
): ResolvedSupportMessengerEntry[] {
const resolved = settings ?? supportMessengerSettings.value
const text = (messageText ?? '').trim()
const source = resolved.messengers?.length
? resolved.messengers
: resolveLegacySingleMessenger(resolved)
// فقط لیست resolve‌شده؛ قبل از load خالی است تا legacy دوباره دکمه نسازد
const source = Array.isArray(resolved.messengers) ? resolved.messengers : []
return source
.map((entry) => {
@@ -125,14 +127,6 @@ export function resolveSupportMessengerEntries(
.filter((entry): entry is ResolvedSupportMessengerEntry => entry !== null)
}
function resolveLegacySingleMessenger(settings: SupportMessengerSettings): SupportMessengerEntry[] {
const messenger = (settings.messenger ?? '').trim().toLowerCase()
const id = (settings.messengerId ?? '').trim()
if (!messenger || !id) return []
if (!VALID_SUPPORT_MESSENGERS.has(messenger)) return []
return [{ messenger, id }]
}
/** @deprecated از depositGuideText استفاده کنید */
export const DEFAULT_SUPPORT_MESSENGER_TEXT = DEFAULT_DEPOSIT_GUIDE_TEXT
+21 -4
View File
@@ -71,29 +71,46 @@ _axios.interceptors.response.use(
}
const apiMessage = extractApiErrorMessage(response?.data)
const status = Number(response?.status ?? 0)
if (response?.status === 403) {
showApiError(apiMessage || 'شما دسترسی لازم برای انجام این عملیات را ندارید', config)
showApiError(
withHttpStatus(apiMessage || 'شما دسترسی لازم برای انجام این عملیات را ندارید', 403),
config,
)
return Promise.reject(error)
}
if (response?.status === 422) {
if (apiMessage) {
showApiError(apiMessage, config)
showApiError(withHttpStatus(apiMessage, 422), config)
} else {
showApiError(withHttpStatus('خطا در اعتبارسنجی', 422), config)
}
return Promise.reject(error)
}
if (response) {
showApiError(apiMessage || DEFAULT_LOAD_ERROR, config)
if (status >= 500) {
showApiError(withHttpStatus(SERVER_ERROR, status), config)
} else {
showApiError(withHttpStatus(apiMessage || DEFAULT_LOAD_ERROR, status), config)
}
return Promise.reject(error)
}
showApiError(DEFAULT_LOAD_ERROR, config)
// بدون status (نه 4xx نه 5xx): قطع شبکه، timeout، DNS، …
showApiError(NETWORK_ERROR, config)
return Promise.reject(error)
})
const DEFAULT_LOAD_ERROR = 'خطا در بارگذاری اطلاعات'
const SERVER_ERROR = 'خطا در برقراری ارتباط با سرور'
const NETWORK_ERROR = 'لطفا اینترنت خود را بررسی کنید و مجددا تلاش کنید'
function withHttpStatus(message: string, status: number): string {
return `${message} (${status})`
}
function extractApiErrorMessage(data: unknown): string {
if (!data || typeof data !== 'object') {
@@ -477,7 +477,7 @@
.dhome-default-actions {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
gap: 8px;
}
+79 -44
View File
@@ -67,87 +67,68 @@
<!-- <div class="bg-gradient-to-l mt-3 p-1 shadow-lg from-primary rounded-lg p-px to-transparent ">-->
<div class="mt-3 p-1 shadow-lg from-primary rounded-lg p-px to-transparent border-solid border border-primary">
<div class=" p-1 py-6 rounded-lg bg-white">
<div class="flex justify-between items-start gap-1">
<div class="home-quick-actions">
<div
class="text-center min-w-0 flex-1 home_appear_fade_in_up"
class="home-quick-action home_appear_fade_in_up"
style="text-align: -webkit-center"
@click="router.push({ name: 'gold' })"
>
<div
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
style="border: solid 1px #a72b77"
>
<Icon class="text-white" icon="vuesax-linear:money-receive" />
<div class="home-quick-action__icon">
<Icon class="text-white" icon="vuesax-linear:money-receive" size="18" />
</div>
<div class="text-primary text-[11px] leading-tight mt-1">خرید و فروش</div>
<div class="home-quick-action__label">خرید و فروش</div>
</div>
<div
class="text-center min-w-0 flex-1 home_appear_fade_in_down"
class="home-quick-action home_appear_fade_in_down"
style="text-align: -webkit-center"
@click="router.push({ name: 'wallet', query: { state: 'deposit' } })"
>
<div
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
style="border: solid 1px #a72b77"
>
<Icon class="text-white" icon="vuesax-linear:card-add" />
<div class="home-quick-action__icon">
<Icon class="text-white" icon="vuesax-linear:card-add" size="18" />
</div>
<div class="text-primary text-[11px] leading-tight mt-1">واریز نقدی</div>
<div class="home-quick-action__label">واریز نقدی</div>
</div>
<div
class="text-center min-w-0 flex-1 home_appear_fade_in_up"
class="home-quick-action home_appear_fade_in_up"
style="text-align: -webkit-center"
@click="openWholeBalanceVisible()"
>
<div
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
style="border: solid 1px #a72b77"
>
<Icon class="text-white" icon="vuesax-linear:receipt-item" />
<div class="home-quick-action__icon">
<Icon class="text-white" icon="vuesax-linear:receipt-item" size="18" />
</div>
<div class="text-primary text-[11px] leading-tight mt-1">موجودی</div>
<div class="home-quick-action__label">موجودی</div>
</div>
<div
class="text-center min-w-0 flex-1 home_appear_fade_in_down"
class="home-quick-action home_appear_fade_in_down"
style="text-align: -webkit-center"
@click="router.push({ name: 'fund' })"
>
<div
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
style=" border: solid 1px #a72b77"
>
<Icon class="text-white font-bold" icon="vuesax-linear:archive-1" />
<div class="home-quick-action__icon">
<Icon class="text-white font-bold" icon="vuesax-linear:archive-1" size="18" />
</div>
<div class="text-primary text-[11px] leading-tight mt-1">دارایی ها</div>
<div class="home-quick-action__label">دارایی ها</div>
</div>
<div
v-if="productFundsEnabled"
class="text-center min-w-0 flex-1 home_appear_fade_in_down"
class="home-quick-action home_appear_fade_in_down"
style="text-align: -webkit-center"
@click="router.push({ name: 'piggy' })"
>
<div
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
style=" border: solid 1px #a72b77"
>
<Icon class="text-white font-bold" icon="vuesax-linear:safe-home" />
<div class="home-quick-action__icon">
<Icon class="text-white font-bold" icon="vuesax-linear:safe-home" size="18" />
</div>
<div class="text-primary text-[11px] leading-tight mt-1">قلک</div>
<div class="home-quick-action__label">قلک</div>
</div>
<div
class="text-center min-w-0 flex-1 home_appear_fade_in_down"
class="home-quick-action home_appear_fade_in_down"
style="text-align: -webkit-center"
@click="router.push({ name: 'earnMoney' })"
>
<div
class="rounded-full w-[44px] h-[44px] mx-auto bg-primary flex items-center justify-center"
style=" border: solid 1px #a72b77"
>
<Icon class="text-white font-bold" icon="vuesax-linear:dollar-circle" />
<div class="home-quick-action__icon">
<Icon class="text-white font-bold" icon="vuesax-linear:dollar-circle" size="18" />
</div>
<div class="text-primary text-[11px] leading-tight mt-1">کسب درآمد</div>
<div class="home-quick-action__label">کسب درآمد</div>
</div>
</div>
</div>
<!-- <ADivider></ADivider>-->
@@ -957,6 +938,60 @@ function copyCode() {
}
}
.home-quick-actions {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 2px;
}
.home-quick-action {
flex: 1 1 0;
min-width: 0;
text-align: center;
}
.home-quick-action__icon {
width: 40px;
height: 40px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
background: var(--App-primary, #a72b77);
border: solid 1px #a72b77;
}
.home-quick-action__label {
margin-top: 4px;
padding: 0 2px;
color: var(--App-primary, #a72b77);
font-size: 10px;
font-weight: 500;
line-height: 1.25;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
word-break: break-word;
}
@media (min-width: 640px) {
.home-quick-actions {
gap: 4px;
}
.home-quick-action__icon {
width: 44px;
height: 44px;
}
.home-quick-action__label {
font-size: 11px;
}
}
.odometer {
font-size: 60px;
text-align: center;
+118 -94
View File
@@ -1,95 +1,97 @@
<template>
<div class="piggy-page">
<header class="piggy-bar">
<button type="button" class="piggy-bar__btn" aria-label="بازگشت" @click="goBack">
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
</button>
<h1 class="piggy-bar__title">قلک‌ها</h1>
<button type="button" class="piggy-bar__btn piggy-bar__btn--primary" aria-label="قلک جدید" @click="goAdd">
<BasicIcon icon="vuesax-linear:add" size="20" />
</button>
</header>
<div class="piggy-page__inner">
<header class="piggy-bar">
<button type="button" class="piggy-bar__btn" aria-label="بازگشت" @click="goBack">
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
</button>
<h1 class="piggy-bar__title">قلک‌ها</h1>
<button type="button" class="piggy-bar__btn piggy-bar__btn--primary" aria-label="قلک جدید" @click="goAdd">
<BasicIcon icon="vuesax-linear:add" size="20" />
</button>
</header>
<a-spin :spinning="busy">
<section class="piggy-summary">
<div class="piggy-summary__card">
<div class="piggy-summary__row">
<div>
<p class="piggy-summary__label">جمع موجودی داخل قلک‌ها</p>
<p class="piggy-summary__value" dir="ltr">
{{ formatPiggyAmount(totalGold) }}
<span class="piggy-summary__unit">گرم</span>
</p>
</div>
<div class="piggy-summary__badge">
<BasicIcon icon="vuesax-linear:safe-home" size="22" />
</div>
</div>
<div class="piggy-summary__meta">
<span>{{ rows.length }} قلک</span>
<span class="piggy-summary__dot" />
<span>{{ groups.length }} محصول</span>
</div>
</div>
<BasicButton class="!w-full mt-3" type="primary" size="middle" block @click="goAdd">
<div class="flex items-center justify-center gap-2">
<BasicIcon icon="vuesax-linear:add-circle" size="18" />
<span>{{ rows.length ? 'افزودن قلک' : 'ساخت اولین قلک' }}</span>
</div>
</BasicButton>
</section>
<section class="piggy-body">
<div v-if="!rows.length" class="piggy-empty">
<div class="piggy-empty__icon">
<BasicIcon icon="vuesax-linear:safe-home" size="36" />
</div>
<p class="piggy-empty__title">قلکی ندارید</p>
<p class="piggy-empty__desc">
برای جدا کردن بخشی از موجودی هر محصول (مثلاً آبشده یا سکه) یک قلک بسازید.
</p>
</div>
<div v-else class="piggy-groups">
<div v-for="group in groups" :key="group.productId" class="piggy-group">
<div class="piggy-group__head">
<a-spin :spinning="busy">
<section class="piggy-summary">
<div class="piggy-summary__card">
<div class="piggy-summary__row">
<div class="min-w-0">
<p class="piggy-group__name">{{ group.productTitle }}</p>
<p class="piggy-group__count">{{ group.funds.length }} قلک</p>
<p class="piggy-summary__label">جمع موجودی داخل قلک‌ها</p>
<p class="piggy-summary__value" dir="ltr">
{{ formatPiggyAmount(totalGold) }}
<span class="piggy-summary__unit">گرم</span>
</p>
</div>
<div class="piggy-summary__badge">
<BasicIcon icon="vuesax-linear:safe-home" size="22" />
</div>
<p class="piggy-group__total" dir="ltr">
{{ formatPiggyAmount(group.totalGold) }}
<span>گرم</span>
</p>
</div>
<div class="piggy-group__list">
<button
v-for="item in group.funds"
:key="item.id"
type="button"
class="piggy-row"
@click="openInfo(item.id!)"
>
<div class="piggy-row__icon">
<BasicIcon :icon="piggyIconName(item.title)" size="20" />
</div>
<div class="piggy-row__main">
<div class="piggy-row__title">{{ item.title || 'بدون عنوان' }}</div>
<div class="piggy-row__sub">کد {{ item.id }}</div>
</div>
<div class="piggy-row__amount" dir="ltr">
<strong>{{ formatPiggyAmount(fundWalletGold(item)) }}</strong>
<span>گرم</span>
</div>
<BasicIcon class="piggy-row__chevron" icon="vuesax-linear:arrow-left-1" size="16" />
</button>
<div class="piggy-summary__meta">
<span>{{ rows.length }} قلک</span>
<span class="piggy-summary__dot" />
<span>{{ groups.length }} محصول</span>
</div>
</div>
</div>
</section>
</a-spin>
<BasicButton class="!w-full mt-3" type="primary" size="middle" block @click="goAdd">
<div class="flex items-center justify-center gap-2">
<BasicIcon icon="vuesax-linear:add-circle" size="18" />
<span>{{ rows.length ? 'افزودن قلک' : 'ساخت اولین قلک' }}</span>
</div>
</BasicButton>
</section>
<section class="piggy-body">
<div v-if="!rows.length" class="piggy-empty">
<div class="piggy-empty__icon">
<BasicIcon icon="vuesax-linear:safe-home" size="36" />
</div>
<p class="piggy-empty__title">قلکی ندارید</p>
<p class="piggy-empty__desc">
برای جدا کردن بخشی از موجودی هر محصول (مثلاً آبشده یا سکه) یک قلک بسازید.
</p>
</div>
<div v-else class="piggy-groups">
<div v-for="group in groups" :key="group.productId" class="piggy-group">
<div class="piggy-group__head">
<div class="min-w-0">
<p class="piggy-group__name">{{ group.productTitle }}</p>
<p class="piggy-group__count">{{ group.funds.length }} قلک</p>
</div>
<p class="piggy-group__total" dir="ltr">
{{ formatPiggyAmount(group.totalGold) }}
<span>گرم</span>
</p>
</div>
<div class="piggy-group__list">
<button
v-for="item in group.funds"
:key="item.id"
type="button"
class="piggy-row"
@click="openInfo(item.id!)"
>
<div class="piggy-row__icon">
<BasicIcon :icon="piggyIconName(item.title)" size="20" />
</div>
<div class="piggy-row__main">
<div class="piggy-row__title">{{ item.title || 'بدون عنوان' }}</div>
<div class="piggy-row__sub">کد {{ item.id }}</div>
</div>
<div class="piggy-row__amount" dir="ltr">
<strong>{{ formatPiggyAmount(fundWalletGold(item)) }}</strong>
<span>گرم</span>
</div>
<BasicIcon class="piggy-row__chevron" icon="vuesax-linear:arrow-left-1" size="16" />
</button>
</div>
</div>
</div>
</section>
</a-spin>
</div>
</div>
</template>
@@ -143,23 +145,33 @@ onMounted(async () => {
<style scoped>
.piggy-page {
@apply relative min-h-full bg-gray-50 pb-24;
box-sizing: border-box;
min-height: 100%;
width: 100%;
background: #f9fafb;
padding: 0 12px calc(var(--app-bottom-chrome-height, 108px) + 1rem);
}
.piggy-page__inner {
box-sizing: border-box;
width: 100%;
max-width: 26rem;
margin-inline: auto;
}
.piggy-bar {
@apply sticky top-0 z-20 flex items-center justify-between bg-white px-3 py-3;
box-shadow: 0 1px 0 rgba(15, 23, 42, 0.06);
}
.piggy-bar__title {
@apply m-0 text-base font-bold text-gray-900;
@apply m-0 truncate px-2 text-base font-bold text-gray-900;
}
.piggy-bar__btn {
@apply flex h-9 w-9 items-center justify-center rounded-full border-none bg-gray-100 text-gray-800;
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-none bg-gray-100 text-gray-800;
}
.piggy-bar__btn--primary {
@apply bg-primary text-white;
}
.piggy-summary {
@apply px-4 pt-4;
@apply px-3 pt-4 sm:px-4;
}
.piggy-summary__card {
@apply rounded-2xl bg-primary p-4 text-white;
@@ -171,7 +183,7 @@ onMounted(async () => {
@apply m-0 text-sm text-white/80;
}
.piggy-summary__value {
@apply m-0 mt-1 flex items-baseline gap-2 text-3xl font-bold;
@apply m-0 mt-1 flex flex-wrap items-baseline gap-2 text-2xl font-bold sm:text-3xl;
}
.piggy-summary__unit {
@apply text-sm font-medium text-white/75;
@@ -180,13 +192,13 @@ onMounted(async () => {
@apply flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-white/15;
}
.piggy-summary__meta {
@apply mt-4 flex items-center gap-2 text-xs text-white/75;
@apply mt-4 flex flex-wrap items-center gap-2 text-xs text-white/75;
}
.piggy-summary__dot {
@apply h-1 w-1 rounded-full bg-white/50;
}
.piggy-body {
@apply px-4 pt-4;
@apply px-3 pt-4 sm:px-4;
}
.piggy-groups {
@apply flex flex-col gap-4;
@@ -196,7 +208,7 @@ onMounted(async () => {
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
}
.piggy-group__head {
@apply flex items-center justify-between gap-3 border-0 border-b border-solid border-gray-100 px-4 py-3;
@apply flex items-center justify-between gap-3 border-0 border-b border-solid border-gray-100 px-3 py-3 sm:px-4;
}
.piggy-group__name {
@apply m-0 truncate text-sm font-bold text-gray-900;
@@ -214,10 +226,10 @@ onMounted(async () => {
@apply flex flex-col;
}
.piggy-row {
@apply flex w-full items-center gap-3 border-0 border-b border-solid border-gray-50 bg-transparent px-3 py-3 text-right last:border-b-0;
@apply flex w-full min-w-0 items-center gap-2 border-0 border-b border-solid border-gray-50 bg-transparent px-2.5 py-3 text-right last:border-b-0 sm:gap-3 sm:px-3;
}
.piggy-row__icon {
@apply flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gray-50 text-primary;
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-gray-50 text-primary sm:h-10 sm:w-10;
}
.piggy-row__main {
@apply min-w-0 flex-1;
@@ -253,4 +265,16 @@ onMounted(async () => {
.piggy-empty__desc {
@apply m-0 mt-2 text-sm leading-6 text-gray-500;
}
@media (min-width: 768px) {
.piggy-page {
padding: 16px 16px 2rem;
}
.piggy-page__inner {
max-width: 28rem;
}
.piggy-bar {
border-radius: 12px;
}
}
</style>
+104 -69
View File
@@ -1,75 +1,77 @@
<template>
<div class="piggy-info">
<header class="piggy-info__hero">
<div class="piggy-info__top">
<button type="button" class="piggy-info__btn" aria-label="بازگشت" @click="goBack">
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
</button>
<h1 class="piggy-info__title">{{ fund?.title || 'قلک' }}</h1>
<button type="button" class="piggy-info__btn" aria-label="حذف" @click="onDelete">
<BasicIcon icon="vuesax-linear:trash" size="18" />
</button>
</div>
<a-spin :spinning="busy">
<div class="piggy-info__balance-block">
<p class="piggy-info__product">{{ fund?.product?.title || '—' }}</p>
<p class="piggy-info__label">موجودی قلک</p>
<p class="piggy-info__value" dir="ltr">
{{ formatPiggyAmount(potGold) }}
<span>گرم</span>
</p>
<p class="piggy-info__free">
آزاد قابل واریز:
<strong dir="ltr">{{ formatPiggyAmount(freeGold) }}</strong>
گرم
</p>
<div class="piggy-info__inner">
<header class="piggy-info__hero">
<div class="piggy-info__top">
<button type="button" class="piggy-info__btn" aria-label="بازگشت" @click="goBack">
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
</button>
<h1 class="piggy-info__title">{{ fund?.title || 'قلک' }}</h1>
<button type="button" class="piggy-info__btn" aria-label="حذف" @click="onDelete">
<BasicIcon icon="vuesax-linear:trash" size="18" />
</button>
</div>
</a-spin>
</header>
<div class="piggy-info__sheet">
<div class="piggy-info__actions">
<BasicButton class="!flex-1" size="middle" @click="openTx(false)">
<div class="flex items-center justify-center gap-2">
<BasicIcon icon="vuesax-linear:export" size="18" />
<span>برداشت به آزاد</span>
<a-spin :spinning="busy">
<div class="piggy-info__balance-block">
<p class="piggy-info__product">{{ fund?.product?.title || '—' }}</p>
<p class="piggy-info__label">موجودی قلک</p>
<p class="piggy-info__value" dir="ltr">
{{ formatPiggyAmount(potGold) }}
<span>گرم</span>
</p>
<p class="piggy-info__free">
آزاد قابل واریز:
<strong dir="ltr">{{ formatPiggyAmount(freeGold) }}</strong>
گرم
</p>
</div>
</BasicButton>
<BasicButton class="!flex-1" type="primary" size="middle" @click="openTx(true)">
<div class="flex items-center justify-center gap-2">
<BasicIcon icon="vuesax-linear:import" size="18" />
<span>واریز از آزاد</span>
</div>
</BasicButton>
</div>
</a-spin>
</header>
<section class="piggy-info__tx">
<h2 class="piggy-info__tx-title">تراکنش‌های این قلک</h2>
<div v-if="!transactions.length" class="piggy-info__tx-empty">
<BasicIcon icon="vuesax-linear:receipt-2" size="28" />
<p class="m-0 mt-2 text-sm text-gray-500">هنوز جابه‌جایی ثبت نشده</p>
<div class="piggy-info__sheet">
<div class="piggy-info__actions">
<BasicButton class="piggy-info__action" size="middle" @click="openTx(false)">
<div class="piggy-info__action-inner">
<BasicIcon icon="vuesax-linear:export" size="18" />
<span>برداشت به آزاد</span>
</div>
</BasicButton>
<BasicButton class="piggy-info__action" type="primary" size="middle" @click="openTx(true)">
<div class="piggy-info__action-inner">
<BasicIcon icon="vuesax-linear:import" size="18" />
<span>واریز از آزاد</span>
</div>
</BasicButton>
</div>
<div v-else class="piggy-info__tx-list">
<div v-for="tx in transactions" :key="tx.id" class="piggy-info__tx-row">
<div class="piggy-info__tx-dot" />
<div class="min-w-0 flex-1 text-right">
<div class="truncate text-sm font-bold text-gray-900">
{{ tx.type_str || tx.type || 'تراکنش' }}
<section class="piggy-info__tx">
<h2 class="piggy-info__tx-title">تراکنش‌های این قلک</h2>
<div v-if="!transactions.length" class="piggy-info__tx-empty">
<BasicIcon icon="vuesax-linear:receipt-2" size="28" />
<p class="m-0 mt-2 text-sm text-gray-500">هنوز جابه‌جایی ثبت نشده</p>
</div>
<div v-else class="piggy-info__tx-list">
<div v-for="tx in transactions" :key="tx.id" class="piggy-info__tx-row">
<div class="piggy-info__tx-dot" />
<div class="min-w-0 flex-1 text-right">
<div class="truncate text-sm font-bold text-gray-900">
{{ tx.type_str || tx.type || 'تراکنش' }}
</div>
<div class="mt-0.5 text-[11px] text-gray-400">{{ tx.created_at || '' }}</div>
</div>
<div class="shrink-0 font-bold text-primary" dir="ltr">
{{ formatPiggyAmount(Number(tx.gold ?? 0)) }}
</div>
<div class="mt-0.5 text-[11px] text-gray-400">{{ tx.created_at || '' }}</div>
</div>
<div class="shrink-0 font-bold text-primary" dir="ltr">
{{ formatPiggyAmount(Number(tx.gold ?? 0)) }}
</div>
</div>
</div>
</section>
</section>
</div>
</div>
<BasicModal v-model:visible="txVisible" :title="isDeposit ? 'واریز به قلک' : 'برداشت از قلک'">
<div class="flex flex-col gap-3 p-1">
<p class="m-0 text-xs text-gray-500">
<p class="m-0 text-xs leading-5 text-gray-500">
{{
isDeposit
? `از موجودی آزاد «${fund?.product?.title || 'محصول'}» — حداکثر ${formatPiggyAmount(freeGold)} گرم`
@@ -218,46 +220,62 @@ onMounted(async () => {
<style scoped>
.piggy-info {
@apply min-h-full bg-gray-50 pb-28;
box-sizing: border-box;
min-height: 100%;
width: 100%;
background: #f9fafb;
padding: 0 12px calc(var(--app-bottom-chrome-height, 108px) + 1rem);
}
.piggy-info__inner {
box-sizing: border-box;
width: 100%;
max-width: 26rem;
margin-inline: auto;
}
.piggy-info__hero {
@apply bg-primary px-3 pb-8 pt-3 text-white;
}
.piggy-info__top {
@apply flex items-center justify-between;
@apply flex items-center justify-between gap-2;
}
.piggy-info__btn {
@apply flex h-9 w-9 items-center justify-center rounded-full border-none bg-white/15 text-white;
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-none bg-white/15 text-white;
}
.piggy-info__title {
@apply m-0 max-w-[55%] truncate text-base font-bold text-white;
@apply m-0 min-w-0 flex-1 truncate text-center text-base font-bold text-white;
}
.piggy-info__balance-block {
@apply mt-6 text-center;
@apply mt-6 px-2 text-center;
}
.piggy-info__product {
@apply m-0 inline-flex rounded-full bg-white/15 px-3 py-1 text-xs text-white/90;
@apply m-0 inline-flex max-w-full truncate rounded-full bg-white/15 px-3 py-1 text-xs text-white/90;
}
.piggy-info__label {
@apply m-0 mt-4 text-sm text-white/75;
}
.piggy-info__value {
@apply m-0 mt-1 flex items-baseline justify-center gap-2 text-4xl font-bold;
@apply m-0 mt-1 flex flex-wrap items-baseline justify-center gap-2 text-3xl font-bold sm:text-4xl;
}
.piggy-info__value span {
@apply text-sm font-medium text-white/75;
}
.piggy-info__free {
@apply m-0 mt-3 text-xs text-white/70;
@apply m-0 mt-3 text-xs leading-5 text-white/70;
}
.piggy-info__free strong {
@apply text-white;
}
.piggy-info__sheet {
@apply relative z-10 -mt-4 rounded-t-3xl bg-gray-50 px-4 pt-5;
@apply relative z-10 -mt-4 rounded-t-3xl bg-gray-50 px-3 pt-5 sm:px-4;
}
.piggy-info__actions {
@apply flex gap-2;
@apply flex flex-col gap-2 sm:flex-row;
}
.piggy-info__action {
@apply !w-full sm:!flex-1;
}
.piggy-info__action-inner {
@apply flex items-center justify-center gap-2 text-sm;
}
.piggy-info__tx {
@apply mt-5;
@@ -273,10 +291,27 @@ onMounted(async () => {
@apply flex flex-col gap-2;
}
.piggy-info__tx-row {
@apply flex items-center gap-3 rounded-2xl bg-white p-3;
@apply flex min-w-0 items-center gap-3 rounded-2xl bg-white p-3;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
}
.piggy-info__tx-dot {
@apply h-2.5 w-2.5 shrink-0 rounded-full bg-primary;
}
@media (min-width: 768px) {
.piggy-info {
padding: 16px 16px 2rem;
}
.piggy-info__inner {
max-width: 26rem;
overflow: hidden;
border-radius: 16px;
}
.piggy-info__hero {
border-radius: 16px 16px 0 0;
}
.piggy-info__actions {
flex-direction: row;
}
}
</style>
+218 -84
View File
@@ -1,78 +1,80 @@
<template>
<div class="piggy-create">
<header class="piggy-bar">
<button type="button" class="piggy-bar__btn" aria-label="بازگشت" @click="goBack">
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
</button>
<h1 class="piggy-bar__title">{{ editId ? 'ویرایش قلک' : 'ساخت قلک' }}</h1>
<span class="w-9" />
</header>
<div class="piggy-create__inner">
<header class="piggy-bar">
<button type="button" class="piggy-bar__btn" aria-label="بازگشت" @click="goBack">
<BasicIcon icon="vuesax-linear:arrow-right-1" size="22" />
</button>
<h1 class="piggy-bar__title">{{ editId ? 'ویرایش قلک' : 'ساخت قلک' }}</h1>
<span class="w-9 shrink-0" />
</header>
<a-spin :spinning="busy">
<div class="piggy-create__body">
<section class="piggy-create__step">
<div class="piggy-create__step-head">
<span class="piggy-create__step-num">۱</span>
<div>
<h2 class="piggy-create__step-title">محصول</h2>
<p class="piggy-create__step-desc">این قلک فقط برای همین محصول پر می‌شود</p>
<a-spin :spinning="busy">
<div class="piggy-create__body">
<section class="piggy-create__step">
<div class="piggy-create__step-head">
<span class="piggy-create__step-num">۱</span>
<div class="min-w-0">
<h2 class="piggy-create__step-title">محصول</h2>
<p class="piggy-create__step-desc">این قلک فقط برای همین محصول پر می‌شود</p>
</div>
</div>
</div>
<div v-if="editId" class="piggy-create__locked">
<BasicIcon icon="vuesax-linear:box-1" size="18" />
<span>{{ lockedProductTitle || 'محصول ثابت' }}</span>
</div>
<div v-else class="piggy-create__products">
<button
v-for="p in products"
:key="p.id"
type="button"
class="piggy-create__product"
:class="{ 'piggy-create__product--on': productId === p.id }"
@click="productId = p.id"
>
<div v-if="editId" class="piggy-create__locked">
<BasicIcon icon="vuesax-linear:box-1" size="18" />
<span class="truncate">{{ p.title || `محصول ${p.id}` }}</span>
</button>
</div>
</section>
<section class="piggy-create__step">
<div class="piggy-create__step-head">
<span class="piggy-create__step-num">۲</span>
<div>
<h2 class="piggy-create__step-title">نام قلک</h2>
<p class="piggy-create__step-desc">مثلاً پس‌انداز، هدیه یا ازدواج</p>
<span class="truncate">{{ lockedProductTitle || 'محصول ثابت' }}</span>
</div>
</div>
<div class="piggy-create__title-row">
<div class="piggy-create__title-icon">
<BasicIcon :icon="piggyIconName(title)" size="22" />
<div v-else class="piggy-create__products">
<button
v-for="p in products"
:key="p.id"
type="button"
class="piggy-create__product"
:class="{ 'piggy-create__product--on': productId === p.id }"
@click="productId = p.id"
>
<BasicIcon icon="vuesax-linear:box-1" size="18" />
<span class="min-w-0 truncate">{{ p.title || `محصول ${p.id}` }}</span>
</button>
</div>
<BasicInput v-model:value="title" class="!flex-1" placeholder="عنوان قلک" maxlength="25" />
</div>
</section>
<div class="piggy-create__presets">
<button
v-for="preset in presets"
:key="preset"
type="button"
class="piggy-create__chip"
:class="{ 'piggy-create__chip--on': title === preset }"
@click="title = preset"
>
{{ preset }}
</button>
</div>
</section>
<section class="piggy-create__step">
<div class="piggy-create__step-head">
<span class="piggy-create__step-num">۲</span>
<div class="min-w-0">
<h2 class="piggy-create__step-title">نام قلک</h2>
<p class="piggy-create__step-desc">مثلاً پس‌انداز، هدیه یا ازدواج</p>
</div>
</div>
<BasicButton class="!w-full" type="primary" size="middle" block :disabled="busy" @click="submit">
{{ editId ? 'ذخیره' : 'ساخت قلک' }}
</BasicButton>
</div>
</a-spin>
<div class="piggy-create__title-row">
<div class="piggy-create__title-icon">
<BasicIcon :icon="piggyIconName(title)" size="22" />
</div>
<BasicInput v-model:value="title" class="!min-w-0 !flex-1" placeholder="عنوان قلک" maxlength="25" />
</div>
<div class="piggy-create__presets">
<button
v-for="preset in presets"
:key="preset"
type="button"
class="piggy-create__chip"
:class="{ 'piggy-create__chip--on': title === preset }"
@click="title = preset"
>
{{ preset }}
</button>
</div>
</section>
<BasicButton class="!w-full" type="primary" size="middle" block :disabled="busy" @click="submit">
{{ editId ? 'ذخیره' : 'ساخت قلک' }}
</BasicButton>
</div>
</a-spin>
</div>
</div>
</template>
@@ -161,62 +163,194 @@ onMounted(async () => {
<style scoped>
.piggy-create {
@apply min-h-full bg-gray-50 pb-24;
box-sizing: border-box;
min-height: 100%;
width: 100%;
background: #f9fafb;
padding: 0 12px calc(var(--app-bottom-chrome-height, 108px) + 1rem);
}
.piggy-create__inner {
box-sizing: border-box;
width: 100%;
max-width: 26rem;
margin-inline: auto;
}
.piggy-bar {
@apply sticky top-0 z-20 flex items-center justify-between bg-white px-3 py-3;
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 12px;
box-shadow: 0 1px 0 rgba(15, 23, 42, 0.06);
}
.piggy-bar__title {
@apply m-0 text-base font-bold text-gray-900;
margin: 0;
padding: 0 8px;
font-size: 1rem;
font-weight: 700;
color: #111827;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.piggy-bar__btn {
@apply flex h-9 w-9 items-center justify-center rounded-full border-none bg-gray-100 text-gray-800;
display: flex;
width: 36px;
height: 36px;
flex-shrink: 0;
align-items: center;
justify-content: center;
border: none;
border-radius: 9999px;
background: #f3f4f6;
color: #1f2937;
}
.piggy-create__body {
@apply flex flex-col gap-4 px-4 pt-4;
display: flex;
flex-direction: column;
gap: 12px;
padding-top: 16px;
}
.piggy-create__step {
@apply rounded-2xl bg-white p-4;
border-radius: 16px;
background: #fff;
padding: 14px;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
}
.piggy-create__step-head {
@apply mb-4 flex items-start gap-3;
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 12px;
}
.piggy-create__step-num {
@apply flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-sm font-bold text-white;
display: flex;
width: 32px;
height: 32px;
flex-shrink: 0;
align-items: center;
justify-content: center;
border-radius: 9999px;
background: var(--App-primary, #0a1b37);
color: #fff;
font-size: 0.875rem;
font-weight: 700;
}
.piggy-create__step-title {
@apply m-0 text-sm font-bold text-gray-900;
margin: 0;
font-size: 0.875rem;
font-weight: 700;
color: #111827;
}
.piggy-create__step-desc {
@apply m-0 mt-1 text-xs text-gray-500;
margin: 4px 0 0;
font-size: 0.75rem;
color: #6b7280;
}
.piggy-create__products {
@apply grid grid-cols-2 gap-2;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.piggy-create__product {
@apply flex items-center gap-2 rounded-xl border-none bg-gray-50 px-3 py-3 text-right text-sm text-gray-700;
display: inline-flex;
max-width: 100%;
min-width: 0;
align-items: center;
gap: 8px;
border: none;
border-radius: 12px;
background: #f9fafb;
padding: 10px 14px;
text-align: right;
font-size: 0.875rem;
color: #374151;
}
.piggy-create__product--on {
@apply bg-primary text-white;
background: var(--App-primary, #0a1b37);
color: #fff;
}
.piggy-create__locked {
@apply flex items-center gap-2 rounded-xl bg-gray-50 px-3 py-3 text-sm font-bold text-gray-800;
display: flex;
min-width: 0;
align-items: center;
gap: 8px;
border-radius: 12px;
background: #f9fafb;
padding: 12px;
font-size: 0.875rem;
font-weight: 700;
color: #1f2937;
}
.piggy-create__title-row {
@apply flex items-center gap-2;
display: flex;
min-width: 0;
align-items: center;
gap: 8px;
}
.piggy-create__title-icon {
@apply flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gray-50 text-primary;
display: flex;
width: 44px;
height: 44px;
flex-shrink: 0;
align-items: center;
justify-content: center;
border-radius: 12px;
background: #f9fafb;
color: var(--App-primary, #0a1b37);
}
.piggy-create__presets {
@apply mt-3 flex flex-wrap gap-2;
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.piggy-create__chip {
@apply rounded-full border-none bg-gray-100 px-3 py-2 text-xs text-gray-600;
border: none;
border-radius: 9999px;
background: #f3f4f6;
padding: 8px 12px;
font-size: 0.75rem;
color: #4b5563;
}
.piggy-create__chip--on {
@apply bg-primary text-white;
background: var(--App-primary, #0a1b37);
color: #fff;
}
@media (min-width: 768px) {
.piggy-create {
padding: 16px 16px 2rem;
}
.piggy-create__inner {
max-width: 26rem;
}
.piggy-bar {
border-radius: 12px;
}
}
</style>
@@ -125,6 +125,13 @@ export function useUserPage() {
onClick: () => router.push({ name: 'about' }),
visible: true,
},
{
id: 'giftCard',
label: 'ثبت کارت هدیه',
icon: 'vuesax-linear:gift',
onClick: () => router.push({ name: 'giftCard' }),
visible: settingsStore.isSettingTruthy('has_page_gift_card'),
},
{
id: 'support',
label: 'پشتیبانی',
@@ -94,7 +94,7 @@
"
:style="{ opacity: getSwipeOpacity(message.id) }"
>
↩
<BasicIcon icon="vuesax-linear:undo" size="16" />
</div>
<div
@@ -281,10 +281,13 @@
: 'support-reply-button--support'
"
title="پاسخ"
@pointerdown.stop
@pointerup.stop
@pointercancel.stop
@pointermove.stop
@click.stop="emit('reply', message)"
>
<!-- <BasicIcon icon="vuesax-linear:message-text" size="17" />-->
<div class="w-1rem h-1rem flex justify-center">↩</div>
<BasicIcon icon="vuesax-linear:undo" size="16" />
</BasicButton>
</div>
</section>
@@ -1129,7 +1132,8 @@ defineExpose({
}
.support-day-group {
display: contents;
display: block;
position: relative;
}
.support-day-sticky {
@@ -1148,12 +1152,12 @@ defineExpose({
justify-content: center;
padding: 7px 14px;
border-radius: 999px;
background: color-mix(in srgb, var(--bg, #fff) 72%, transparent);
background: var(--bg, #fff);
border: 1px solid rgb(var(--colorPrimaryRGB) / 0.18);
color: var(--colorText, #111827);
backdrop-filter: blur(8px);
font-size: 12px;
font-weight: 700;
white-space: nowrap;
box-shadow: 0 6px 20px rgb(var(--colorPrimaryRGB) / 0.08);
}
@@ -1221,6 +1225,10 @@ defineExpose({
transform 0.15s ease;
}
.support-reply-button :deep(svg) {
display: block;
}
.support-row:hover .support-reply-button,
.support-row:focus-within .support-reply-button {
opacity: 1;
@@ -1316,27 +1324,36 @@ defineExpose({
position: relative;
display: flex;
gap: 8px;
margin-bottom: 9px;
padding: 8px 10px;
border-radius: 14px;
margin-bottom: 10px;
padding: 9px 11px;
border-radius: 12px;
overflow: hidden;
min-width: 10rem;
border: 1px solid transparent;
}
.support-bubble__reply--user {
background: rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.22);
border-color: rgba(255, 255, 255, 0.28);
}
.support-bubble__reply--support {
background: rgb(var(--colorPrimaryRGB) / 0.08);
background: #f4f4f5;
border-color: #e4e4e7;
}
.support-bubble__reply-line {
width: 3px;
border-radius: 999px;
flex: 0 0 3px;
background: currentColor;
opacity: 0.75;
align-self: stretch;
min-height: 28px;
background: var(--colorPrimary, #2563eb);
opacity: 1;
}
.support-bubble__reply--user .support-bubble__reply-line {
background: rgba(255, 255, 255, 0.92);
}
.support-bubble__reply-content {
@@ -1362,7 +1379,7 @@ defineExpose({
unicode-bidi: plaintext;
font-size: 12px;
line-height: 1.7;
opacity: 0.86;
opacity: 1;
}
.support-bubble--user .support-bubble__reply-text,
@@ -1375,7 +1392,7 @@ defineExpose({
}
.support-bubble--support .support-bubble__reply-text {
color: color-mix(in srgb, var(--colorText, #111827) 72%, transparent);
color: #52525b;
}
.support-bubble__attachments {