feat(admin): standardize Buttons and Modals across settings pages and media manager
This commit is contained in:
parent
16516e7003
commit
6de7d8be8a
@ -4,8 +4,10 @@ import { toast } from 'react-hot-toast';
|
||||
import api from '../services/api';
|
||||
import Spinner from '../components/ui/Spinner';
|
||||
import PriceInput from '../components/ui/PriceInput';
|
||||
import Button from '../components/ui/Button';
|
||||
import type { FinancialSettings } from '../types/admin';
|
||||
|
||||
|
||||
export default function FinancialSettingsPage() {
|
||||
const [financial, setFinancial] = useState<FinancialSettings>({
|
||||
taxPercentage: 10,
|
||||
@ -169,13 +171,15 @@ export default function FinancialSettingsPage() {
|
||||
onChange={(val) => setDonationInput(val)}
|
||||
className="px-4 py-2 rounded-xl border border-gray-200 text-xs"
|
||||
/>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
size="xs"
|
||||
startIcon={Plus}
|
||||
onClick={addDonationOption}
|
||||
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl text-xs font-bold flex items-center gap-1 shrink-0 cursor-pointer"
|
||||
>
|
||||
<Plus className="w-4 h-4" /> افزودن
|
||||
</button>
|
||||
افزودن
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2 pt-2">
|
||||
@ -266,17 +270,19 @@ export default function FinancialSettingsPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4 border-t border-gray-100">
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
className="bg-emerald-600 hover:bg-emerald-700 text-white font-bold py-3 px-8 rounded-xl transition-colors flex items-center gap-2 shadow-md shadow-emerald-200 cursor-pointer"
|
||||
variant="primary"
|
||||
size="md"
|
||||
startIcon={Save}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
{isSaving ? <Spinner size="sm" /> : <Save className="w-5 h-5" />}
|
||||
ذخیره تنظیمات مالی
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,6 +6,12 @@ import Spinner from '../components/ui/Spinner';
|
||||
import Pagination from '../components/ui/Pagination';
|
||||
import ConfirmModal from '../components/ui/ConfirmModal';
|
||||
import ImagePreviewModal from '../components/ui/ImagePreviewModal';
|
||||
import Button from '../components/ui/Button';
|
||||
import Modal from '../components/ui/Modal';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
interface Media {
|
||||
id: string;
|
||||
@ -299,26 +305,20 @@ export default function MediaManager() {
|
||||
multiple
|
||||
className="hidden"
|
||||
/>
|
||||
<button
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
startIcon={Upload}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={isUploading}
|
||||
className="bg-purple-600 hover:bg-purple-700 disabled:opacity-70 text-white px-4 py-2.5 rounded-xl flex items-center gap-2 text-sm font-bold transition-all shadow-md shadow-purple-200 cursor-pointer"
|
||||
isLoading={isUploading}
|
||||
>
|
||||
{isUploading ? (
|
||||
<>
|
||||
<Spinner size="sm" />
|
||||
<span>در حال آپلود {uploadProgress ? `(${uploadProgress}٪)` : ''}...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Upload className="w-4 h-4" />
|
||||
<span>آپلود فایل جدید (عکس، ویدئو، پادکست، PDF)</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
{isUploading ? `در حال آپلود ${uploadProgress ? `(${uploadProgress}٪)` : ''}...` : 'آپلود فایل جدید'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Upload Progress Bar if active */}
|
||||
{isUploading && uploadProgress !== null && (
|
||||
<div className="w-full bg-gray-100 rounded-full h-2 overflow-hidden shadow-inner">
|
||||
@ -596,82 +596,82 @@ export default function MediaManager() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Edit SEO Modal */}
|
||||
{editingSeoMedia && (
|
||||
<div className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-xs">
|
||||
<div className="bg-white rounded-2xl shadow-xl w-full max-w-md overflow-hidden p-6 space-y-4">
|
||||
<div className="flex items-center justify-between border-b pb-3">
|
||||
<h3 className="text-lg font-bold text-gray-900">تنظیمات سئوی تصویر</h3>
|
||||
<button onClick={() => setEditingSeoMedia(null)} className="text-gray-400 hover:text-red-500">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 text-right">
|
||||
<div>
|
||||
<label className="text-xs font-bold text-gray-700 block mb-1">متن جایگزین (Alt Text) *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={seoFormData.altText}
|
||||
onChange={(e) => setSeoFormData({ ...seoFormData, altText: e.target.value })}
|
||||
placeholder="مثال: عکس مکمل کانیدروکس گپ کنینا"
|
||||
className="w-full px-3 py-2 border rounded-xl text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-bold text-gray-700 block mb-1">عنوان تصویر (Title)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={seoFormData.title}
|
||||
onChange={(e) => setSeoFormData({ ...seoFormData, title: e.target.value })}
|
||||
placeholder="عنوان تصویر برای تولتیپ هور"
|
||||
className="w-full px-3 py-2 border rounded-xl text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-bold text-gray-700 block mb-1">توضیحات (Description)</label>
|
||||
<textarea
|
||||
rows={2}
|
||||
value={seoFormData.description}
|
||||
onChange={(e) => setSeoFormData({ ...seoFormData, description: e.target.value })}
|
||||
placeholder="توضیحات کامل سئو..."
|
||||
className="w-full px-3 py-2 border rounded-xl text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-bold text-gray-700 block mb-1">زیرنویس (Caption)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={seoFormData.caption}
|
||||
onChange={(e) => setSeoFormData({ ...seoFormData, caption: e.target.value })}
|
||||
placeholder="متن کپشن زیر عکس در مقالات"
|
||||
className="w-full px-3 py-2 border rounded-xl text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-2 border-t">
|
||||
<button
|
||||
type="button"
|
||||
<Modal
|
||||
isOpen={!!editingSeoMedia}
|
||||
onClose={() => setEditingSeoMedia(null)}
|
||||
title="تنظیمات سئوی تصویر"
|
||||
icon={ImageIcon}
|
||||
maxWidth="md"
|
||||
footer={
|
||||
<div className="flex justify-end gap-2.5 w-full">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => setEditingSeoMedia(null)}
|
||||
className="px-4 py-2 rounded-xl text-xs font-bold text-gray-600 bg-gray-100 hover:bg-gray-200"
|
||||
>
|
||||
انصراف
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={handleSaveSeo}
|
||||
className="px-5 py-2 rounded-xl text-xs font-bold text-white bg-purple-600 hover:bg-purple-700 shadow-sm"
|
||||
>
|
||||
ذخیره سئو
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="space-y-3 text-right font-vazir">
|
||||
<div>
|
||||
<label className="text-xs font-bold text-gray-700 block mb-1">متن جایگزین (Alt Text) *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={seoFormData.altText}
|
||||
onChange={(e) => setSeoFormData({ ...seoFormData, altText: e.target.value })}
|
||||
placeholder="مثال: عکس مکمل کانیدروکس گپ کنینا"
|
||||
className="w-full px-3 py-2 border rounded-xl text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-bold text-gray-700 block mb-1">عنوان تصویر (Title)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={seoFormData.title}
|
||||
onChange={(e) => setSeoFormData({ ...seoFormData, title: e.target.value })}
|
||||
placeholder="عنوان تصویر برای تولتیپ هور"
|
||||
className="w-full px-3 py-2 border rounded-xl text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-bold text-gray-700 block mb-1">توضیحات (Description)</label>
|
||||
<textarea
|
||||
rows={2}
|
||||
value={seoFormData.description}
|
||||
onChange={(e) => setSeoFormData({ ...seoFormData, description: e.target.value })}
|
||||
placeholder="توضیحات کامل سئو..."
|
||||
className="w-full px-3 py-2 border rounded-xl text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-bold text-gray-700 block mb-1">زیرنویس (Caption)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={seoFormData.caption}
|
||||
onChange={(e) => setSeoFormData({ ...seoFormData, caption: e.target.value })}
|
||||
placeholder="متن کپشن زیر عکس در مقالات"
|
||||
className="w-full px-3 py-2 border rounded-xl text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={!!deleteTargetId}
|
||||
title="حذف تصویر"
|
||||
|
||||
@ -4,8 +4,10 @@ import { toast } from 'react-hot-toast';
|
||||
import api from '../services/api';
|
||||
import Spinner from '../components/ui/Spinner';
|
||||
import MediaSelector from '../components/ui/MediaSelector';
|
||||
import Button from '../components/ui/Button';
|
||||
import type { SeoSettings } from '../types/admin';
|
||||
|
||||
|
||||
export default function SeoSettingsPage() {
|
||||
const [seo, setSeo] = useState<SeoSettings>({
|
||||
brandNameFa: 'کنینا ایران',
|
||||
@ -271,31 +273,34 @@ export default function SeoSettingsPage() {
|
||||
className="flex-1 px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-xs font-mono"
|
||||
dir="ltr"
|
||||
/>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setIsMediaSelectorOpen(true)}
|
||||
className="px-3 py-3 bg-purple-50 text-purple-600 hover:bg-purple-100 rounded-xl font-bold text-xs shrink-0 cursor-pointer"
|
||||
>
|
||||
انتخاب از رسانه
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4 border-t border-gray-100">
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-8 rounded-xl transition-colors flex items-center gap-2 shadow-md shadow-purple-200 cursor-pointer"
|
||||
variant="primary"
|
||||
size="md"
|
||||
startIcon={Save}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
{isSaving ? <Spinner size="sm" /> : <Save className="w-5 h-5" />}
|
||||
ذخیره تنظیمات سئو و ساختار عناوین
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
|
||||
{isMediaSelectorOpen && (
|
||||
<MediaSelector
|
||||
isOpen={isMediaSelectorOpen}
|
||||
|
||||
@ -20,6 +20,9 @@ import {
|
||||
import { toast } from 'react-hot-toast';
|
||||
import api from '../services/api';
|
||||
import Spinner from '../components/ui/Spinner';
|
||||
import Button from '../components/ui/Button';
|
||||
|
||||
|
||||
|
||||
export default function Settings() {
|
||||
const [activeTab, setActiveTab] = useState<'brand' | 'contact' | 'links'>('brand');
|
||||
@ -358,14 +361,15 @@ export default function Settings() {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-8 rounded-xl transition-colors flex items-center gap-2 shadow-md shadow-purple-200 cursor-pointer"
|
||||
variant="primary"
|
||||
size="md"
|
||||
startIcon={Save}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
{isSaving ? <Spinner size="sm" /> : <Save className="w-5 h-5" />}
|
||||
<span>ذخیره هویت برند</span>
|
||||
</button>
|
||||
ذخیره هویت برند
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : activeTab === 'contact' ? (
|
||||
@ -468,16 +472,18 @@ export default function Settings() {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-8 rounded-xl transition-colors flex items-center gap-2 shadow-md shadow-purple-200 cursor-pointer"
|
||||
variant="primary"
|
||||
size="md"
|
||||
startIcon={Save}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
{isSaving ? <Spinner size="sm" /> : <Save className="w-5 h-5" />}
|
||||
<span>ذخیره اطلاعات تماس</span>
|
||||
</button>
|
||||
ذخیره اطلاعات تماس
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
) : (
|
||||
/* Tab 3: Dedicated Modules Grid */
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5">
|
||||
|
||||
@ -4,6 +4,9 @@ import { toast } from 'react-hot-toast';
|
||||
import api from '../services/api';
|
||||
import Spinner from '../components/ui/Spinner';
|
||||
import PriceInput from '../components/ui/PriceInput';
|
||||
import Button from '../components/ui/Button';
|
||||
|
||||
|
||||
|
||||
export default function ShippingSettingsPage() {
|
||||
const [shipping, setShipping] = useState({
|
||||
@ -148,17 +151,19 @@ export default function ShippingSettingsPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-xl transition-colors flex items-center gap-2 shadow-md shadow-blue-200 cursor-pointer"
|
||||
variant="primary"
|
||||
size="md"
|
||||
startIcon={Save}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
{isSaving ? <Spinner size="sm" /> : <Save className="w-5 h-5" />}
|
||||
<span>ذخیره تغییرات ارسال</span>
|
||||
</button>
|
||||
ذخیره تغییرات ارسال
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -39,6 +39,9 @@ import {
|
||||
import { toast } from 'react-hot-toast';
|
||||
import api from '../services/api';
|
||||
import Spinner from '../components/ui/Spinner';
|
||||
import Button from '../components/ui/Button';
|
||||
|
||||
|
||||
|
||||
interface SmsConfigState {
|
||||
enabled: boolean;
|
||||
@ -767,17 +770,19 @@ export default function SmsSettingsPage() {
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="flex justify-end pt-2">
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-3.5 px-8 rounded-xl transition-all flex items-center gap-2 shadow-lg shadow-purple-200 disabled:opacity-50"
|
||||
variant="primary"
|
||||
size="md"
|
||||
startIcon={Save}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
{isSaving ? <Spinner size="sm" /> : <Save className="w-5 h-5" />}
|
||||
ذخیره تنظیمات درگاه پیامک
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{/* Balance & Test SMS Sidebar (1 col on large) */}
|
||||
<div className="space-y-6">
|
||||
{/* Realtime Credit / Balance Card */}
|
||||
|
||||
@ -19,6 +19,9 @@ import {
|
||||
import { toast } from 'react-hot-toast';
|
||||
import api from '../services/api';
|
||||
import Spinner from '../components/ui/Spinner';
|
||||
import Button from '../components/ui/Button';
|
||||
|
||||
|
||||
|
||||
interface SslStatus {
|
||||
exists: boolean;
|
||||
@ -434,14 +437,16 @@ export default function SslSettingsPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 pt-2">
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isUpdating || !certFile || !keyFile}
|
||||
className="px-6 py-2.5 bg-purple-600 hover:bg-purple-700 disabled:opacity-50 text-white rounded-xl text-xs font-bold transition-all shadow-md shadow-purple-200 flex items-center gap-2 cursor-pointer"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
startIcon={ShieldCheck}
|
||||
disabled={!certFile || !keyFile}
|
||||
isLoading={isUpdating}
|
||||
>
|
||||
{isUpdating ? <Spinner size="sm" /> : <ShieldCheck className="w-4 h-4" />}
|
||||
<span>نصب و فعالسازی گواهی جدید</span>
|
||||
</button>
|
||||
نصب و فعالسازی گواهی جدید
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
@ -479,14 +484,16 @@ export default function SslSettingsPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 pt-2">
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isUpdating || !certText.trim() || !keyText.trim()}
|
||||
className="px-6 py-2.5 bg-purple-600 hover:bg-purple-700 disabled:opacity-50 text-white rounded-xl text-xs font-bold transition-all shadow-md shadow-purple-200 flex items-center gap-2 cursor-pointer"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
startIcon={ShieldCheck}
|
||||
disabled={!certText.trim() || !keyText.trim()}
|
||||
isLoading={isUpdating}
|
||||
>
|
||||
{isUpdating ? <Spinner size="sm" /> : <ShieldCheck className="w-4 h-4" />}
|
||||
<span>ذخیره و اعمال تغییرات</span>
|
||||
</button>
|
||||
ذخیره و اعمال تغییرات
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
@ -509,15 +516,18 @@ export default function SslSettingsPage() {
|
||||
className="flex-1 px-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-xs font-mono outline-none focus:border-blue-500"
|
||||
dir="ltr"
|
||||
/>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
startIcon={ExternalLink}
|
||||
onClick={handleTestOnline}
|
||||
disabled={isTestingOnline}
|
||||
className="px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white text-xs font-bold rounded-xl transition-all shadow-xs flex items-center justify-center gap-2 cursor-pointer"
|
||||
isLoading={isTestingOnline}
|
||||
>
|
||||
{isTestingOnline ? <Spinner size="sm" /> : <ExternalLink className="w-4 h-4" />}
|
||||
<span>تست زنده پورت ۴۴۳ دامنه</span>
|
||||
</button>
|
||||
تست زنده پورت ۴۴۳ دامنه
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{onlineResult && (
|
||||
|
||||
@ -14,8 +14,11 @@ import {
|
||||
import { toast } from 'react-hot-toast';
|
||||
import api from '../services/api';
|
||||
import Spinner from '../components/ui/Spinner';
|
||||
import Button from '../components/ui/Button';
|
||||
import type { SystemSettings } from '../types/admin';
|
||||
|
||||
|
||||
|
||||
export default function SystemSettingsPage() {
|
||||
const [system, setSystem] = useState<SystemSettings>({
|
||||
maintenanceMode: false,
|
||||
@ -324,17 +327,19 @@ export default function SystemSettingsPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-8 rounded-xl transition-colors flex items-center gap-2 shadow-md shadow-purple-200 cursor-pointer"
|
||||
variant="primary"
|
||||
size="md"
|
||||
startIcon={Save}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
{isSaving ? <Spinner size="sm" /> : <Save className="w-5 h-5" />}
|
||||
<span>ذخیره تنظیمات سیستمی</span>
|
||||
</button>
|
||||
ذخیره تنظیمات سیستمی
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user