Localize order modals, add dual logos, and improve admin order tools.
Farsi business order/variation UI, light/dark logo upload on business profile, payment order steps, variation editing, super-admin site links, and checkout/auth fixes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
dc570c1e7f
commit
5e799eddf9
@@ -2,6 +2,8 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { X } from 'lucide-react'
|
||||
import { useLocale } from '@meshkee/dashboard-ui'
|
||||
import { useT } from '../i18n/useT'
|
||||
import type { BusinessMessageKey } from '../i18n/messages'
|
||||
import type { Order, OrderTransaction, TransactionStatus, TransactionType } from '../services/orderService'
|
||||
import { formatIrtPrice } from '../utils/irtPrice'
|
||||
import modalStyles from './VariationsModal.module.css'
|
||||
@@ -15,19 +17,18 @@ interface OrderTransactionsModalProps {
|
||||
|
||||
const ANIMATION_MS = 220
|
||||
|
||||
function transactionTypeLabel(type: TransactionType) {
|
||||
switch (type) {
|
||||
case 'pos':
|
||||
return 'POS'
|
||||
case 'cash':
|
||||
return 'Cash'
|
||||
case 'transfer':
|
||||
return 'Transfer'
|
||||
case 'e_payment_gate':
|
||||
return 'E-payment gateway'
|
||||
default:
|
||||
return type
|
||||
}
|
||||
const TYPE_KEYS: Record<TransactionType, BusinessMessageKey> = {
|
||||
pos: 'orderTransactions.type.pos',
|
||||
cash: 'orderTransactions.type.cash',
|
||||
transfer: 'orderTransactions.type.transfer',
|
||||
e_payment_gate: 'orderTransactions.type.e_payment_gate',
|
||||
}
|
||||
|
||||
const STATUS_KEYS: Record<TransactionStatus, BusinessMessageKey> = {
|
||||
pending: 'orderTransactions.status.pending',
|
||||
completed: 'orderTransactions.status.completed',
|
||||
failed: 'orderTransactions.status.failed',
|
||||
refunded: 'orderTransactions.status.refunded',
|
||||
}
|
||||
|
||||
function statusClass(status: TransactionStatus) {
|
||||
@@ -56,34 +57,8 @@ function formatDateTime(value: string, locale: 'en' | 'fa') {
|
||||
})
|
||||
}
|
||||
|
||||
function transactionDetails(transaction: OrderTransaction, locale: 'en' | 'fa') {
|
||||
const rows: { label: string; value: string }[] = [
|
||||
{ label: 'Status', value: transaction.status },
|
||||
{ label: 'Date', value: formatDateTime(transaction.createdAt, locale) },
|
||||
]
|
||||
|
||||
if (transaction.type === 'pos' && transaction.posType) {
|
||||
rows.push({ label: 'POS type', value: transaction.posType })
|
||||
}
|
||||
if (transaction.type === 'transfer') {
|
||||
if (transaction.transferAccount) {
|
||||
rows.push({ label: 'Account number', value: transaction.transferAccount })
|
||||
}
|
||||
if (transaction.transferRefNumber) {
|
||||
rows.push({ label: 'Ref code', value: transaction.transferRefNumber })
|
||||
}
|
||||
}
|
||||
if (transaction.type === 'e_payment_gate' && transaction.gatewayType) {
|
||||
rows.push({ label: 'Gateway', value: transaction.gatewayType })
|
||||
}
|
||||
if (transaction.notes) {
|
||||
rows.push({ label: 'Notes', value: transaction.notes })
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
export function OrderTransactionsModal({ open, order, onClose }: OrderTransactionsModalProps) {
|
||||
const t = useT()
|
||||
const { locale } = useLocale()
|
||||
const dateLocale = locale === 'fa' ? 'fa' : 'en'
|
||||
const [mounted, setMounted] = useState(open)
|
||||
@@ -120,6 +95,40 @@ export function OrderTransactionsModal({ open, order, onClose }: OrderTransactio
|
||||
|
||||
if (!mounted || !order) return null
|
||||
|
||||
function transactionDetails(transaction: OrderTransaction) {
|
||||
const rows: { label: string; value: string; isStatus?: boolean }[] = [
|
||||
{
|
||||
label: t('orderTransactions.status'),
|
||||
value: t(STATUS_KEYS[transaction.status] ?? 'orderTransactions.status.pending'),
|
||||
isStatus: true,
|
||||
},
|
||||
{
|
||||
label: t('orderTransactions.date'),
|
||||
value: formatDateTime(transaction.createdAt, dateLocale),
|
||||
},
|
||||
]
|
||||
|
||||
if (transaction.type === 'pos' && transaction.posType) {
|
||||
rows.push({ label: t('orderTransactions.posType'), value: transaction.posType })
|
||||
}
|
||||
if (transaction.type === 'transfer') {
|
||||
if (transaction.transferAccount) {
|
||||
rows.push({ label: t('orderTransactions.account'), value: transaction.transferAccount })
|
||||
}
|
||||
if (transaction.transferRefNumber) {
|
||||
rows.push({ label: t('orderTransactions.refCode'), value: transaction.transferRefNumber })
|
||||
}
|
||||
}
|
||||
if (transaction.type === 'e_payment_gate' && transaction.gatewayType) {
|
||||
rows.push({ label: t('orderTransactions.gateway'), value: transaction.gatewayType })
|
||||
}
|
||||
if (transaction.notes) {
|
||||
rows.push({ label: t('orderTransactions.notes'), value: transaction.notes })
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={`${modalStyles.overlay} ${closing ? modalStyles.overlayOut : modalStyles.overlayIn}`}
|
||||
@@ -135,33 +144,40 @@ export function OrderTransactionsModal({ open, order, onClose }: OrderTransactio
|
||||
<div className={modalStyles.header}>
|
||||
<div>
|
||||
<h2 id="order-transactions-title" className={modalStyles.title}>
|
||||
Transaction details
|
||||
{t('orderTransactions.title')}
|
||||
</h2>
|
||||
<p className={modalStyles.subtitle}>{order.orderNumber}</p>
|
||||
</div>
|
||||
<button type="button" className={modalStyles.closeBtn} onClick={onClose} aria-label="Close">
|
||||
<button
|
||||
type="button"
|
||||
className={modalStyles.closeBtn}
|
||||
onClick={onClose}
|
||||
aria-label={t('orderTransactions.close')}
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={`${modalStyles.body} ${styles.body}`}>
|
||||
{transactions.length === 0 ? (
|
||||
<p className={styles.empty}>No payment transactions recorded for this order.</p>
|
||||
<p className={styles.empty}>{t('orderTransactions.empty')}</p>
|
||||
) : (
|
||||
<ul className={styles.txList}>
|
||||
{transactions.map((transaction) => {
|
||||
const details = transactionDetails(transaction, dateLocale)
|
||||
const details = transactionDetails(transaction)
|
||||
return (
|
||||
<li key={transaction.id} className={styles.txCard}>
|
||||
<div className={styles.txHeader}>
|
||||
<div className={styles.txType}>{transactionTypeLabel(transaction.type)}</div>
|
||||
<div className={styles.txType}>
|
||||
{t(TYPE_KEYS[transaction.type] ?? 'orderTransactions.type.e_payment_gate')}
|
||||
</div>
|
||||
<div className={styles.txAmount}>{formatIrtPrice(transaction.amount)}</div>
|
||||
</div>
|
||||
<div className={styles.txMeta}>
|
||||
{details.map((detail) => (
|
||||
<div key={detail.label} className={styles.txMetaItem}>
|
||||
{detail.label}:{' '}
|
||||
{detail.label === 'Status' ? (
|
||||
{detail.isStatus ? (
|
||||
<span className={`${styles.statusBadge} ${statusClass(transaction.status)}`}>
|
||||
{detail.value}
|
||||
</span>
|
||||
@@ -179,20 +195,25 @@ export function OrderTransactionsModal({ open, order, onClose }: OrderTransactio
|
||||
|
||||
<div className={styles.summary}>
|
||||
<span className={styles.summaryLabel}>
|
||||
{transactions.length} {transactions.length === 1 ? 'transaction' : 'transactions'}
|
||||
{t('orderTransactions.summary', {
|
||||
count: transactions.length,
|
||||
txLabel:
|
||||
transactions.length === 1
|
||||
? t('orderTransactions.one')
|
||||
: t('orderTransactions.many'),
|
||||
})}
|
||||
</span>
|
||||
<span className={styles.summaryValue}>{formatIrtPrice(totalPaid)}</span>
|
||||
</div>
|
||||
|
||||
<div className={modalStyles.actions}>
|
||||
<button type="button" className={modalStyles.cancelBtn} onClick={onClose}>
|
||||
Close
|
||||
{t('orderTransactions.close')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
,
|
||||
document.body,
|
||||
</div>,
|
||||
document.body,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user