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:
@@ -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