feat: update UI components to use CSS variables for dark mode compatibility

This commit is contained in:
hamed
2026-06-15 14:50:45 +03:30
parent df7a784701
commit e95c5c12d1
8 changed files with 260 additions and 73 deletions
+23 -23
View File
@@ -21,17 +21,17 @@ const EMPTY_ARR: Appointment[] = [];
// ─────────────────────────────────────────────────────────────────────────────
const STATUS_META: Record<string, { label: string; color: string; bg: string }> = {
pending: { label: 'رزرو شده', color: '#3b82f6', bg: '#eff6ff' },
confirmed: { label: 'تأیید شده', color: '#22c55e', bg: '#f0fdf4' },
completed: { label: 'تکمیل شده', color: '#16a34a', bg: '#dcfce7' },
cancelled_by_doctor: { label: 'لغو پزشک', color: '#ef4444', bg: '#fef2f2' },
cancelled_by_user: { label: 'لغو بیمار', color: '#ef4444', bg: '#fef2f2' },
no_show: { label: 'غیبت', color: '#9ca3af', bg: '#f9fafb' },
expired: { label: 'منقضی', color: '#9ca3af', bg: '#f9fafb' },
pending: { label: 'رزرو شده', color: 'var(--info)', bg: 'var(--info-bg)' },
confirmed: { label: 'تأیید شده', color: 'var(--success)', bg: 'var(--success-bg)' },
completed: { label: 'تکمیل شده', color: 'var(--success)', bg: 'var(--success-bg)' },
cancelled_by_doctor: { label: 'لغو پزشک', color: 'var(--danger)', bg: 'var(--danger-bg)' },
cancelled_by_user: { label: 'لغو بیمار', color: 'var(--danger)', bg: 'var(--danger-bg)' },
no_show: { label: 'غیبت', color: 'var(--text-3)', bg: 'var(--surface-2)' },
expired: { label: 'منقضی', color: 'var(--text-3)', bg: 'var(--surface-2)' },
};
function statusMeta(s: string) {
return STATUS_META[s] ?? { label: s, color: '#9ca3af', bg: '#f9fafb' };
return STATUS_META[s] ?? { label: s, color: 'var(--text-3)', bg: 'var(--surface-2)' };
}
// ─────────────────────────────────────────────────────────────────────────────
@@ -275,7 +275,7 @@ function CancelledBadge({ appt }: { appt: Appointment }) {
<div style={{
display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px',
borderRadius: 'var(--r-sm)', background: '#f9fafb',
border: '1px dashed #d1d5db', fontSize: 12, color: '#6b7280',
border: '1px dashed var(--border-2)', fontSize: 12, color: 'var(--text-3)',
marginBottom: 4,
}}>
<span style={{ fontSize: 14, lineHeight: 1 }}></span>
@@ -283,11 +283,11 @@ function CancelledBadge({ appt }: { appt: Appointment }) {
padding: '1px 6px', borderRadius: 99, fontSize: 11, fontWeight: 700,
background: `${sm.color}15`, color: sm.color, whiteSpace: 'nowrap',
}}>{sm.label}</span>
<span style={{ direction: 'ltr', color: '#374151', fontWeight: 600, whiteSpace: 'nowrap' }}>
<span style={{ direction: 'ltr', color: 'var(--text)', fontWeight: 600, whiteSpace: 'nowrap' }}>
{appt.appointment_time} {appt.end_time}
</span>
<span style={{ fontWeight: 600, color: '#374151' }}>{appt.patient_name || '—'}</span>
<span style={{ direction: 'ltr', color: '#9ca3af' }}>{appt.patient_mobile}</span>
<span style={{ fontWeight: 600, color: 'var(--text)' }}>{appt.patient_name || '—'}</span>
<span style={{ direction: 'ltr', color: 'var(--text-3)' }}>{appt.patient_mobile}</span>
</div>
);
}
@@ -333,10 +333,10 @@ function SlotCard({ slot, queryKey, onBook }: { slot: SlotItem; queryKey: unknow
<div style={{ display: 'flex', flexDirection: 'column' }}>
{slot.cancelled_appointment && <CancelledBadge appt={slot.cancelled_appointment} />}
<div style={{
border: '1.5px dashed #d1d5db', borderRadius: 'var(--r)', padding: '10px 16px',
border: '1.5px dashed var(--border-2)', borderRadius: 'var(--r)', padding: '10px 16px',
display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'not-allowed', color: '#9ca3af', fontSize: 13, fontWeight: 600,
background: '#f9fafb', gap: 6,
cursor: 'not-allowed', color: 'var(--text-3)', fontSize: 13, fontWeight: 600,
background: 'var(--surface-2)', gap: 6,
}}>
<span style={{ fontSize: 15 }}></span> گذشته
</div>
@@ -350,13 +350,13 @@ function SlotCard({ slot, queryKey, onBook }: { slot: SlotItem; queryKey: unknow
<div
onClick={() => onBook(slot)}
style={{
border: '1.5px dashed #93c5fd', borderRadius: 'var(--r)', padding: '10px 16px',
border: '1.5px dashed color-mix(in oklch, var(--info) 50%, transparent)', borderRadius: 'var(--r)', padding: '10px 16px',
display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'pointer', color: '#3b82f6', fontSize: 13, fontWeight: 600,
background: '#eff6ff', transition: 'all 0.15s', gap: 6,
cursor: 'pointer', color: 'var(--info)', fontSize: 13, fontWeight: 600,
background: 'var(--info-bg)', transition: 'all 0.15s', gap: 6,
}}
onMouseEnter={e => (e.currentTarget.style.background = '#dbeafe')}
onMouseLeave={e => (e.currentTarget.style.background = '#eff6ff')}
onMouseEnter={e => (e.currentTarget.style.background = 'color-mix(in oklch, var(--info) 20%, transparent)')}
onMouseLeave={e => (e.currentTarget.style.background = 'var(--info-bg)')}
>
<span style={{ fontSize: 18 }}></span> نوبت جدید
</div>
@@ -751,14 +751,14 @@ export default function AppointmentsPage() {
<div style={{
display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12,
padding: '10px 16px', borderRadius: 'var(--r-sm)',
background: '#eff6ff', border: '1px solid #bfdbfe',
fontSize: 13, color: '#1d4ed8',
background: 'var(--info-bg)', border: '1px solid color-mix(in oklch, var(--info) 40%, transparent)',
fontSize: 13, color: 'var(--info)',
}}>
<span style={{ fontSize: 18 }}>👆</span>
<span>روی یک زمان خالی <strong>کلیک کنید</strong> تا نوبت جدید ثبت شود</span>
<button
onClick={() => setBookingHint(false)}
style={{ marginRight: 'auto', background: 'none', border: 'none', cursor: 'pointer', color: '#93c5fd', fontSize: 16 }}
style={{ marginRight: 'auto', background: 'none', border: 'none', cursor: 'pointer', color: 'var(--info)', fontSize: 16 }}
>
</button>
+2 -2
View File
@@ -190,9 +190,9 @@ export default function LoginPage() {
{searchParams.get('error') === 'access_denied' && (
<div style={{
background: '#fee2e2', border: '1px solid #fca5a5',
background: 'var(--danger-bg)', border: '1px solid var(--danger)',
borderRadius: 8, padding: '10px 14px',
fontSize: 13, color: '#dc2626', marginBottom: 16,
fontSize: 13, color: 'var(--danger)', marginBottom: 16,
textAlign: 'center',
}}>
حساب شما دسترسی به پنل مدیریت را ندارد
+8 -8
View File
@@ -211,7 +211,7 @@ export default function SettingsPage() {
transition: 'background .2s', position: 'relative',
}}>
<div style={{
position: 'absolute', top: 2, width: 20, height: 20, borderRadius: '50%', background: '#fff',
position: 'absolute', top: 2, width: 20, height: 20, borderRadius: '50%', background: 'var(--surface)',
transition: 'right .2s', right: commissionEnabled ? 2 : 22,
boxShadow: '0 1px 3px rgba(0,0,0,.2)',
}} />
@@ -306,22 +306,22 @@ export default function SettingsPage() {
{/* درگاه پرداخت */}
<div className="card card-pad">
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: '1.25rem' }}>
<div className="ico" style={{ background: '#fef3c7', color: '#d97706', width: 36, height: 36, borderRadius: 10 }}>
<div className="ico" style={{ background: 'var(--warning-bg)', color: 'var(--warning)', width: 36, height: 36, borderRadius: 10 }}>
<span style={{ fontSize: 16 }}>💳</span>
</div>
<h3 style={{ fontSize: 15 }}>درگاه پرداخت</h3>
</div>
{/* حالت تست */}
<div style={{ marginBottom: '1.25rem', padding: '12px 16px', borderRadius: 'var(--r-sm)', background: paymentTestMode ? '#fef9c3' : 'var(--surface)', border: `1px solid ${paymentTestMode ? '#fbbf24' : 'var(--border)'}` }}>
<div style={{ marginBottom: '1.25rem', padding: '12px 16px', borderRadius: 'var(--r-sm)', background: paymentTestMode ? 'var(--warning-bg)' : 'var(--surface)', border: `1px solid ${paymentTestMode ? 'var(--warning)' : 'var(--border)'}` }}>
<label style={{ display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer' }}>
<input type="hidden" {...register('payment_test_mode')} />
<div
style={{ position: 'relative', width: 44, height: 24, flexShrink: 0, cursor: 'pointer' }}
onClick={() => setValue('payment_test_mode', paymentTestMode ? '0' : '1', { shouldDirty: true })}
>
<div style={{ width: 44, height: 24, borderRadius: 12, background: paymentTestMode ? '#f59e0b' : 'var(--border)', transition: 'background .2s', position: 'relative' }}>
<div style={{ position: 'absolute', top: 2, width: 20, height: 20, borderRadius: '50%', background: '#fff', transition: 'right .2s', right: paymentTestMode ? 2 : 22, boxShadow: '0 1px 3px rgba(0,0,0,.2)' }} />
<div style={{ width: 44, height: 24, borderRadius: 12, background: paymentTestMode ? 'var(--warning)' : 'var(--border)', transition: 'background .2s', position: 'relative' }}>
<div style={{ position: 'absolute', top: 2, width: 20, height: 20, borderRadius: '50%', background: 'var(--surface)', transition: 'right .2s', right: paymentTestMode ? 2 : 22, boxShadow: '0 1px 3px rgba(0,0,0,.2)' }} />
</div>
</div>
<div>
@@ -336,7 +336,7 @@ export default function SettingsPage() {
{/* Mellat */}
<div style={{ marginBottom: '1.25rem' }}>
<div style={{ fontSize: 13.5, fontWeight: 600, marginBottom: '0.75rem', display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#6366f1', display: 'inline-block' }} />
<span style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--violet)', display: 'inline-block' }} />
درگاه ملت (Mellat)
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '0.75rem' }}>
@@ -370,7 +370,7 @@ export default function SettingsPage() {
{/* SEP */}
<div>
<div style={{ fontSize: 13.5, fontWeight: 600, marginBottom: '0.75rem', display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#10b981', display: 'inline-block' }} />
<span style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--success)', display: 'inline-block' }} />
درگاه سپ (SEP)
</div>
<div style={{ maxWidth: 240 }}>
@@ -385,7 +385,7 @@ export default function SettingsPage() {
{/* تنظیمات پیامک */}
<div className="card card-pad">
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: '1.25rem' }}>
<div className="ico" style={{ background: '#ede9fe', color: '#7c3aed', width: 36, height: 36, borderRadius: 10 }}>
<div className="ico" style={{ background: 'var(--violet-bg)', color: 'var(--violet)', width: 36, height: 36, borderRadius: 10 }}>
<span style={{ fontSize: 16 }}>📱</span>
</div>
<h3 style={{ fontSize: 15 }}>تنظیمات پیامک (SMS)</h3>
+16 -16
View File
@@ -293,30 +293,30 @@ function SmsWalletPageInner() {
<div style={{
display: 'flex', alignItems: 'center', gap: 10,
padding: '10px 14px', borderRadius: 8,
background: '#fef9c3', border: '1px solid #fbbf24', fontSize: 13,
background: 'var(--warning-bg)', border: '1px solid var(--warning)', fontSize: 13,
}}>
<ClockIcon style={{ width: 16, color: '#b45309', flexShrink: 0 }} />
<span style={{ color: '#92400e' }}>در انتظار تأیید ادمین</span>
<ClockIcon style={{ width: 16, color: 'var(--warning)', flexShrink: 0 }} />
<span style={{ color: 'var(--warning)' }}>در انتظار تأیید ادمین</span>
</div>
)}
{currentSettings.post_visit_text_status === 'approved' && (
<div style={{
display: 'flex', alignItems: 'center', gap: 10,
padding: '10px 14px', borderRadius: 8,
background: '#dcfce7', border: '1px solid #86efac', fontSize: 13,
background: 'var(--success-bg)', border: '1px solid var(--success)', fontSize: 13,
}}>
<CheckCircleIcon style={{ width: 16, color: '#16a34a', flexShrink: 0 }} />
<span style={{ color: '#15803d' }}>تأیید شده و فعال است</span>
<CheckCircleIcon style={{ width: 16, color: 'var(--success)', flexShrink: 0 }} />
<span style={{ color: 'var(--success)' }}>تأیید شده و فعال است</span>
</div>
)}
{currentSettings.post_visit_text_status === 'rejected' && (
<div style={{
display: 'flex', alignItems: 'flex-start', gap: 10,
padding: '10px 14px', borderRadius: 8,
background: '#fee2e2', border: '1px solid #fca5a5', fontSize: 13,
background: 'var(--danger-bg)', border: '1px solid var(--danger)', fontSize: 13,
}}>
<XCircleIcon style={{ width: 16, color: '#dc2626', flexShrink: 0, marginTop: 1 }} />
<div style={{ color: '#dc2626' }}>
<XCircleIcon style={{ width: 16, color: 'var(--danger)', flexShrink: 0, marginTop: 1 }} />
<div style={{ color: 'var(--danger)' }}>
<div>متن پیامک رد شد</div>
{currentSettings.post_visit_text_reject_reason && (
<div style={{ marginTop: 3, fontSize: 12, opacity: 0.85 }}>دلیل: {currentSettings.post_visit_text_reject_reason}</div>
@@ -422,16 +422,16 @@ function SmsWalletPageInner() {
{/* انتخاب درگاه */}
{isTestMode ? (
<div style={{
background: '#fef9c3',
border: '1px solid #fbbf24',
background: 'var(--warning-bg)',
border: '1px solid var(--warning)',
borderRadius: 10,
padding: '12px 16px',
display: 'flex', alignItems: 'center', gap: 10,
}}>
<span style={{ fontSize: 20 }}></span>
<div>
<div style={{ fontWeight: 700, fontSize: 13.5, color: '#92400e' }}>درگاه آزمایشی فعال است</div>
<div style={{ fontSize: 12, color: '#b45309', marginTop: 2 }}>پول واقعی کسر نخواهد شد این تراکنش آزمایشی است</div>
<div style={{ fontWeight: 700, fontSize: 13.5, color: 'var(--warning)' }}>درگاه آزمایشی فعال است</div>
<div style={{ fontSize: 12, color: 'var(--warning)', marginTop: 2, opacity: 0.8 }}>پول واقعی کسر نخواهد شد این تراکنش آزمایشی است</div>
</div>
</div>
) : (
@@ -476,10 +476,10 @@ function SmsWalletPageInner() {
{watchAmount && Number(watchAmount) >= 10000 && (
<div style={{
background: isTestMode ? '#fef9c3' : 'var(--primary-subtle)',
border: `1px solid ${isTestMode ? '#fbbf24' : 'oklch(0.85 0.06 256)'}`,
background: isTestMode ? 'var(--warning-bg)' : 'var(--primary-soft)',
border: `1px solid ${isTestMode ? 'var(--warning)' : 'color-mix(in oklch, var(--primary) 40%, transparent)'}`,
borderRadius: 8, padding: '10px 14px',
fontSize: 13, color: isTestMode ? '#92400e' : 'var(--primary)', fontWeight: 500,
fontSize: 13, color: isTestMode ? 'var(--warning)' : 'var(--primary)', fontWeight: 500,
}}>
{isTestMode
? `پرداخت آزمایشی ${formatRial(Number(watchAmount))}`
+16 -16
View File
@@ -27,17 +27,17 @@ const PLAN_META: Record<string, {
}> = {
free: {
label: 'رایگان', desc: 'برای شروع کار با سیستم',
color: '#64748b', bg: '#f1f5f9', border: '#cbd5e1',
color: 'var(--text-2)', bg: 'var(--surface-2)', border: 'var(--border)',
icon: ShieldCheckIcon,
},
basic: {
label: 'پایه', desc: 'برای مطب‌های کوچک و متوسط',
color: '#2563eb', bg: '#eff6ff', border: '#93c5fd',
color: 'var(--info)', bg: 'var(--info-bg)', border: 'color-mix(in oklch, var(--info) 40%, transparent)',
icon: RocketLaunchIcon,
},
professional: {
label: 'حرفه‌ای', desc: 'برای کلینیک‌های بزرگ',
color: '#7c3aed', bg: '#f5f3ff', border: '#c4b5fd',
color: 'var(--violet)', bg: 'var(--violet-bg)', border: 'color-mix(in oklch, var(--violet) 40%, transparent)',
icon: SparklesIcon,
},
};
@@ -207,14 +207,14 @@ export default function SubscriptionPage() {
{/* ── بنر تریال (اگر هنوز استفاده نشده و اشتراکی ندارد) ── */}
{!myLoading && !my && !usedTrial && (
<div style={{
background: 'linear-gradient(135deg, #eff6ff, #f5f3ff)',
border: '1.5px dashed #93c5fd',
background: 'linear-gradient(135deg, var(--info-bg), var(--violet-bg))',
border: '1.5px dashed color-mix(in oklch, var(--info) 50%, transparent)',
borderRadius: 'var(--r-lg)',
padding: '18px 22px',
marginBottom: 24,
display: 'flex', alignItems: 'center', gap: 14,
}}>
<SparklesIcon style={{ width: 28, color: '#2563eb', flexShrink: 0 }} />
<SparklesIcon style={{ width: 28, color: 'var(--info)', flexShrink: 0 }} />
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 700, fontSize: 15, color: 'var(--text)', marginBottom: 2 }}>
یک ماه تریال رایگان دارید!
@@ -307,16 +307,16 @@ export default function SubscriptionPage() {
{/* انتخاب درگاه */}
{isTestMode ? (
<div style={{
background: '#fef9c3',
border: '1px solid #fbbf24',
background: 'var(--warning-bg)',
border: '1px solid var(--warning)',
borderRadius: 10,
padding: '12px 16px',
display: 'flex', alignItems: 'center', gap: 10,
}}>
<span style={{ fontSize: 20 }}></span>
<div>
<div style={{ fontWeight: 700, fontSize: 13.5, color: '#92400e' }}>درگاه آزمایشی فعال است</div>
<div style={{ fontSize: 12, color: '#b45309', marginTop: 2 }}>پول واقعی کسر نخواهد شد این تراکنش آزمایشی است</div>
<div style={{ fontWeight: 700, fontSize: 13.5, color: 'var(--warning)' }}>درگاه آزمایشی فعال است</div>
<div style={{ fontSize: 12, color: 'var(--warning)', marginTop: 2, opacity: 0.8 }}>پول واقعی کسر نخواهد شد این تراکنش آزمایشی است</div>
</div>
</div>
) : (
@@ -475,10 +475,10 @@ function PlanCard({
<span style={{
width: 20, height: 20, borderRadius: '50%', flexShrink: 0,
display: 'grid', placeItems: 'center',
background: enabled ? '#dcfce7' : 'var(--surface-3)',
background: enabled ? 'var(--success-bg)' : 'var(--surface-3)',
}}>
{enabled
? <CheckIcon style={{ width: 12, color: '#16a34a' }} />
? <CheckIcon style={{ width: 12, color: 'var(--success)' }} />
: <XMarkIcon style={{ width: 11, color: 'var(--text-3)' }} />
}
</span>
@@ -493,8 +493,8 @@ function PlanCard({
{trialPeriod && !usedTrial && plan.level > 0 && !isDowngrade && (
<div style={{
marginTop: 14, padding: '10px 12px', borderRadius: 'var(--r-sm)',
background: 'linear-gradient(135deg, #eff6ff, #f5f3ff)',
border: '1px dashed #93c5fd', fontSize: 12.5,
background: 'linear-gradient(135deg, var(--info-bg), var(--violet-bg))',
border: '1px dashed color-mix(in oklch, var(--info) 50%, transparent)', fontSize: 12.5,
display: 'flex', alignItems: 'center', gap: 8,
}}>
<span>🎁</span>
@@ -520,8 +520,8 @@ function PlanCard({
<div style={{
display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 12px', borderRadius: 'var(--r-sm)',
background: '#fef9c3', border: '1px solid #fbbf24',
fontSize: 12.5, color: '#92400e', marginBottom: 2,
background: 'var(--warning-bg)', border: '1px solid var(--warning)',
fontSize: 12.5, color: 'var(--warning)', marginBottom: 2,
}}>
<span style={{ fontSize: 15 }}>🔒</span>
<span>تا پایان اشتراک فعلی قابل انتخاب نیست</span>