From f3aeaad94824a38656a4b9a0d7669bc2f93b7563 Mon Sep 17 00:00:00 2001 From: romantarkin Date: Wed, 23 Jul 2025 14:14:34 +0500 Subject: [PATCH] style and modals --- frontend/src/components/Paginator.js | 37 ++++ frontend/src/modals/CreateCampaignModal.js | 45 ++++ frontend/src/modals/CreateGroupModal.js | 23 ++ frontend/src/modals/CreateSmtpModal.js | 41 ++++ frontend/src/modals/CreateSubscriberModal.js | 30 +++ frontend/src/modals/CreateTemplateModal.js | 20 ++ frontend/src/modals/CreateUnsubModal.js | 23 ++ frontend/src/modals/CreateUserModal.js | 33 +++ frontend/src/modals/EditCampaignModal.js | 45 ++++ frontend/src/modals/EditGroupModal.js | 23 ++ frontend/src/modals/EditSmtpModal.js | 41 ++++ frontend/src/modals/EditSubscriberModal.js | 30 +++ frontend/src/modals/EditTemplateModal.js | 20 ++ frontend/src/modals/EditUnsubModal.js | 23 ++ frontend/src/modals/EditUserModal.js | 30 +++ frontend/src/modals/Modal.js | 22 ++ frontend/src/pages/CampaignPage.js | 150 +++---------- frontend/src/pages/DeliveryHistoryPage.js | 30 +-- frontend/src/pages/EmailTemplatesPage.js | 94 +++----- frontend/src/pages/GroupsPage.js | 209 ++++-------------- frontend/src/pages/SmtpServersPage.js | 136 +++--------- frontend/src/pages/UnsubscribedPage.js | 100 +++------ frontend/src/pages/UsersPage.js | 120 +++------- frontend/src/styles/CampaignModal.module.css | 59 +++++ frontend/src/styles/GroupModal.module.css | 59 +++++ frontend/src/styles/Modal.module.css | 44 ++++ frontend/src/styles/Paginator.module.css | 39 ++++ frontend/src/styles/SmtpModal.module.css | 59 +++++ .../src/styles/SubscriberModal.module.css | 59 +++++ frontend/src/styles/TemplateModal.module.css | 59 +++++ frontend/src/styles/UnsubModal.module.css | 59 +++++ frontend/src/styles/UserModal.module.css | 59 +++++ 32 files changed, 1171 insertions(+), 650 deletions(-) create mode 100644 frontend/src/components/Paginator.js create mode 100644 frontend/src/modals/CreateCampaignModal.js create mode 100644 frontend/src/modals/CreateGroupModal.js create mode 100644 frontend/src/modals/CreateSmtpModal.js create mode 100644 frontend/src/modals/CreateSubscriberModal.js create mode 100644 frontend/src/modals/CreateTemplateModal.js create mode 100644 frontend/src/modals/CreateUnsubModal.js create mode 100644 frontend/src/modals/CreateUserModal.js create mode 100644 frontend/src/modals/EditCampaignModal.js create mode 100644 frontend/src/modals/EditGroupModal.js create mode 100644 frontend/src/modals/EditSmtpModal.js create mode 100644 frontend/src/modals/EditSubscriberModal.js create mode 100644 frontend/src/modals/EditTemplateModal.js create mode 100644 frontend/src/modals/EditUnsubModal.js create mode 100644 frontend/src/modals/EditUserModal.js create mode 100644 frontend/src/modals/Modal.js create mode 100644 frontend/src/styles/CampaignModal.module.css create mode 100644 frontend/src/styles/GroupModal.module.css create mode 100644 frontend/src/styles/Modal.module.css create mode 100644 frontend/src/styles/Paginator.module.css create mode 100644 frontend/src/styles/SmtpModal.module.css create mode 100644 frontend/src/styles/SubscriberModal.module.css create mode 100644 frontend/src/styles/TemplateModal.module.css create mode 100644 frontend/src/styles/UnsubModal.module.css create mode 100644 frontend/src/styles/UserModal.module.css diff --git a/frontend/src/components/Paginator.js b/frontend/src/components/Paginator.js new file mode 100644 index 0000000..c3d43df --- /dev/null +++ b/frontend/src/components/Paginator.js @@ -0,0 +1,37 @@ +import React from 'react'; +import styles from '../styles/Paginator.module.css'; + +export default function Paginator({ page, total, pageSize, onPageChange, className }) { + const pageCount = Math.ceil(total / pageSize) || 1; + if (pageCount <= 1) return null; + return ( +
+ + {Array.from({ length: pageCount }, (_, i) => ( + + ))} + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/CreateCampaignModal.js b/frontend/src/modals/CreateCampaignModal.js new file mode 100644 index 0000000..7223231 --- /dev/null +++ b/frontend/src/modals/CreateCampaignModal.js @@ -0,0 +1,45 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/CampaignModal.module.css'; + +export default function CreateCampaignModal({ isOpen, onClose, campaign, groups, versions, loading, onChange, onSave, getVersionName }) { + return ( + +

Добавить кампанию

+
+ + + + + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/CreateGroupModal.js b/frontend/src/modals/CreateGroupModal.js new file mode 100644 index 0000000..3c42bd9 --- /dev/null +++ b/frontend/src/modals/CreateGroupModal.js @@ -0,0 +1,23 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/GroupModal.module.css'; + +export default function CreateGroupModal({ isOpen, onClose, group, loading, onChange, onSave }) { + return ( + +

Добавить группу

+
+ + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/CreateSmtpModal.js b/frontend/src/modals/CreateSmtpModal.js new file mode 100644 index 0000000..ba488ce --- /dev/null +++ b/frontend/src/modals/CreateSmtpModal.js @@ -0,0 +1,41 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/SmtpModal.module.css'; + +export default function CreateSmtpModal({ isOpen, onClose, server, loading, onChange, onSave }) { + return ( + +

Добавить SMTP-сервер

+
+ + + + + + + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/CreateSubscriberModal.js b/frontend/src/modals/CreateSubscriberModal.js new file mode 100644 index 0000000..8af97a6 --- /dev/null +++ b/frontend/src/modals/CreateSubscriberModal.js @@ -0,0 +1,30 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/SubscriberModal.module.css'; + +export default function CreateSubscriberModal({ isOpen, onClose, sub, loading, onChange, onSave }) { + return ( + +

Добавить подписчика

+
+ + + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/CreateTemplateModal.js b/frontend/src/modals/CreateTemplateModal.js new file mode 100644 index 0000000..35030b8 --- /dev/null +++ b/frontend/src/modals/CreateTemplateModal.js @@ -0,0 +1,20 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/TemplateModal.module.css'; + +export default function CreateTemplateModal({ isOpen, onClose, template, loading, onChange, onSave }) { + return ( + +

Добавить шаблон

+
+ +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/CreateUnsubModal.js b/frontend/src/modals/CreateUnsubModal.js new file mode 100644 index 0000000..55f7a14 --- /dev/null +++ b/frontend/src/modals/CreateUnsubModal.js @@ -0,0 +1,23 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/UnsubModal.module.css'; + +export default function CreateUnsubModal({ isOpen, onClose, sub, loading, onChange, onSave }) { + return ( + +

Добавить запись

+
+ + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/CreateUserModal.js b/frontend/src/modals/CreateUserModal.js new file mode 100644 index 0000000..c5ae34d --- /dev/null +++ b/frontend/src/modals/CreateUserModal.js @@ -0,0 +1,33 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/UserModal.module.css'; + +export default function CreateUserModal({ isOpen, onClose, user, roles, loading, onChange, onSave }) { + return ( + +

Добавить пользователя

+
+ + + + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/EditCampaignModal.js b/frontend/src/modals/EditCampaignModal.js new file mode 100644 index 0000000..b47e1be --- /dev/null +++ b/frontend/src/modals/EditCampaignModal.js @@ -0,0 +1,45 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/CampaignModal.module.css'; + +export default function EditCampaignModal({ isOpen, onClose, campaign, groups, versions, loading, onChange, onSave, getVersionName }) { + return ( + +

Редактировать кампанию

+
+ + + + + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/EditGroupModal.js b/frontend/src/modals/EditGroupModal.js new file mode 100644 index 0000000..1431118 --- /dev/null +++ b/frontend/src/modals/EditGroupModal.js @@ -0,0 +1,23 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/GroupModal.module.css'; + +export default function EditGroupModal({ isOpen, onClose, group, loading, onChange, onSave }) { + return ( + +

Редактировать группу

+
+ + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/EditSmtpModal.js b/frontend/src/modals/EditSmtpModal.js new file mode 100644 index 0000000..a923b4a --- /dev/null +++ b/frontend/src/modals/EditSmtpModal.js @@ -0,0 +1,41 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/SmtpModal.module.css'; + +export default function EditSmtpModal({ isOpen, onClose, server, loading, onChange, onSave }) { + return ( + +

Редактировать SMTP-сервер

+
+ + + + + + + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/EditSubscriberModal.js b/frontend/src/modals/EditSubscriberModal.js new file mode 100644 index 0000000..bf5e23f --- /dev/null +++ b/frontend/src/modals/EditSubscriberModal.js @@ -0,0 +1,30 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/SubscriberModal.module.css'; + +export default function EditSubscriberModal({ isOpen, onClose, sub, loading, onChange, onSave }) { + return ( + +

Редактировать подписчика

+
+ + + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/EditTemplateModal.js b/frontend/src/modals/EditTemplateModal.js new file mode 100644 index 0000000..4d38f9b --- /dev/null +++ b/frontend/src/modals/EditTemplateModal.js @@ -0,0 +1,20 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/TemplateModal.module.css'; + +export default function EditTemplateModal({ isOpen, onClose, template, loading, onChange, onSave }) { + return ( + +

Редактировать шаблон

+
+ +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/EditUnsubModal.js b/frontend/src/modals/EditUnsubModal.js new file mode 100644 index 0000000..3d64868 --- /dev/null +++ b/frontend/src/modals/EditUnsubModal.js @@ -0,0 +1,23 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/UnsubModal.module.css'; + +export default function EditUnsubModal({ isOpen, onClose, sub, loading, onChange, onSave }) { + return ( + +

Редактировать запись

+
+ + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/EditUserModal.js b/frontend/src/modals/EditUserModal.js new file mode 100644 index 0000000..268f5a5 --- /dev/null +++ b/frontend/src/modals/EditUserModal.js @@ -0,0 +1,30 @@ +import React from 'react'; +import Modal from './Modal'; +import styles from '../styles/UserModal.module.css'; + +export default function EditUserModal({ isOpen, onClose, user, roles, loading, onChange, onSave }) { + return ( + +

Редактировать пользователя

+
+ + + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/modals/Modal.js b/frontend/src/modals/Modal.js new file mode 100644 index 0000000..6c4a82f --- /dev/null +++ b/frontend/src/modals/Modal.js @@ -0,0 +1,22 @@ +import React from 'react'; +import styles from '../styles/Modal.module.css'; + +export default function Modal({ isOpen, onClose, children, disabled }) { + if (!isOpen) return null; + return ( +
+
+ + {children} +
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/CampaignPage.js b/frontend/src/pages/CampaignPage.js index fc020ad..f0e82cb 100644 --- a/frontend/src/pages/CampaignPage.js +++ b/frontend/src/pages/CampaignPage.js @@ -1,5 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useUser } from '../context/UserContext'; +import EditCampaignModal from '../modals/EditCampaignModal'; +import CreateCampaignModal from '../modals/CreateCampaignModal'; +import Paginator from '../components/Paginator'; const PAGE_SIZE = 10; @@ -228,126 +231,39 @@ function CampaignPage() { )} -
-
- - {Array.from({ length: Math.ceil(total / PAGE_SIZE) || 1 }, (_, i) => ( - - ))} - -
-
+ )} {editCampaign && ( -
-
- -

Редактировать кампанию

-
- - - - - -
- - -
-
-
-
+ setEditCampaign(null)} + campaign={editCampaign} + groups={groups} + versions={versions} + loading={editLoading} + onChange={setEditCampaign} + onSave={handleEditSave} + getVersionName={getVersionName} + /> )} {createCampaign && ( -
-
- -

Добавить кампанию

-
- - - - - -
- - -
-
-
-
+ setCreateCampaign(null)} + campaign={createCampaign} + groups={groups} + versions={versions} + loading={createLoading} + onChange={setCreateCampaign} + onSave={handleCreateSave} + getVersionName={getVersionName} + /> )} ); @@ -364,9 +280,5 @@ const inputStyle = { marginTop: 4, padding: '10px 12px', borderRadius: 8, border const saveBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; const cancelBtnStyle = { background: '#f3f4f6', color: '#6366f1', border: 'none', borderRadius: 8, padding: '10px 18px', fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.2s' }; const addBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; -const paginatorWrapperStyle = { marginTop: 24, display: 'flex', gap: 6, alignItems: 'center', justifyContent: 'flex-end' }; -const paginatorBtnStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 18, fontWeight: 700, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageActiveStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', cursor: 'default', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)' }; export default CampaignPage; \ No newline at end of file diff --git a/frontend/src/pages/DeliveryHistoryPage.js b/frontend/src/pages/DeliveryHistoryPage.js index 4225547..4ec00df 100644 --- a/frontend/src/pages/DeliveryHistoryPage.js +++ b/frontend/src/pages/DeliveryHistoryPage.js @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useUser } from '../context/UserContext'; +import Paginator from '../components/Paginator'; const PAGE_SIZE = 10; @@ -80,25 +81,12 @@ function DeliveryHistoryPage() { )} -
-
- - {Array.from({ length: Math.ceil(total / PAGE_SIZE) || 1 }, (_, i) => ( - - ))} - -
-
+ )} @@ -107,9 +95,5 @@ function DeliveryHistoryPage() { const thStyle = { padding: '10px 16px', textAlign: 'left', fontWeight: 600, borderBottom: '2px solid #e5e7eb', background: '#f3f4f6' }; const tdStyle = { padding: '10px 16px', background: '#fff' }; -const paginatorWrapperStyle = { marginTop: 24, display: 'flex', gap: 6, alignItems: 'center', justifyContent: 'flex-end' }; -const paginatorBtnStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 18, fontWeight: 700, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageActiveStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', cursor: 'default', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)' }; export default DeliveryHistoryPage; \ No newline at end of file diff --git a/frontend/src/pages/EmailTemplatesPage.js b/frontend/src/pages/EmailTemplatesPage.js index 8d12560..3f7ea73 100644 --- a/frontend/src/pages/EmailTemplatesPage.js +++ b/frontend/src/pages/EmailTemplatesPage.js @@ -1,5 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useUser } from '../context/UserContext'; +import EditTemplateModal from '../modals/EditTemplateModal'; +import CreateTemplateModal from '../modals/CreateTemplateModal'; +import Paginator from '../components/Paginator'; const PAGE_SIZE = 10; @@ -164,76 +167,33 @@ function EmailTemplatesPage() { )} -
-
- - {Array.from({ length: Math.ceil(total / PAGE_SIZE) || 1 }, (_, i) => ( - - ))} - -
-
+ )} {editTemplate && ( -
-
- -

Редактировать шаблон

-
- -
- - -
-
-
-
+ setEditTemplate(null)} + template={editTemplate} + loading={editLoading} + onChange={setEditTemplate} + onSave={handleEditSave} + /> )} {createTemplate && ( -
-
- -

Добавить шаблон

-
- -
- - -
-
-
-
+ setCreateTemplate(null)} + template={createTemplate} + loading={createLoading} + onChange={setCreateTemplate} + onSave={handleCreateSave} + /> )} ); @@ -249,10 +209,6 @@ const labelStyle = { fontWeight: 500, color: '#374151', fontSize: 15, display: ' const inputStyle = { marginTop: 4, padding: '10px 12px', borderRadius: 8, border: '1.5px solid #c7d2fe', fontSize: 16, outline: 'none', background: '#f8fafc', transition: 'border 0.2s' }; const saveBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; const cancelBtnStyle = { background: '#f3f4f6', color: '#6366f1', border: 'none', borderRadius: 8, padding: '10px 18px', fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.2s' }; -const paginatorWrapperStyle = { marginTop: 24, display: 'flex', gap: 6, alignItems: 'center', justifyContent: 'flex-end' }; -const paginatorBtnStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 18, fontWeight: 700, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageActiveStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', cursor: 'default', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)' }; const addBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; export default EmailTemplatesPage; \ No newline at end of file diff --git a/frontend/src/pages/GroupsPage.js b/frontend/src/pages/GroupsPage.js index 59fb6c4..815f4a4 100644 --- a/frontend/src/pages/GroupsPage.js +++ b/frontend/src/pages/GroupsPage.js @@ -1,5 +1,10 @@ import React, { useState, useEffect } from 'react'; import { useUser } from '../context/UserContext'; +import EditGroupModal from '../modals/EditGroupModal'; +import CreateGroupModal from '../modals/CreateGroupModal'; +import EditSubscriberModal from '../modals/EditSubscriberModal'; +import CreateSubscriberModal from '../modals/CreateSubscriberModal'; +import Paginator from '../components/Paginator'; const PAGE_SIZE = 10; @@ -171,82 +176,33 @@ function GroupsPage() { )} -
-
- - {Array.from({ length: Math.ceil(total / PAGE_SIZE) || 1 }, (_, i) => ( - - ))} - -
-
+ )} {editGroup && ( -
-
- -

Редактировать группу

-
- - -
- - -
-
-
-
+ setEditGroup(null)} + group={editGroup} + loading={editLoading} + onChange={setEditGroup} + onSave={handleEditSave} + /> )} {createGroup && ( -
-
- -

Добавить группу

-
- - -
- - -
-
-
-
+ setCreateGroup(null)} + group={createGroup} + loading={createLoading} + onChange={setCreateGroup} + onSave={handleCreateSave} + /> )} ); @@ -433,96 +389,33 @@ function SubscribersInGroupPage({ group, onBack, token }) { )} -
-
- - {Array.from({ length: Math.ceil(total / PAGE_SIZE) || 1 }, (_, i) => ( - - ))} - -
-
+ )} {editSub && ( -
-
- -

Редактировать подписчика

-
- - - -
- - -
-
-
-
+ setEditSub(null)} + sub={editSub} + loading={editLoading} + onChange={setEditSub} + onSave={handleEditSave} + /> )} {createSub && ( -
-
- -

Добавить подписчика

-
- - - -
- - -
-
-
-
+ setCreateSub(null)} + sub={createSub} + loading={createLoading} + onChange={setCreateSub} + onSave={handleCreateSave} + /> )} ); @@ -539,9 +432,5 @@ const inputStyle = { marginTop: 4, padding: '10px 12px', borderRadius: 8, border const saveBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; const cancelBtnStyle = { background: '#f3f4f6', color: '#6366f1', border: 'none', borderRadius: 8, padding: '10px 18px', fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.2s' }; const addBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; -const paginatorWrapperStyle = { marginTop: 24, display: 'flex', gap: 6, alignItems: 'center', justifyContent: 'flex-end' }; -const paginatorBtnStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 18, fontWeight: 700, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageActiveStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', cursor: 'default', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)' }; export default GroupsPage; \ No newline at end of file diff --git a/frontend/src/pages/SmtpServersPage.js b/frontend/src/pages/SmtpServersPage.js index c2d533e..46f97ac 100644 --- a/frontend/src/pages/SmtpServersPage.js +++ b/frontend/src/pages/SmtpServersPage.js @@ -1,5 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useUser } from '../context/UserContext'; +import EditSmtpModal from '../modals/EditSmtpModal'; +import CreateSmtpModal from '../modals/CreateSmtpModal'; +import Paginator from '../components/Paginator'; const PAGE_SIZE = 10; @@ -178,118 +181,33 @@ function SmtpServersPage() { )} -
-
- - {Array.from({ length: Math.ceil(total / PAGE_SIZE) || 1 }, (_, i) => ( - - ))} - -
-
+ )} {editServer && ( -
-
- -

Редактировать SMTP-сервер

-
- - - - - - - -
- - -
-
-
-
+ setEditServer(null)} + server={editServer} + loading={editLoading} + onChange={setEditServer} + onSave={handleEditSave} + /> )} {createServer && ( -
-
- -

Добавить SMTP-сервер

-
- - - - - - - -
- - -
-
-
-
+ setCreateServer(null)} + server={createServer} + loading={createLoading} + onChange={setCreateServer} + onSave={handleCreateSave} + /> )} ); @@ -305,10 +223,6 @@ const labelStyle = { fontWeight: 500, color: '#374151', fontSize: 15, display: ' const inputStyle = { marginTop: 4, padding: '10px 12px', borderRadius: 8, border: '1.5px solid #c7d2fe', fontSize: 16, outline: 'none', background: '#f8fafc', transition: 'border 0.2s' }; const saveBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; const cancelBtnStyle = { background: '#f3f4f6', color: '#6366f1', border: 'none', borderRadius: 8, padding: '10px 18px', fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.2s' }; -const paginatorWrapperStyle = { marginTop: 24, display: 'flex', gap: 6, alignItems: 'center', justifyContent: 'flex-end' }; -const paginatorBtnStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 18, fontWeight: 700, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageActiveStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', cursor: 'default', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)' }; const addBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; export default SmtpServersPage; \ No newline at end of file diff --git a/frontend/src/pages/UnsubscribedPage.js b/frontend/src/pages/UnsubscribedPage.js index 440d49d..c46a6af 100644 --- a/frontend/src/pages/UnsubscribedPage.js +++ b/frontend/src/pages/UnsubscribedPage.js @@ -1,5 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useUser } from '../context/UserContext'; +import EditUnsubModal from '../modals/EditUnsubModal'; +import CreateUnsubModal from '../modals/CreateUnsubModal'; +import Paginator from '../components/Paginator'; const PAGE_SIZE = 10; @@ -168,82 +171,33 @@ function UnsubscribedPage() { )} -
-
- - {Array.from({ length: Math.ceil(total / PAGE_SIZE) || 1 }, (_, i) => ( - - ))} - -
-
+ )} {editSub && ( -
-
- -

Редактировать запись

-
- - -
- - -
-
-
-
+ setEditSub(null)} + sub={editSub} + loading={editLoading} + onChange={setEditSub} + onSave={handleEditSave} + /> )} {createSub && ( -
-
- -

Добавить запись

-
- - -
- - -
-
-
-
+ setCreateSub(null)} + sub={createSub} + loading={createLoading} + onChange={setCreateSub} + onSave={handleCreateSave} + /> )} ); @@ -260,9 +214,5 @@ const inputStyle = { marginTop: 4, padding: '10px 12px', borderRadius: 8, border const saveBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; const cancelBtnStyle = { background: '#f3f4f6', color: '#6366f1', border: 'none', borderRadius: 8, padding: '10px 18px', fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.2s' }; const addBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; -const paginatorWrapperStyle = { marginTop: 24, display: 'flex', gap: 6, alignItems: 'center', justifyContent: 'flex-end' }; -const paginatorBtnStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 18, fontWeight: 700, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageActiveStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', cursor: 'default', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)' }; export default UnsubscribedPage; \ No newline at end of file diff --git a/frontend/src/pages/UsersPage.js b/frontend/src/pages/UsersPage.js index ce222f3..6766b8d 100644 --- a/frontend/src/pages/UsersPage.js +++ b/frontend/src/pages/UsersPage.js @@ -1,5 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useUser } from '../context/UserContext'; +import EditUserModal from '../modals/EditUserModal'; +import CreateUserModal from '../modals/CreateUserModal'; +import Paginator from '../components/Paginator'; const PAGE_SIZE = 10; @@ -199,99 +202,35 @@ function UsersPage() { )} -
-
- - {Array.from({ length: Math.ceil(usersTotal / PAGE_SIZE) || 1 }, (_, i) => ( - - ))} - -
-
+ )} {editUser && ( -
-
- -

Редактировать пользователя

-
- - - -
- - -
-
-
-
+ setEditUser(null)} + user={editUser} + roles={roles} + loading={editLoading} + onChange={setEditUser} + onSave={handleEditSave} + /> )} {createUser && ( -
-
- -

Добавить пользователя

-
- - - - -
- - -
-
-
-
+ setCreateUser(null)} + user={createUser} + roles={roles} + loading={createLoading} + onChange={setCreateUser} + onSave={handleCreateSave} + /> )} ); @@ -308,11 +247,6 @@ const inputStyle = { marginTop: 4, padding: '10px 12px', borderRadius: 8, border const saveBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; const cancelBtnStyle = { background: '#f3f4f6', color: '#6366f1', border: 'none', borderRadius: 8, padding: '10px 18px', fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.2s' }; -const paginatorWrapperStyle = { marginTop: 24, display: 'flex', gap: 6, alignItems: 'center', justifyContent: 'flex-end' }; -const paginatorBtnStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 18, fontWeight: 700, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageStyle = { border: 'none', background: '#f3f4f6', color: '#6366f1', borderRadius: '50%', width: 36, height: 36, fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.18s, color 0.18s', boxShadow: '0 1px 4px 0 rgba(99,102,241,0.06)' }; -const paginatorPageActiveStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', cursor: 'default', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)' }; - const addBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; export default UsersPage; \ No newline at end of file diff --git a/frontend/src/styles/CampaignModal.module.css b/frontend/src/styles/CampaignModal.module.css new file mode 100644 index 0000000..1936cfc --- /dev/null +++ b/frontend/src/styles/CampaignModal.module.css @@ -0,0 +1,59 @@ +.title { + margin: 0 0 18px 0; + text-align: center; + color: #3730a3; + font-weight: 700; +} +.form { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 260px; +} +.label { + font-weight: 500; + color: #374151; + font-size: 15px; + display: flex; + flex-direction: column; + gap: 4px; +} +.input { + margin-top: 4px; + padding: 10px 12px; + border-radius: 8px; + border: 1.5px solid #c7d2fe; + font-size: 16px; + outline: none; + background: #f8fafc; + transition: border 0.2s; +} +.actions { + display: flex; + gap: 12px; + margin-top: 10px; + justify-content: flex-end; +} +.saveBtn { + background: linear-gradient(90deg, #6366f1 0%, #06b6d4 100%); + color: #fff; + border: none; + border-radius: 8px; + padding: 10px 22px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px 0 rgba(99,102,241,0.10); + transition: background 0.2s; +} +.cancelBtn { + background: #f3f4f6; + color: #6366f1; + border: none; + border-radius: 8px; + padding: 10px 18px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} \ No newline at end of file diff --git a/frontend/src/styles/GroupModal.module.css b/frontend/src/styles/GroupModal.module.css new file mode 100644 index 0000000..1936cfc --- /dev/null +++ b/frontend/src/styles/GroupModal.module.css @@ -0,0 +1,59 @@ +.title { + margin: 0 0 18px 0; + text-align: center; + color: #3730a3; + font-weight: 700; +} +.form { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 260px; +} +.label { + font-weight: 500; + color: #374151; + font-size: 15px; + display: flex; + flex-direction: column; + gap: 4px; +} +.input { + margin-top: 4px; + padding: 10px 12px; + border-radius: 8px; + border: 1.5px solid #c7d2fe; + font-size: 16px; + outline: none; + background: #f8fafc; + transition: border 0.2s; +} +.actions { + display: flex; + gap: 12px; + margin-top: 10px; + justify-content: flex-end; +} +.saveBtn { + background: linear-gradient(90deg, #6366f1 0%, #06b6d4 100%); + color: #fff; + border: none; + border-radius: 8px; + padding: 10px 22px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px 0 rgba(99,102,241,0.10); + transition: background 0.2s; +} +.cancelBtn { + background: #f3f4f6; + color: #6366f1; + border: none; + border-radius: 8px; + padding: 10px 18px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} \ No newline at end of file diff --git a/frontend/src/styles/Modal.module.css b/frontend/src/styles/Modal.module.css new file mode 100644 index 0000000..822ebe0 --- /dev/null +++ b/frontend/src/styles/Modal.module.css @@ -0,0 +1,44 @@ +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0,0,0,0.18); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal { + background: #fff; + border-radius: 14px; + padding: 32px 28px 24px 28px; + min-width: 340px; + box-shadow: 0 12px 48px 0 rgba(31,38,135,0.22); + position: relative; + animation: modalIn 0.18s cubic-bezier(.4,1.3,.6,1); +} + +.closeBtn { + position: absolute; + top: 12px; + right: 16px; + background: none; + border: none; + font-size: 26px; + color: #6366f1; + cursor: pointer; + font-weight: 700; + line-height: 1; + padding: 0; + z-index: 2; + opacity: 0.8; + transition: opacity 0.2s; +} + +@keyframes modalIn { + from { transform: translateY(40px) scale(0.98); opacity: 0; } + to { transform: none; opacity: 1; } +} \ No newline at end of file diff --git a/frontend/src/styles/Paginator.module.css b/frontend/src/styles/Paginator.module.css new file mode 100644 index 0000000..26d271a --- /dev/null +++ b/frontend/src/styles/Paginator.module.css @@ -0,0 +1,39 @@ +.wrapper { + margin-top: 24px; + display: flex; + gap: 6px; + align-items: center; + justify-content: flex-end; +} +.btn { + border: none; + background: #f3f4f6; + color: #6366f1; + border-radius: 50%; + width: 36px; + height: 36px; + font-size: 18px; + font-weight: 700; + cursor: pointer; + transition: background 0.18s, color 0.18s; + box-shadow: 0 1px 4px 0 rgba(99,102,241,0.06); +} +.page { + border: none; + background: #f3f4f6; + color: #6366f1; + border-radius: 50%; + width: 36px; + height: 36px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.18s, color 0.18s; + box-shadow: 0 1px 4px 0 rgba(99,102,241,0.06); +} +.pageActive { + background: linear-gradient(90deg, #6366f1 0%, #06b6d4 100%); + color: #fff; + cursor: default; + box-shadow: 0 2px 8px 0 rgba(99,102,241,0.10); +} \ No newline at end of file diff --git a/frontend/src/styles/SmtpModal.module.css b/frontend/src/styles/SmtpModal.module.css new file mode 100644 index 0000000..1936cfc --- /dev/null +++ b/frontend/src/styles/SmtpModal.module.css @@ -0,0 +1,59 @@ +.title { + margin: 0 0 18px 0; + text-align: center; + color: #3730a3; + font-weight: 700; +} +.form { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 260px; +} +.label { + font-weight: 500; + color: #374151; + font-size: 15px; + display: flex; + flex-direction: column; + gap: 4px; +} +.input { + margin-top: 4px; + padding: 10px 12px; + border-radius: 8px; + border: 1.5px solid #c7d2fe; + font-size: 16px; + outline: none; + background: #f8fafc; + transition: border 0.2s; +} +.actions { + display: flex; + gap: 12px; + margin-top: 10px; + justify-content: flex-end; +} +.saveBtn { + background: linear-gradient(90deg, #6366f1 0%, #06b6d4 100%); + color: #fff; + border: none; + border-radius: 8px; + padding: 10px 22px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px 0 rgba(99,102,241,0.10); + transition: background 0.2s; +} +.cancelBtn { + background: #f3f4f6; + color: #6366f1; + border: none; + border-radius: 8px; + padding: 10px 18px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} \ No newline at end of file diff --git a/frontend/src/styles/SubscriberModal.module.css b/frontend/src/styles/SubscriberModal.module.css new file mode 100644 index 0000000..1936cfc --- /dev/null +++ b/frontend/src/styles/SubscriberModal.module.css @@ -0,0 +1,59 @@ +.title { + margin: 0 0 18px 0; + text-align: center; + color: #3730a3; + font-weight: 700; +} +.form { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 260px; +} +.label { + font-weight: 500; + color: #374151; + font-size: 15px; + display: flex; + flex-direction: column; + gap: 4px; +} +.input { + margin-top: 4px; + padding: 10px 12px; + border-radius: 8px; + border: 1.5px solid #c7d2fe; + font-size: 16px; + outline: none; + background: #f8fafc; + transition: border 0.2s; +} +.actions { + display: flex; + gap: 12px; + margin-top: 10px; + justify-content: flex-end; +} +.saveBtn { + background: linear-gradient(90deg, #6366f1 0%, #06b6d4 100%); + color: #fff; + border: none; + border-radius: 8px; + padding: 10px 22px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px 0 rgba(99,102,241,0.10); + transition: background 0.2s; +} +.cancelBtn { + background: #f3f4f6; + color: #6366f1; + border: none; + border-radius: 8px; + padding: 10px 18px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} \ No newline at end of file diff --git a/frontend/src/styles/TemplateModal.module.css b/frontend/src/styles/TemplateModal.module.css new file mode 100644 index 0000000..1936cfc --- /dev/null +++ b/frontend/src/styles/TemplateModal.module.css @@ -0,0 +1,59 @@ +.title { + margin: 0 0 18px 0; + text-align: center; + color: #3730a3; + font-weight: 700; +} +.form { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 260px; +} +.label { + font-weight: 500; + color: #374151; + font-size: 15px; + display: flex; + flex-direction: column; + gap: 4px; +} +.input { + margin-top: 4px; + padding: 10px 12px; + border-radius: 8px; + border: 1.5px solid #c7d2fe; + font-size: 16px; + outline: none; + background: #f8fafc; + transition: border 0.2s; +} +.actions { + display: flex; + gap: 12px; + margin-top: 10px; + justify-content: flex-end; +} +.saveBtn { + background: linear-gradient(90deg, #6366f1 0%, #06b6d4 100%); + color: #fff; + border: none; + border-radius: 8px; + padding: 10px 22px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px 0 rgba(99,102,241,0.10); + transition: background 0.2s; +} +.cancelBtn { + background: #f3f4f6; + color: #6366f1; + border: none; + border-radius: 8px; + padding: 10px 18px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} \ No newline at end of file diff --git a/frontend/src/styles/UnsubModal.module.css b/frontend/src/styles/UnsubModal.module.css new file mode 100644 index 0000000..1936cfc --- /dev/null +++ b/frontend/src/styles/UnsubModal.module.css @@ -0,0 +1,59 @@ +.title { + margin: 0 0 18px 0; + text-align: center; + color: #3730a3; + font-weight: 700; +} +.form { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 260px; +} +.label { + font-weight: 500; + color: #374151; + font-size: 15px; + display: flex; + flex-direction: column; + gap: 4px; +} +.input { + margin-top: 4px; + padding: 10px 12px; + border-radius: 8px; + border: 1.5px solid #c7d2fe; + font-size: 16px; + outline: none; + background: #f8fafc; + transition: border 0.2s; +} +.actions { + display: flex; + gap: 12px; + margin-top: 10px; + justify-content: flex-end; +} +.saveBtn { + background: linear-gradient(90deg, #6366f1 0%, #06b6d4 100%); + color: #fff; + border: none; + border-radius: 8px; + padding: 10px 22px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px 0 rgba(99,102,241,0.10); + transition: background 0.2s; +} +.cancelBtn { + background: #f3f4f6; + color: #6366f1; + border: none; + border-radius: 8px; + padding: 10px 18px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} \ No newline at end of file diff --git a/frontend/src/styles/UserModal.module.css b/frontend/src/styles/UserModal.module.css new file mode 100644 index 0000000..1936cfc --- /dev/null +++ b/frontend/src/styles/UserModal.module.css @@ -0,0 +1,59 @@ +.title { + margin: 0 0 18px 0; + text-align: center; + color: #3730a3; + font-weight: 700; +} +.form { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 260px; +} +.label { + font-weight: 500; + color: #374151; + font-size: 15px; + display: flex; + flex-direction: column; + gap: 4px; +} +.input { + margin-top: 4px; + padding: 10px 12px; + border-radius: 8px; + border: 1.5px solid #c7d2fe; + font-size: 16px; + outline: none; + background: #f8fafc; + transition: border 0.2s; +} +.actions { + display: flex; + gap: 12px; + margin-top: 10px; + justify-content: flex-end; +} +.saveBtn { + background: linear-gradient(90deg, #6366f1 0%, #06b6d4 100%); + color: #fff; + border: none; + border-radius: 8px; + padding: 10px 22px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px 0 rgba(99,102,241,0.10); + transition: background 0.2s; +} +.cancelBtn { + background: #f3f4f6; + color: #6366f1; + border: none; + border-radius: 8px; + padding: 10px 18px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} \ No newline at end of file