import Taro from '@tarojs/taro'; import { Dialog } from '@taroify/core'; import { useCallback, useEffect, useState } from 'react'; import { DialogStatus, PREFIX } from '@/components/product-dialog/const'; import JobBuy from '@/components/product-dialog/steps-ui/job-buy'; import ContactCustomerService from '@/components/product-dialog/steps-ui/job-contact-customer'; import ContactDirect from '@/components/product-dialog/steps-ui/job-contact-direct'; import UnableUnlockContent from '@/components/product-dialog/steps-ui/job-unable'; import { DeclarationType, ProductType } from '@/constants/product'; import { JobDetails } from '@/types/job'; import { GetProductIsUnlockResponse, ProductInfo } from '@/types/product'; import { logWithPrefix } from '@/utils/common'; import { requestUseProduct } from '@/utils/product'; import Toast from '@/utils/toast'; import '../index.less'; interface IProps { data: JobDetails; /** Product use record from parent - if exists, user has already unlocked this job */ productRecord?: GetProductIsUnlockResponse | null; /** Product balance info from parent */ productInfo?: ProductInfo; /** Callback to refresh product balance in parent after purchase */ onRefreshBalance?: () => Promise; onClose: () => void; } const PRODUCT_CODE = ProductType.VIP; const log = logWithPrefix('product-job-contact-dialog'); /** * Integrated dialog component for job contact flow * Handles: balance check -> buy if needed -> use product -> show contact info * * @param productRecord - Pass from parent to avoid duplicate API calls * @param productInfo - Pass from parent to avoid duplicate API calls * @param onRefreshBalance - Callback to refresh balance in parent after purchase */ function ProductJobContactDialog(props: IProps) { const { data, productRecord, productInfo, onRefreshBalance, onClose } = props; const [status, setStatus] = useState(DialogStatus.LOADING); const [publisherAcctNo, setPublisherAcctNo] = useState(''); /** * Handle contact display based on declaration type */ const showContactResult = useCallback((declarationTypeResult?: ProductInfo['declarationTypeResult']) => { if (declarationTypeResult?.type === DeclarationType.Direct && declarationTypeResult.publisherAcctNo) { log('show JOB_CONTACT_DIRECT', declarationTypeResult.publisherAcctNo); setPublisherAcctNo(declarationTypeResult.publisherAcctNo); setStatus(DialogStatus.JOB_CONTACT_DIRECT); } else { log('show JOB_CONTACT_CS'); setStatus(DialogStatus.JOB_CONTACT_CS); } }, []); /** * Use product and show contact info */ const consumeProductAndShowContact = useCallback(async () => { const productResult = await requestUseProduct(PRODUCT_CODE, { jobId: data.id }); log('consumeProductAndShowContact result', productResult); // Refresh balance in parent after consuming product await onRefreshBalance?.(); showContactResult(productResult?.declarationTypeResult); }, [data.id, showContactResult, onRefreshBalance]); /** * Callback after successful purchase * Refresh balance via parent callback and use product to show contact info */ const handleAfterBuy = useCallback(async () => { log('handleAfterBuy - start'); try { Taro.showLoading({ mask: true, title: '加载中...' }); // Refresh balance via parent callback await onRefreshBalance?.(); // Use product and show contact info after purchase await consumeProductAndShowContact(); } catch (e) { log('handleAfterBuy error', e); Toast.error('出错了,请重试'); onClose(); } finally { Taro.hideLoading(); } }, [consumeProductAndShowContact, onRefreshBalance, onClose]); const handleReport = useCallback(() => { log('report', data.id); }, [data.id]); /** * Initialize dialog on mount */ useEffect(() => { let isMounted = true; const init = async () => { try { Taro.showLoading({ mask: true, title: '加载中...' }); log('init with productRecord', productRecord); log('init with productInfo', productInfo); // Step 1: Already unlocked - show contact directly if (productRecord) { log('show JOB_CONTACT_DIRECT from productRecord', productRecord.declarationTypeResult); showContactResult(productRecord.declarationTypeResult); return; } // Step 2: No productInfo - error state if (!productInfo) { log('no productInfo provided, closing'); Toast.error('出错了,请重试'); onClose(); return; } // Step 3: Determine status based on balance if (!productInfo.isPaidVip && !productInfo.freeBalance) { log('show JOB_BUY'); if (isMounted) setStatus(DialogStatus.JOB_BUY); } else if (!productInfo.balance) { log('show JOB_UNABLE_UNLOCK'); if (isMounted) setStatus(DialogStatus.JOB_UNABLE_UNLOCK); } else { await consumeProductAndShowContact(); } } catch (e) { log('init error', e); Toast.error('出错了,请重试'); onClose(); } finally { Taro.hideLoading(); } }; init(); return () => { isMounted = false; }; // Only run on mount - props are captured at dialog open time // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (status === DialogStatus.LOADING) { return null; } return ( {status === DialogStatus.JOB_CONTACT_CS && } {status === DialogStatus.JOB_CONTACT_DIRECT && ( )} {status === DialogStatus.JOB_BUY && ( )} {status === DialogStatus.JOB_UNABLE_UNLOCK && } ); } export default ProductJobContactDialog;