Port modular price, product, and PWA fixes into boursecu
Sync live quote handling, SSE product images, store hours, file URL normalization, PWA icon cache, TV monitor, about software, SuperAdmin menu gates, price alerts, and related UX from modular while preserving boursecu branding and config constraints. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
variant?: 'mobile' | 'sidebar' | 'float'
|
||||
variant?: 'mobile' | 'sidebar' | 'float' | 'tv'
|
||||
}>(),
|
||||
{ variant: 'mobile' },
|
||||
)
|
||||
|
||||
@@ -16,7 +16,14 @@
|
||||
|
||||
</style>-->
|
||||
<template>
|
||||
<VanActionSheet v-model:show="show" teleport="body" :z-index="zIndex">
|
||||
<VanActionSheet
|
||||
v-model:show="show"
|
||||
teleport="body"
|
||||
overlay
|
||||
lock-scroll
|
||||
close-on-click-overlay
|
||||
:z-index="zIndex"
|
||||
>
|
||||
<div class="justify-center cursor-pointer flex mt-1" @click="show = false">
|
||||
<span class="rounded-sm w-12 border-t-2 border-t-solid border-t-gray-500 mt-2"></span>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,11 @@
|
||||
import {usePrefix} from "@/utils/usePrefix";
|
||||
import {computed} from "vue";
|
||||
import {usePrice} from "@/utils/usePrice";
|
||||
import {persianToEnglishDigits, shouldNormalizeInputDigits} from '@/utils/persianDigits';
|
||||
import {
|
||||
normalizeDecimalInput,
|
||||
persianToEnglishDigits,
|
||||
shouldNormalizeDecimalSeparators,
|
||||
} from '@/utils/persianDigits';
|
||||
|
||||
//Props
|
||||
interface Prop {
|
||||
@@ -81,19 +85,23 @@ function normalizeValue(value: unknown) {
|
||||
return value
|
||||
}
|
||||
|
||||
let normalized = String(value)
|
||||
if (shouldNormalizeInputDigits(props)) {
|
||||
normalized = persianToEnglishDigits(normalized)
|
||||
if (shouldNormalizeDecimalSeparators(props)) {
|
||||
const isPrice = String(props.type ?? '').trim().toLowerCase() === 'price'
|
||||
return normalizeDecimalInput(value as string | number, {
|
||||
commaAsThousand: isPrice,
|
||||
})
|
||||
}
|
||||
if (props.type === 'price') {
|
||||
normalized = normalized.replaceAll(',', '')
|
||||
}
|
||||
return normalized
|
||||
|
||||
return persianToEnglishDigits(String(value))
|
||||
}
|
||||
|
||||
const handlerChange = (e: Event | string) => {
|
||||
const raw = typeof e === 'string' ? e : (e.target as HTMLInputElement | null)?.value
|
||||
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() : ''
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<a
|
||||
v-if="href && src"
|
||||
referrerpolicy="origin"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:href="href"
|
||||
class="enamad-badge"
|
||||
:class="`enamad-badge--${size}`"
|
||||
>
|
||||
<img
|
||||
referrerpolicy="origin"
|
||||
:src="src"
|
||||
alt="enamad"
|
||||
class="enamad-badge__img"
|
||||
:code="code || undefined"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: 'sm' | 'lg'
|
||||
}>(),
|
||||
{ size: 'sm' },
|
||||
)
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const href = computed(() =>
|
||||
String(settingsStore.getSettingByKey('enamad_url') ?? '').trim(),
|
||||
)
|
||||
const src = computed(() =>
|
||||
String(settingsStore.getSettingByKey('enamad_logo') ?? '').trim(),
|
||||
)
|
||||
const code = computed(() => {
|
||||
try {
|
||||
const u = new URL(href.value || src.value)
|
||||
return u.searchParams.get('Code') || u.searchParams.get('code') || ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.enamad-badge {
|
||||
display: inline-flex;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.enamad-badge__img {
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.enamad-badge--sm .enamad-badge__img {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.enamad-badge--lg .enamad-badge__img {
|
||||
height: 120px;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
v-model:visible="visible"
|
||||
width="400px"
|
||||
destroy-on-close
|
||||
custom-footer
|
||||
wrap-class-name="gold-day-calc-modal"
|
||||
>
|
||||
<template #title>
|
||||
<div class="gold-day-calc-modal__title">
|
||||
<BasicIcon icon="vuesax-linear:chart-2" size="20" class="gold-day-calc-modal__title-icon" />
|
||||
<span>محاسبهگر طلای شما</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="gold-day-calc" dir="rtl">
|
||||
<BasicInput
|
||||
v-model:value="weight"
|
||||
title="وزن طلا (گرم)"
|
||||
type="tel"
|
||||
place-holder="مثلاً ۱۰.۵"
|
||||
inputmode="decimal"
|
||||
/>
|
||||
|
||||
<BasicInput
|
||||
:value="unitPriceInput"
|
||||
title="قیمت روز طلا"
|
||||
type="tel"
|
||||
disabled
|
||||
addon-before="ریال"
|
||||
place-holder="قیمت صفحه اصلی"
|
||||
/>
|
||||
|
||||
<BasicInput
|
||||
v-model:value="feePercent"
|
||||
title="اجرت ساخت (%)"
|
||||
type="tel"
|
||||
place-holder="مثلاً ۷"
|
||||
inputmode="decimal"
|
||||
/>
|
||||
|
||||
<BasicInput
|
||||
v-model:value="taxPercent"
|
||||
title="سود و مالیات (%)"
|
||||
type="tel"
|
||||
place-holder="مثلاً ۹"
|
||||
inputmode="decimal"
|
||||
/>
|
||||
|
||||
<div class="gold-day-calc__result rounded-xl border border-solid border-gray-200 bg-gray-50 p-3">
|
||||
<div class="text-xs text-gray-500 mb-1">کل نهایی</div>
|
||||
<div class="gold-day-calc__total text-lg font-bold text-primary">
|
||||
<span class="gold-day-calc__num">{{ formattedTotal }}</span>
|
||||
<span class="text-sm font-medium text-gray-600">ریال</span>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-col gap-1 text-xs text-gray-600">
|
||||
<div class="flex justify-between gap-2">
|
||||
<span>هزینه طلا</span>
|
||||
<span class="gold-day-calc__num">{{ formatRial(breakdown.goldCost) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<span>اجرت</span>
|
||||
<span class="gold-day-calc__num">{{ formatRial(breakdown.feeCost) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<span>سود و مالیات</span>
|
||||
<span class="gold-day-calc__num">{{ formatRial(breakdown.taxCost) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<BasicButton type="primary" class="!w-full" size="middle" @click="recalculate">
|
||||
محاسبه مجدد
|
||||
</BasicButton>
|
||||
</template>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
|
||||
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { goldDayCalculatorVisible } from '@/composables/useGoldDayCalculator'
|
||||
import {
|
||||
getLastPrices,
|
||||
getTradeBuyPrice,
|
||||
lastPrice,
|
||||
startLastPricePolling,
|
||||
stopLastPricePolling,
|
||||
} from '@/utils/price'
|
||||
import { getPriceDisplayState, NO_QUOTE_MESSAGE } from '@/utils/storeHours'
|
||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { persianToEnglishDigits } from '@/utils/persianDigits'
|
||||
|
||||
const visible = goldDayCalculatorVisible
|
||||
|
||||
const weight = ref<string | number>('')
|
||||
const feePercent = ref<string | number>('')
|
||||
const taxPercent = ref<string | number>('')
|
||||
const tick = ref(0)
|
||||
|
||||
function parseNum(raw: string | number | undefined): number {
|
||||
const s = persianToEnglishDigits(String(raw ?? ''))
|
||||
.replace(/,/g, '')
|
||||
.replace(/[^\d.]/g, '')
|
||||
const n = Number.parseFloat(s)
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
const liveUnitPrice = computed(() => {
|
||||
void tick.value
|
||||
if (getPriceDisplayState() !== 'open') return 0
|
||||
return getTradeBuyPrice(lastPrice.value) || 0
|
||||
})
|
||||
|
||||
const unitPriceInput = computed(() => {
|
||||
void tick.value
|
||||
const state = getPriceDisplayState()
|
||||
if (state === 'closed') return 'فروشگاه بسته است'
|
||||
if (state === 'no_quote') return NO_QUOTE_MESSAGE
|
||||
const p = liveUnitPrice.value
|
||||
return p > 0 ? usePriceWithoutType(p) : ''
|
||||
})
|
||||
|
||||
const breakdown = computed(() => {
|
||||
void tick.value
|
||||
const w = parseNum(weight.value)
|
||||
const fee = parseNum(feePercent.value)
|
||||
const tax = parseNum(taxPercent.value)
|
||||
const unit = liveUnitPrice.value
|
||||
const goldCost = w * unit
|
||||
const feeCost = goldCost * (fee / 100)
|
||||
const taxCost = (goldCost + feeCost) * (tax / 100)
|
||||
const total = goldCost + feeCost + taxCost
|
||||
return { goldCost, feeCost, taxCost, total }
|
||||
})
|
||||
|
||||
const formattedTotal = computed(() => formatRial(breakdown.value.total))
|
||||
|
||||
function formatRial(n: number) {
|
||||
if (!Number.isFinite(n) || n <= 0) return '۰'
|
||||
return usePriceWithoutType(Math.round(n))
|
||||
}
|
||||
|
||||
function recalculate() {
|
||||
tick.value += 1
|
||||
void getLastPrices()
|
||||
}
|
||||
|
||||
watch(visible, async (open) => {
|
||||
if (open) {
|
||||
startLastPricePolling()
|
||||
await getLastPrices()
|
||||
tick.value += 1
|
||||
} else {
|
||||
stopLastPricePolling()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gold-day-calc {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.gold-day-calc :deep(.ant-input-group) {
|
||||
direction: ltr;
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gold-day-calc :deep(.ant-input-group > .ant-input) {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.gold-day-calc :deep(.ant-input:not(.ant-input-group *)) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.gold-day-calc__result {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.gold-day-calc__total {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
justify-content: flex-start;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.gold-day-calc__num {
|
||||
direction: ltr;
|
||||
unicode-bidi: isolate;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.gold-day-calc-modal {
|
||||
.ant-modal-content {
|
||||
direction: rtl;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: min(88dvh, 640px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ant-modal-header {
|
||||
flex-shrink: 0;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
padding-left: 2.75rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.ant-modal-title {
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gold-day-calc-modal__title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 0.5rem;
|
||||
direction: rtl;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gold-day-calc-modal__title-icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--App-secondary-color, #c9a062);
|
||||
}
|
||||
|
||||
.ant-modal-close {
|
||||
inset-inline-start: 0 !important;
|
||||
inset-inline-end: auto !important;
|
||||
right: auto !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding-bottom: 0.25rem;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
#body {
|
||||
max-height: min(48dvh, 360px) !important;
|
||||
overflow-y: auto !important;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.ant-modal-footer {
|
||||
flex-shrink: 0;
|
||||
margin-top: 0;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid rgba(148, 163, 184, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767.98px) {
|
||||
.gold-day-calc-modal {
|
||||
.ant-modal {
|
||||
max-width: calc(100vw - 1.5rem);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.ant-modal-content {
|
||||
max-height: min(86dvh, 100%);
|
||||
}
|
||||
|
||||
#body {
|
||||
max-height: min(42dvh, 280px) !important;
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
.gold-day-calc-modal #body {
|
||||
max-height: min(38dvh, 240px) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<img
|
||||
:src="currentSrc"
|
||||
:class="[imgClass, dimmed && 'product-image--dimmed']"
|
||||
:alt="alt"
|
||||
@error="handleError"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { getProductDisplayImage } from '@/utils/productAssets'
|
||||
import type { Product } from '@/models/products'
|
||||
import defaultImg from '@/assets/img/coin.png'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
product?: Product | null
|
||||
imgClass?: string
|
||||
alt?: string
|
||||
dimmed?: boolean
|
||||
}>(),
|
||||
{
|
||||
imgClass: 'max-h-full max-w-full object-contain',
|
||||
alt: '',
|
||||
dimmed: false,
|
||||
},
|
||||
)
|
||||
|
||||
function productImageKey(product?: Product | null): string {
|
||||
return [
|
||||
product?.file_id ?? '',
|
||||
product?.file?.id ?? '',
|
||||
product?.file?.full_src ?? '',
|
||||
product?.file?.src ?? '',
|
||||
product?.file?.url ?? '',
|
||||
product?.img ?? '',
|
||||
].join('|')
|
||||
}
|
||||
|
||||
const currentSrc = ref(getProductDisplayImage(props.product) || defaultImg)
|
||||
const usingFallback = ref(false)
|
||||
|
||||
watch(
|
||||
() => productImageKey(props.product),
|
||||
() => {
|
||||
usingFallback.value = false
|
||||
currentSrc.value = getProductDisplayImage(props.product) || defaultImg
|
||||
},
|
||||
)
|
||||
|
||||
function handleError() {
|
||||
if (usingFallback.value) return
|
||||
usingFallback.value = true
|
||||
currentSrc.value = defaultImg
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.product-image--dimmed {
|
||||
opacity: 0.45;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="trade-fee-summary">
|
||||
<div class="trade-fee-summary__quote">
|
||||
<div class="trade-fee-summary__label">مظنه هر {{ weightUnit }}</div>
|
||||
<div class="trade-fee-summary__value trade-fee-summary__value--strong">
|
||||
<span>{{ formattedUnitPrice }}</span>
|
||||
<span class="trade-fee-summary__unit">ریال</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(section, index) in breakdown.sections"
|
||||
:key="section.heading"
|
||||
class="trade-fee-summary__section"
|
||||
:class="{ 'trade-fee-summary__section--divided': index > 0 }"
|
||||
>
|
||||
<div class="trade-fee-summary__heading">{{ section.heading }}</div>
|
||||
|
||||
<div
|
||||
v-for="(row, rowIndex) in section.rows"
|
||||
:key="`${section.heading}-${rowIndex}`"
|
||||
class="trade-fee-summary__row"
|
||||
:class="{
|
||||
'trade-fee-summary__row--divider': row.dividerBefore,
|
||||
'trade-fee-summary__row--bold': row.bold,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="trade-fee-summary__label"
|
||||
:class="{ 'trade-fee-summary__label--strong': row.bold }"
|
||||
>
|
||||
<span v-if="row.plus" class="trade-fee-summary__sign trade-fee-summary__sign--plus">+</span>
|
||||
<span v-else-if="row.minus && !row.bold" class="trade-fee-summary__sign trade-fee-summary__sign--minus">−</span>
|
||||
{{ row.label }}
|
||||
</div>
|
||||
<div
|
||||
class="trade-fee-summary__value"
|
||||
:class="{
|
||||
'trade-fee-summary__value--minus': row.minus && !row.bold,
|
||||
'trade-fee-summary__value--plus': row.plus,
|
||||
'trade-fee-summary__value--strong': row.bold,
|
||||
}"
|
||||
>
|
||||
<template v-if="row.gram != null">
|
||||
<span>{{ formatTradeGramDisplay(row.gram) }}</span>
|
||||
<span class="trade-fee-summary__unit">{{ weightUnit }}</span>
|
||||
</template>
|
||||
<template v-else-if="row.rial != null">
|
||||
<span v-if="row.minus && row.bold">−</span>
|
||||
<span>{{ formatRial(row.rial) }}</span>
|
||||
<span class="trade-fee-summary__unit">ریال</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="section.footnote" class="trade-fee-summary__footnote">
|
||||
{{ section.footnote }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
import {
|
||||
buildTradeConfirmBreakdown,
|
||||
formatTradeGramDisplay,
|
||||
type TradeInputLock,
|
||||
} from '@/utils/serviceFee'
|
||||
|
||||
const props = defineProps<{
|
||||
isBuy: boolean
|
||||
inputLock: TradeInputLock
|
||||
displayTradeGoldAmount: number | string
|
||||
weightUnit: string
|
||||
unitPriceRial: number
|
||||
feeInGrams: number
|
||||
equivalentPrice: string
|
||||
payablePrice: number
|
||||
orderGoldAmount: number
|
||||
feeInRial: number
|
||||
sellGoldDebit?: number
|
||||
}>()
|
||||
|
||||
const formattedUnitPrice = computed(() =>
|
||||
usePriceWithoutType(Math.round(props.unitPriceRial)),
|
||||
)
|
||||
|
||||
function formatRial(value: number) {
|
||||
return usePriceWithoutType(Math.round(value))
|
||||
}
|
||||
|
||||
const breakdown = computed(() =>
|
||||
buildTradeConfirmBreakdown({
|
||||
isBuy: props.isBuy,
|
||||
inputLock: props.inputLock,
|
||||
orderGoldAmount: props.orderGoldAmount,
|
||||
feeInGrams: props.feeInGrams,
|
||||
feeInRial: props.feeInRial,
|
||||
payablePrice: props.payablePrice,
|
||||
unitPriceRial: props.unitPriceRial,
|
||||
sellGoldDebit: props.sellGoldDebit,
|
||||
}),
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.trade-fee-summary {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.75rem;
|
||||
background: var(--app-surface-inset, var(--app-surface));
|
||||
border: 1px solid var(--app-border);
|
||||
}
|
||||
|
||||
.trade-fee-summary__quote {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid var(--app-border);
|
||||
}
|
||||
|
||||
.trade-fee-summary__section {
|
||||
margin-top: 0.75rem;
|
||||
|
||||
&--divided {
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--app-border);
|
||||
}
|
||||
}
|
||||
|
||||
.trade-fee-summary__heading {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
color: var(--app-text-primary);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.trade-fee-summary__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding: 0.25rem 0;
|
||||
|
||||
&--divider {
|
||||
margin-top: 0.25rem;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px dashed var(--app-border);
|
||||
}
|
||||
|
||||
&--bold {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.trade-fee-summary__label {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: var(--app-text-secondary);
|
||||
text-align: right;
|
||||
|
||||
&--strong {
|
||||
font-weight: 700;
|
||||
color: var(--app-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.trade-fee-summary__value {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--app-text-primary);
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
|
||||
&--strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&--minus {
|
||||
color: var(--app-down, #ef4444);
|
||||
}
|
||||
|
||||
&--plus {
|
||||
color: var(--app-up, #22c55e);
|
||||
}
|
||||
}
|
||||
|
||||
.trade-fee-summary__unit {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
color: var(--app-text-muted);
|
||||
}
|
||||
|
||||
.trade-fee-summary__sign {
|
||||
margin-left: 0.25rem;
|
||||
|
||||
&--plus {
|
||||
color: var(--app-up, #22c55e);
|
||||
}
|
||||
|
||||
&--minus {
|
||||
color: var(--app-down, #ef4444);
|
||||
}
|
||||
}
|
||||
|
||||
.trade-fee-summary__footnote {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem 0.625rem;
|
||||
border-radius: 0.625rem;
|
||||
background: var(--app-surface, rgba(255, 255, 255, 0.04));
|
||||
border: 1px solid var(--app-border);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.7;
|
||||
color: var(--app-text-secondary);
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user