feat: add Kavenegar template guide and update PreRegistrationController to use appUrl
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { CheckIcon, XMarkIcon, TrashIcon, PlusIcon, PencilIcon } from '@heroicons/react/24/outline';
|
||||
import { CheckIcon, XMarkIcon, TrashIcon, PlusIcon, PencilIcon, ClipboardDocumentIcon } from '@heroicons/react/24/outline';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
@@ -45,6 +45,75 @@ const TAG_FILTER_OPTIONS = [
|
||||
...Object.entries(TAG_LABELS).map(([value, label]) => ({ value, label })),
|
||||
];
|
||||
|
||||
// الگوی تمپلت کاوهنگار را از body (با {var}) و token_map (var→slot) میسازد:
|
||||
// هر {var} با %slot جایگزین میشود.
|
||||
function buildKavenegarPattern(body: string, tokenMap: Record<string, string>): string {
|
||||
return body.replace(/\{([a-zA-Z0-9_]+)\}/g, (whole, key) =>
|
||||
tokenMap[key] ? `%${tokenMap[key]}` : whole,
|
||||
);
|
||||
}
|
||||
|
||||
function KavenegarGuide({ msg }: { msg: SmsMessageText }) {
|
||||
const tokenMap = msg.token_map ?? {};
|
||||
const pattern = buildKavenegarPattern(msg.body, tokenMap);
|
||||
const templateName = msg.kavenegar_template ?? '';
|
||||
|
||||
const copy = (text: string, label: string) => {
|
||||
navigator.clipboard.writeText(text).then(
|
||||
() => toast.success(`${label} کپی شد`),
|
||||
() => toast.error('کپی ناموفق بود'),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<details style={{ marginTop: 4 }}>
|
||||
<summary style={{ cursor: 'pointer', fontSize: 12, color: 'var(--primary)', userSelect: 'none' }}>
|
||||
نحوه تعریف این تمپلت در پنل کاوهنگار
|
||||
</summary>
|
||||
<div style={{ marginTop: 8, padding: 12, background: 'var(--surface-2)', borderRadius: 'var(--r-sm)', display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
<div style={{ fontSize: 11, color: 'var(--text-3)', lineHeight: 1.8 }}>
|
||||
در پنل کاوهنگار → بخش «الگوها» یک الگوی جدید با نام و متن زیر بسازید و تأیید بگیرید.
|
||||
متن واقعی پیامک از همین الگو ارسال میشود.
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span style={{ fontSize: 12, color: 'var(--text-2)', minWidth: 90 }}>نام الگو:</span>
|
||||
<code dir="ltr" style={{ fontSize: 12, flex: 1, direction: 'ltr' }}>{templateName || '—'}</code>
|
||||
<button type="button" className="btn ghost sm" disabled={!templateName} onClick={() => copy(templateName, 'نام الگو')}>
|
||||
<ClipboardDocumentIcon style={{ width: 13, height: 13 }} /> کپی
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<span style={{ fontSize: 12, color: 'var(--text-2)' }}>متن الگو (با %token):</span>
|
||||
<button type="button" className="btn ghost sm" onClick={() => copy(pattern, 'متن الگو')}>
|
||||
<ClipboardDocumentIcon style={{ width: 13, height: 13 }} /> کپی متن
|
||||
</button>
|
||||
</div>
|
||||
<pre dir="rtl" style={{ margin: 0, padding: 10, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--r-sm)', fontSize: 13, lineHeight: 1.9, whiteSpace: 'pre-wrap', fontFamily: 'inherit' }}>
|
||||
{pattern}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{Object.keys(tokenMap).length > 0 && (
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<span style={{ fontSize: 11, color: 'var(--text-3)' }}>نگاشت متغیرها:</span>
|
||||
{Object.entries(tokenMap).map(([k, slot]) => (
|
||||
<span key={k} className="chip" dir="ltr">{`{${k}} → %${slot}`}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ fontSize: 11, color: 'var(--text-3)', lineHeight: 1.8 }}>
|
||||
توجه: <code dir="ltr">token</code>، <code dir="ltr">token2</code>، <code dir="ltr">token3</code> فاصله نمیپذیرند؛
|
||||
مقادیر دارای فاصله در <code dir="ltr">token10</code>/<code dir="ltr">token20</code> قرار میگیرند.
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SmsPage() {
|
||||
const qc = useQueryClient();
|
||||
const [activeTab, setActiveTab] = useState<Tab>('samples');
|
||||
@@ -497,6 +566,7 @@ export default function SmsPage() {
|
||||
{m.kavenegar_template || 'تعریفنشده'}
|
||||
</span>
|
||||
</div>
|
||||
<KavenegarGuide msg={m} />
|
||||
{m.variables.length > 0 && (
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||
{m.variables.map((v) => <span key={v} className="chip" dir="ltr">{`{${v}}`}</span>)}
|
||||
|
||||
@@ -112,6 +112,10 @@ services:
|
||||
arguments:
|
||||
$appUrl: '%env(APP_BASE_URL)%'
|
||||
|
||||
App\Auth\Controller\PreRegistrationController:
|
||||
arguments:
|
||||
$appUrl: '%env(APP_BASE_URL)%'
|
||||
|
||||
App\Sms\Controller\SmsWalletController:
|
||||
arguments:
|
||||
$appBaseUrl: '%env(APP_BASE_URL)%'
|
||||
|
||||
@@ -34,6 +34,7 @@ class PreRegistrationController extends BaseController
|
||||
private readonly UserPasswordHasherInterface $hasher,
|
||||
private readonly SmsService $sms,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly string $appUrl,
|
||||
) {}
|
||||
|
||||
#[Route('/api/v1/pre-registration', methods: ['POST'])]
|
||||
@@ -160,7 +161,7 @@ class PreRegistrationController extends BaseController
|
||||
[
|
||||
'username' => $preReg->getMobile(),
|
||||
'password' => $password,
|
||||
'link' => 'https://clinic-pro.ddev.site/admin',
|
||||
'link' => rtrim($this->appUrl, '/') . '/admin',
|
||||
],
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
Reference in New Issue
Block a user