Bill SMS in credits and add IRT top-up in Super Admin.

Show credit balance in business settings; Super Admin tops up by IRT (adds credits) and tracks SMS sent per business.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-09 11:10:22 +03:30
co-authored by Cursor
parent a8676854fb
commit 6bc57f26ed
8 changed files with 139 additions and 34 deletions
@@ -8,7 +8,6 @@ import { Tooltip } from './Tooltip'
import { useToast } from '../context/ToastContext'
import { useT } from '../i18n/useT'
import { ApiError, isAbortError } from '../lib/api'
import { formatIrtPrice } from '../utils/irtPrice'
import {
createSmsTemplate,
deleteSmsTemplate,
@@ -26,7 +25,7 @@ import settingsStyles from '../pages/SettingsPage.module.css'
import modalStyles from './VariationsModal.module.css'
const SMS_TEMPLATE_VARS = ['{{user.name}}', '{{user.lastname}}', '{{user.title}}'] as const
const LOW_BALANCE_IRT = 200_000
const LOW_BALANCE_CREDITS = 400
const ANIMATION_MS = 220
const MAX_BODY_LEN = 700
@@ -131,7 +130,7 @@ export function SmsSettingsSection() {
}, [listMounted, editorMounted, editorOpen, listOpen, submitting, removeTarget])
const verifiedCount = templates.filter((item) => item.isVerified).length
const isLowBalance = balance < LOW_BALANCE_IRT
const isLowBalance = balance < LOW_BALANCE_CREDITS
function openCreate() {
setEditing(null)
@@ -265,7 +264,11 @@ export function SmsSettingsSection() {
>
<span className={settingsStyles.smsCreditLabel}>{t('settings.sms.balance')}</span>
<strong className={settingsStyles.smsCreditValue}>
{loading ? '…' : formatIrtPrice(balance)}
{loading
? '…'
: t('settings.sms.balanceValue', {
count: balance.toLocaleString(isFa ? 'fa-IR' : 'en-US'),
})}
</strong>
</div>
+7 -3
View File
@@ -1694,9 +1694,11 @@ const en = {
'settings.sms.title': 'SMS settings',
'settings.sms.desc':
'View your prepaid SMS balance and manage message templates. Sender numbers are managed by Meshkee.',
'settings.sms.balance': 'SMS balance',
'settings.sms.balance': 'SMS credits',
'settings.sms.balanceValue': '{count} SMS',
'settings.sms.balanceHint':
'Balance is in IRT. Each 64 characters costs 400 IRT per recipient (login, orders, customer SMS, etc.). Contact Meshkee to top up.',
'Balance is SMS credits. Each 64 characters costs 1 credit per recipient (login, orders, customer SMS, etc.). Contact Meshkee to top up.',
'settings.sms.sentCount': 'SMS sent',
'settings.sms.credit': 'Account credit',
'settings.sms.creditUnavailable': 'Unavailable',
'settings.sms.templates': 'Templates',
@@ -4157,8 +4159,10 @@ const fa: Record<MessageKey, string> = {
'settings.sms.desc':
'اعتبار پیش‌پرداخت پیامک و قالب‌های پیام را ببینید. شماره فرستنده توسط مشکی مدیریت می‌شود.',
'settings.sms.balance': 'اعتبار پیامک',
'settings.sms.balanceValue': '{count} پیامک',
'settings.sms.balanceHint':
'اعتبار به تومان (IRT) است. هر ۶۴ کاراکتر برای هر گیرنده ۴۰۰ تومان هزینه دارد (ورود، سفارش، پیامک مشتری و غیره). برای افزایش با مشکی تماس بگیرید.',
'اعتبار به واحد پیامک است. هر ۶۴ کاراکتر برای هر گیرنده ۱ اعتبار مصرف می‌کند (ورود، سفارش، پیامک مشتری و غیره). برای افزایش با مشکی تماس بگیرید.',
'settings.sms.sentCount': 'پیامک ارسال‌شده',
'settings.sms.credit': 'اعتبار حساب',
'settings.sms.creditUnavailable': 'در دسترس نیست',
'settings.sms.templates': 'قالب‌ها',
@@ -69,6 +69,9 @@ export async function getSmsCredit(signal?: AbortSignal) {
balance: number
/** @deprecated use balance */
credit: number | null
/** Lifetime SMS messages billed (one per recipient). */
sentCount: number
unit: 'credits'
currency: string | null
message: string | null
}>(smsPath('/credit'), { auth: true, signal })