Add e-payment UI, checkout payment result, and legacy migrate domain lookup.
Wire Mellat settings in business dashboard, customer bank return page with payment details, and super-admin migrate modal domain confirmation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
a11f9643bb
commit
cc94b929e0
@@ -33,12 +33,14 @@ import {
|
||||
addBusinessDomain,
|
||||
createBusiness,
|
||||
listBusinesses,
|
||||
lookupLegacyMigrate,
|
||||
migrateBusinessFromOld,
|
||||
purgeBusinessData,
|
||||
removeBusiness,
|
||||
setBusinessActive,
|
||||
updateBusiness,
|
||||
updateBusinessDomain,
|
||||
type LegacyMigrateLookupResponse,
|
||||
} from '../services/businessService'
|
||||
import { listBusinessCategories } from '../services/categoryService'
|
||||
import { Modal } from '../components/Modal'
|
||||
@@ -111,6 +113,8 @@ const MIGRATE_ENTITY_LABELS: Record<MigrateFromOldEntity, string> = {
|
||||
customer: 'Customers',
|
||||
blog_categories: 'Blog categories',
|
||||
blog: 'Blogs',
|
||||
video_categories: 'Video categories',
|
||||
video: 'Videos',
|
||||
portfolio_categories: 'Portfolio categories',
|
||||
portfolio: 'Portfolios',
|
||||
}
|
||||
@@ -137,6 +141,13 @@ const DATA_ENTITY_GROUPS = [
|
||||
['blog', 'Blogs'],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Videos',
|
||||
items: [
|
||||
['video_categories', 'Categories'],
|
||||
['video', 'Videos'],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Portfolios',
|
||||
items: [
|
||||
@@ -194,6 +205,8 @@ function emptyMigrateEntities(): Record<MigrateFromOldEntity, boolean> {
|
||||
customer: false,
|
||||
blog_categories: false,
|
||||
blog: false,
|
||||
video_categories: false,
|
||||
video: false,
|
||||
portfolio_categories: false,
|
||||
portfolio: false,
|
||||
}
|
||||
@@ -273,6 +286,9 @@ export function BusinessesPage() {
|
||||
const [migrateSubmitting, setMigrateSubmitting] = useState(false)
|
||||
const [migrateError, setMigrateError] = useState('')
|
||||
const [migrateResult, setMigrateResult] = useState<MigrateFromOldResponse | null>(null)
|
||||
const [migrateLookup, setMigrateLookup] = useState<LegacyMigrateLookupResponse | null>(null)
|
||||
const [migrateLookupLoading, setMigrateLookupLoading] = useState(false)
|
||||
const [migrateLookupError, setMigrateLookupError] = useState('')
|
||||
|
||||
const [purgeOpen, setPurgeOpen] = useState(false)
|
||||
const [purgeBusiness, setPurgeBusiness] = useState<BusinessListItem | null>(null)
|
||||
@@ -629,6 +645,8 @@ export function BusinessesPage() {
|
||||
setMigrateEntities(emptyMigrateEntities())
|
||||
setMigrateError('')
|
||||
setMigrateResult(null)
|
||||
setMigrateLookup(null)
|
||||
setMigrateLookupError('')
|
||||
setMigrateOpen(true)
|
||||
}
|
||||
|
||||
@@ -641,8 +659,59 @@ export function BusinessesPage() {
|
||||
setMigrateBusiness(null)
|
||||
setMigrateError('')
|
||||
setMigrateResult(null)
|
||||
setMigrateLookup(null)
|
||||
setMigrateLookupError('')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!migrateOpen || !migrateBusiness) return
|
||||
|
||||
const trimmed = String(migrateOldId).trim()
|
||||
const explicitId = Number(trimmed)
|
||||
const hasExplicitId = trimmed !== '' && Number.isInteger(explicitId) && explicitId > 0
|
||||
|
||||
const controller = new AbortController()
|
||||
const timer = window.setTimeout(() => {
|
||||
void (async () => {
|
||||
setMigrateLookupLoading(true)
|
||||
setMigrateLookupError('')
|
||||
try {
|
||||
const result = await lookupLegacyMigrate(
|
||||
migrateBusiness.id,
|
||||
hasExplicitId ? explicitId : undefined,
|
||||
controller.signal,
|
||||
)
|
||||
if (controller.signal.aborted) return
|
||||
setMigrateLookup(result)
|
||||
|
||||
// Auto-fill from Meshkee domain match when the field is empty
|
||||
if (
|
||||
!hasExplicitId &&
|
||||
result.source === 'meshkee_domain' &&
|
||||
result.oldBusinessId
|
||||
) {
|
||||
setMigrateOldId(result.oldBusinessId)
|
||||
}
|
||||
} catch (err) {
|
||||
if (isAbortError(err)) return
|
||||
setMigrateLookup(null)
|
||||
setMigrateLookupError(
|
||||
err instanceof ApiError
|
||||
? err.message
|
||||
: 'Unable to look up old CMS domain.',
|
||||
)
|
||||
} finally {
|
||||
if (!controller.signal.aborted) setMigrateLookupLoading(false)
|
||||
}
|
||||
})()
|
||||
}, hasExplicitId ? 350 : 0)
|
||||
|
||||
return () => {
|
||||
controller.abort()
|
||||
window.clearTimeout(timer)
|
||||
}
|
||||
}, [migrateOpen, migrateBusiness, migrateOldId])
|
||||
|
||||
function selectMigrateEntity(entity: MigrateFromOldEntity) {
|
||||
setMigrateResult(null)
|
||||
setMigrateError('')
|
||||
@@ -1593,7 +1662,7 @@ export function BusinessesPage() {
|
||||
Link this business to a legacy WillaEngine business id, then choose one
|
||||
data type to migrate. Selecting blogs also migrates news under a top-level
|
||||
News category. Selecting customers also migrates client categories when
|
||||
needed.
|
||||
needed. The old id is resolved from the Meshkee domain when possible.
|
||||
</p>
|
||||
<div className={styles.field}>
|
||||
<label htmlFor="migrate-old-id">Old business id</label>
|
||||
@@ -1612,6 +1681,57 @@ export function BusinessesPage() {
|
||||
disabled={migrateSubmitting}
|
||||
/>
|
||||
</div>
|
||||
{migrateLookupLoading ? (
|
||||
<p className={styles.alertInfo} role="status">
|
||||
Looking up old CMS domain…
|
||||
</p>
|
||||
) : null}
|
||||
{migrateLookupError ? (
|
||||
<p className={styles.alertError} role="alert">
|
||||
{migrateLookupError}
|
||||
</p>
|
||||
) : null}
|
||||
{migrateLookup && !migrateLookupLoading ? (
|
||||
migrateLookup.oldBusinessId && migrateLookup.domains.length > 0 ? (
|
||||
<div className={styles.alertInfo} role="status">
|
||||
<strong>Old CMS match</strong>
|
||||
<div style={{ marginTop: 6 }}>
|
||||
Domain{migrateLookup.domains.length > 1 ? 's' : ''}:{' '}
|
||||
<strong dir="ltr">{migrateLookup.domains.join(', ')}</strong>
|
||||
</div>
|
||||
{(migrateLookup.brandEn || migrateLookup.brandFa) && (
|
||||
<div style={{ marginTop: 4 }}>
|
||||
Brand:{' '}
|
||||
<strong>
|
||||
{[migrateLookup.brandEn, migrateLookup.brandFa]
|
||||
.filter(Boolean)
|
||||
.join(' / ')}
|
||||
</strong>
|
||||
</div>
|
||||
)}
|
||||
{migrateLookup.matchedHost ? (
|
||||
<div style={{ marginTop: 4 }}>
|
||||
Matched from Meshkee host:{' '}
|
||||
<strong dir="ltr">{migrateLookup.matchedHost}</strong>
|
||||
</div>
|
||||
) : null}
|
||||
{migrateLookup.meshkeeHosts.length > 0 ? (
|
||||
<div style={{ marginTop: 4, opacity: 0.85 }}>
|
||||
This business hosts:{' '}
|
||||
<span dir="ltr">{migrateLookup.meshkeeHosts.join(', ')}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<p className={styles.alertInfo} role="status">
|
||||
{migrateOldId.trim()
|
||||
? 'No old CMS business found for this id. Check the number before migrating.'
|
||||
: migrateLookup.meshkeeHosts.length
|
||||
? `No old CMS domain match for ${migrateLookup.meshkeeHosts.join(', ')}. Enter the old business id manually.`
|
||||
: 'This Meshkee business has no domain yet. Enter the old business id manually.'}
|
||||
</p>
|
||||
)
|
||||
) : null}
|
||||
<div className={styles.field} style={{ marginTop: 12 }}>
|
||||
<span className={styles.entityGroupLabel}>Data to migrate (one)</span>
|
||||
<div className={styles.entityChecks}>
|
||||
|
||||
@@ -117,6 +117,32 @@ export async function migrateBusinessFromOld(
|
||||
})
|
||||
}
|
||||
|
||||
export interface LegacyMigrateLookupResponse {
|
||||
meshkeeHosts: string[]
|
||||
meshkeeBusinessName: string
|
||||
oldBusinessId: string | null
|
||||
domains: string[]
|
||||
brandEn: string | null
|
||||
brandFa: string | null
|
||||
slug: string | null
|
||||
matchedHost: string | null
|
||||
source: 'old_business_id' | 'meshkee_domain' | 'none'
|
||||
}
|
||||
|
||||
export async function lookupLegacyMigrate(
|
||||
businessId: string,
|
||||
oldBusinessId?: number,
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
const q = new URLSearchParams()
|
||||
if (oldBusinessId != null) q.set('oldBusinessId', String(oldBusinessId))
|
||||
const query = q.toString()
|
||||
return apiRequest<LegacyMigrateLookupResponse>(
|
||||
`/businesses/${businessId}/migrate-from-old/lookup${query ? `?${query}` : ''}`,
|
||||
{ auth: true, signal },
|
||||
)
|
||||
}
|
||||
|
||||
export async function purgeBusinessData(
|
||||
businessId: string,
|
||||
payload: PurgeBusinessDataPayload,
|
||||
|
||||
@@ -46,6 +46,8 @@ export type MigrateFromOldEntity =
|
||||
| 'customer'
|
||||
| 'blog_categories'
|
||||
| 'blog'
|
||||
| 'video_categories'
|
||||
| 'video'
|
||||
| 'portfolio_categories'
|
||||
| 'portfolio'
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ export const BUSINESS_DASHBOARD_MODULE_IDS = [
|
||||
'blog',
|
||||
'warehouse',
|
||||
'videos',
|
||||
'instructions',
|
||||
'finance',
|
||||
] as const
|
||||
|
||||
@@ -33,6 +34,7 @@ export const BUSINESS_DASHBOARD_MODULE_LABELS: Record<
|
||||
blog: 'Blog',
|
||||
warehouse: 'Warehouse',
|
||||
videos: 'Videos',
|
||||
instructions: 'Instructions',
|
||||
finance: 'Finance',
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user