Refactor forms to disable browser suggestions and improve user experience

- Integrated `noBrowserSuggestProps` across multiple pages to prevent browser autocomplete suggestions on input fields.
- Updated form elements in `AccountsPage`, `BalancePage`, `PaymentsPage`, `ProvidersPage`, `ReportsPage`, `ResourcesPage`, `SettingsPage`, `TariffsPage`, `VpsPage`, and `ProjectSuggestInput` to enhance usability.
- Ensured consistent application of `autoComplete="off"` for select elements to improve form behavior.
This commit is contained in:
Denozordec
2026-03-20 23:00:29 +07:00
parent 3fe2d2bc61
commit 994dde2314
11 changed files with 149 additions and 123 deletions
+2 -1
View File
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { IconFolder } from '@tabler/icons-react'
import { fetchProjectSuggestions } from '../lib/api'
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
function highlightMatch(text, query) {
const q = (query || '').trim().toLowerCase()
@@ -141,6 +142,7 @@ export function ProjectSuggestInput({
return (
<div ref={wrapRef} className="position-relative project-suggest-wrap">
<input
{...noBrowserSuggestProps}
id={id}
type="text"
className={className}
@@ -153,7 +155,6 @@ export function ProjectSuggestInput({
onKeyDown={onKeyDown}
placeholder={placeholder}
disabled={disabled}
autoComplete="off"
spellCheck={false}
aria-label={ariaLabel}
aria-expanded={showPanel}
+17
View File
@@ -0,0 +1,17 @@
/**
* Отключает подсказки автозаполнения браузера и часто используемые хуки менеджеров паролей
* на произвольных текстовых полях (адреса, проекты, заметки и т.д.).
* Для полей пароля API используйте {@link passwordCredentialInputProps}.
*/
export const noBrowserSuggestProps = Object.freeze({
autoComplete: 'off',
'data-lpignore': 'true',
'data-1p-ignore': 'true',
'data-bwignore': 'true',
'data-form-type': 'other',
})
/** Поля пароля/секрета: не отключаем менеджеры паролей, только автоподстановку формы. */
export const passwordCredentialInputProps = Object.freeze({
autoComplete: 'new-password',
})
+12 -11
View File
@@ -9,6 +9,7 @@ import { SyncLogTable } from '../components/SyncLogTable'
import { PageHeader } from '../components/PageHeader'
import { ConvertedAmount } from '../components/ConvertedAmount'
import { syncAccount, testApiConnection, fetchAccountBalance, fetchSyncStatus } from '../lib/api'
import { noBrowserSuggestProps, passwordCredentialInputProps } from '../lib/noBrowserSuggestProps'
import { IconRefresh, IconPlugConnected } from '@tabler/icons-react'
const emptyForm = {
@@ -382,10 +383,10 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
}}
size="modal-md"
>
<form onSubmit={onSubmit} className="row g-3">
<form autoComplete="off" onSubmit={onSubmit} className="row g-3">
<div className="col-12">
<label className="form-label">Хостер</label>
<select
<select autoComplete="off"
className="form-select"
value={form.providerId}
onChange={(e) => setForm((prev) => ({ ...prev, providerId: e.target.value }))}
@@ -401,7 +402,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12">
<label className="form-label">Имя / Псевдоним аккаунта</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.name}
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
@@ -410,7 +411,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12">
<label className="form-label">Ссылка на панель управления</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
placeholder="https://..."
value={form.panelUrl}
@@ -419,7 +420,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Валюта</label>
<select
<select autoComplete="off"
className="form-select"
value={form.currency}
onChange={(e) => setForm((prev) => ({ ...prev, currency: e.target.value }))}
@@ -431,7 +432,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Режим списания</label>
<select
<select autoComplete="off"
className="form-select"
value={form.billingMode}
onChange={(e) => setForm((prev) => ({ ...prev, billingMode: e.target.value }))}
@@ -446,7 +447,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12">
<label className="form-label">Тип API</label>
<select
<select autoComplete="off"
className="form-select"
value={form.apiType}
onChange={(e) => setForm((prev) => ({ ...prev, apiType: e.target.value, apiBaseUrl: '', apiLogin: '', apiPassword: '' }))}
@@ -459,7 +460,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
<>
<div className="col-12">
<label className="form-label">URL API BILLmanager</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
placeholder="https://bill.example.com:1500/billmgr"
value={form.apiBaseUrl}
@@ -468,7 +469,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Логин</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
placeholder="admin"
value={form.apiLogin}
@@ -477,7 +478,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Пароль</label>
<input
<input {...passwordCredentialInputProps}
type="password"
className="form-control"
placeholder={editingAccount?.apiCredentialsSet ? 'Оставьте пустым, чтобы не менять' : ''}
@@ -511,7 +512,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
) : null}
<div className="col-12">
<label className="form-label">Комментарий</label>
<textarea
<textarea {...noBrowserSuggestProps}
className="form-control"
value={form.notes}
onChange={(e) => setForm((prev) => ({ ...prev, notes: e.target.value }))}
+9 -8
View File
@@ -7,6 +7,7 @@ import { UiModal } from '../components/UiModal'
import { EmptyState } from '../components/EmptyState'
import { PageHeader } from '../components/PageHeader'
import { ConvertedAmount } from '../components/ConvertedAmount'
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
const emptyForm = {
type: 'daily_debit',
@@ -191,10 +192,10 @@ export function BalancePage({ db, actions, settings, ratesData }) {
}}
size="modal-md"
>
<form className="row g-3" onSubmit={onSubmit}>
<form className="row g-3" autoComplete="off" onSubmit={onSubmit}>
<div className="col-12">
<label className="form-label">Тип списания</label>
<select
<select autoComplete="off"
className="form-select"
value={form.type}
onChange={(e) => setForm((prev) => ({ ...prev, type: e.target.value }))}
@@ -205,7 +206,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12">
<label className="form-label">Аккаунт</label>
<select
<select autoComplete="off"
className="form-select"
value={form.providerAccountId}
onChange={(e) =>
@@ -222,7 +223,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12">
<label className="form-label">VPS (необязательно)</label>
<select
<select autoComplete="off"
className="form-select"
value={form.vpsId}
onChange={(e) => setForm((prev) => ({ ...prev, vpsId: e.target.value }))}
@@ -237,7 +238,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Дата</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
type="date"
value={form.date}
@@ -246,7 +247,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Валюта</label>
<select
<select autoComplete="off"
className="form-select"
value={form.currency}
onChange={(e) => setForm((prev) => ({ ...prev, currency: e.target.value }))}
@@ -258,7 +259,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12">
<label className="form-label">Сумма</label>
<input
<input {...noBrowserSuggestProps}
type="number"
min="0.01"
step="0.01"
@@ -269,7 +270,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12">
<label className="form-label">Комментарий</label>
<textarea
<textarea {...noBrowserSuggestProps}
className="form-control"
value={form.note}
onChange={(e) => setForm((prev) => ({ ...prev, note: e.target.value }))}
+9 -8
View File
@@ -5,6 +5,7 @@ import { UiModal } from '../components/UiModal'
import { EmptyState } from '../components/EmptyState'
import { PageHeader } from '../components/PageHeader'
import { ConvertedAmount } from '../components/ConvertedAmount'
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
const emptyForm = {
type: 'direct_vps_payment',
@@ -190,10 +191,10 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
}}
size="modal-md"
>
<form className="row g-3" onSubmit={onSubmit}>
<form className="row g-3" autoComplete="off" onSubmit={onSubmit}>
<div className="col-12">
<label className="form-label">Тип</label>
<select
<select autoComplete="off"
className="form-select"
value={form.type}
onChange={(e) =>
@@ -210,7 +211,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12">
<label className="form-label">Аккаунт хостера</label>
<select
<select autoComplete="off"
className="form-select"
value={form.providerAccountId}
onChange={(e) =>
@@ -233,7 +234,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
{form.type === 'direct_vps_payment' ? (
<div className="col-12">
<label className="form-label">VPS</label>
<select
<select autoComplete="off"
className="form-select"
value={form.vpsId}
onChange={(e) => setForm((prev) => ({ ...prev, vpsId: e.target.value }))}
@@ -250,7 +251,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
) : null}
<div className="col-12 col-sm-6">
<label className="form-label">Дата</label>
<input
<input {...noBrowserSuggestProps}
type="date"
className="form-control"
value={form.date}
@@ -260,7 +261,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Валюта</label>
<select
<select autoComplete="off"
className="form-select"
value={form.currency}
onChange={(e) => setForm((prev) => ({ ...prev, currency: e.target.value }))}
@@ -272,7 +273,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12">
<label className="form-label">Сумма</label>
<input
<input {...noBrowserSuggestProps}
type="number"
step="0.01"
min="0.01"
@@ -284,7 +285,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12">
<label className="form-label">Комментарий</label>
<textarea
<textarea {...noBrowserSuggestProps}
className="form-control"
value={form.note}
onChange={(e) => setForm((prev) => ({ ...prev, note: e.target.value }))}
+9 -8
View File
@@ -3,6 +3,7 @@ import { UiModal } from '../components/UiModal'
import { EmptyState } from '../components/EmptyState'
import { PageHeader } from '../components/PageHeader'
import { faviconUrlFromWebsite } from '../lib/utils'
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
const emptyForm = {
name: '',
@@ -146,10 +147,10 @@ export function ProvidersPage({ db, actions }) {
}}
size="modal-md"
>
<form onSubmit={onSubmit} className="row g-3">
<form autoComplete="off" onSubmit={onSubmit} className="row g-3">
<div className="col-12">
<label className="form-label">Название</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.name}
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
@@ -158,7 +159,7 @@ export function ProvidersPage({ db, actions }) {
</div>
<div className="col-12">
<label className="form-label">Сайт</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.website}
onChange={(e) => setForm((prev) => ({ ...prev, website: e.target.value }))}
@@ -166,7 +167,7 @@ export function ProvidersPage({ db, actions }) {
</div>
<div className="col-12">
<label className="form-label">Контакт</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.contact}
onChange={(e) => setForm((prev) => ({ ...prev, contact: e.target.value }))}
@@ -174,7 +175,7 @@ export function ProvidersPage({ db, actions }) {
</div>
<div className="col-12 col-sm-4">
<label className="form-label">Валюта приёма платежей</label>
<select
<select autoComplete="off"
className="form-select"
value={form.baseCurrency}
onChange={(e) => setForm((prev) => ({ ...prev, baseCurrency: e.target.value }))}
@@ -187,7 +188,7 @@ export function ProvidersPage({ db, actions }) {
</div>
<div className="col-12 col-sm-4">
<label className="form-label">Курс USD</label>
<input
<input {...noBrowserSuggestProps}
type="number"
min="0"
step="0.0001"
@@ -199,7 +200,7 @@ export function ProvidersPage({ db, actions }) {
</div>
<div className="col-12 col-sm-4">
<label className="form-label">Курс EUR</label>
<input
<input {...noBrowserSuggestProps}
type="number"
min="0"
step="0.0001"
@@ -211,7 +212,7 @@ export function ProvidersPage({ db, actions }) {
</div>
<div className="col-12">
<label className="form-label">Заметки</label>
<textarea
<textarea {...noBrowserSuggestProps}
className="form-control"
value={form.notes}
onChange={(e) => setForm((prev) => ({ ...prev, notes: e.target.value }))}
+8 -7
View File
@@ -9,6 +9,7 @@ import {
import { ConvertedAmount } from '../components/ConvertedAmount'
import { EmptyState } from '../components/EmptyState'
import { PageHeader } from '../components/PageHeader'
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
function isDateInRange(dateStr, dateFrom, dateTo) {
if (!dateStr) return false
@@ -157,7 +158,7 @@ export function ReportsPage({ db, settings, ratesData }) {
<div className="row g-2 align-items-end">
<div className="col-12 col-md-6 col-xl-3">
<label className="form-label">Хостер</label>
<select
<select autoComplete="off"
className="form-select"
value={filters.providerId}
onChange={(e) =>
@@ -178,7 +179,7 @@ export function ReportsPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-3">
<label className="form-label">Аккаунт</label>
<select
<select autoComplete="off"
className="form-select"
value={filters.providerAccountId}
onChange={(e) =>
@@ -195,7 +196,7 @@ export function ReportsPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-3">
<label className="form-label">Проект / пул</label>
<select
<select autoComplete="off"
className="form-select"
value={filters.project}
onChange={(e) => setFilters((prev) => ({ ...prev, project: e.target.value }))}
@@ -211,7 +212,7 @@ export function ReportsPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-3">
<label className="form-label">Страна</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={filters.country}
onChange={(e) => setFilters((prev) => ({ ...prev, country: e.target.value }))}
@@ -220,7 +221,7 @@ export function ReportsPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-2">
<label className="form-label">Период (YYYY-MM)</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={filters.month}
onChange={(e) =>
@@ -236,7 +237,7 @@ export function ReportsPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-2">
<label className="form-label">Дата с</label>
<input
<input {...noBrowserSuggestProps}
type="date"
className="form-control"
value={filters.dateFrom}
@@ -251,7 +252,7 @@ export function ReportsPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-2">
<label className="form-label">Дата по</label>
<input
<input {...noBrowserSuggestProps}
type="date"
className="form-control"
value={filters.dateTo}
+8 -7
View File
@@ -5,6 +5,7 @@ import {
} from '../lib/utils'
import { EmptyState } from '../components/EmptyState'
import { PageHeader } from '../components/PageHeader'
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
function vpsMonthlyEstimateInBase(item, baseCurrency, ratesData) {
if (item.status !== 'active') return 0
@@ -188,7 +189,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
<div className="row g-2">
<div className="col-12 col-md-6 col-xl-3">
<label className="form-label">Группировать по</label>
<select
<select autoComplete="off"
className="form-select"
value={groupBy}
onChange={(e) => setGroupBy(e.target.value)}
@@ -201,7 +202,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-3">
<label className="form-label">Хостер</label>
<select
<select autoComplete="off"
className="form-select"
value={filters.providerId}
onChange={(e) =>
@@ -222,7 +223,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-3">
<label className="form-label">Аккаунт</label>
<select
<select autoComplete="off"
className="form-select"
value={filters.providerAccountId}
onChange={(e) =>
@@ -239,7 +240,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-3">
<label className="form-label">Проект (фильтр)</label>
<select
<select autoComplete="off"
className="form-select"
value={filters.project}
onChange={(e) => setFilters((prev) => ({ ...prev, project: e.target.value }))}
@@ -255,7 +256,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-3">
<label className="form-label">Страна</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={filters.country}
onChange={(e) => setFilters((prev) => ({ ...prev, country: e.target.value }))}
@@ -264,7 +265,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
</div>
<div className="col-12 col-md-6 col-xl-3">
<label className="form-label">Датацентр</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={filters.datacenter}
onChange={(e) =>
@@ -276,7 +277,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
{customFields.map((f) => (
<div key={f.key} className="col-12 col-md-6 col-xl-3">
<label className="form-label">{f.label}</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={filters[f.key] ?? ''}
onChange={(e) =>
+16 -16
View File
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
import { IconPlus, IconSend, IconTrash } from '@tabler/icons-react'
import { PageHeader } from '../components/PageHeader'
import { sendTelegramTestNotification } from '../lib/api'
import { noBrowserSuggestProps, passwordCredentialInputProps } from '../lib/noBrowserSuggestProps'
const defaultSettings = {
baseCurrency: 'RUB',
@@ -139,10 +140,10 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
<h3 className="card-title">Настройки валют</h3>
</div>
<div className="card-body">
<form className="row g-3" onSubmit={onSubmit}>
<form className="row g-3" autoComplete="off" onSubmit={onSubmit}>
<div className="col-12 col-md-6">
<label className="form-label">Валюта отображения</label>
<select
<select autoComplete="off"
className="form-select"
value={form.baseCurrency}
onChange={(e) => setForm((prev) => ({ ...prev, baseCurrency: e.target.value }))}
@@ -157,7 +158,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
<div className="col-12 col-md-6">
<label className="form-label">Автоконвертация</label>
<label className="form-check">
<input
<input {...noBrowserSuggestProps}
className="form-check-input"
type="checkbox"
checked={form.autoConvert}
@@ -168,7 +169,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
</div>
<div className="col-12">
<label className="form-label">Ссылка на курсы валют</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.ratesUrl}
onChange={(e) => setForm((prev) => ({ ...prev, ratesUrl: e.target.value }))}
@@ -201,7 +202,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
Текстовые поля для расширенного режима просмотра списка VPS. Отображаются как колонки в таблице и в форме редактирования.
</p>
<div className="d-flex gap-2 mb-3">
<input
<input {...noBrowserSuggestProps}
type="text"
className="form-control"
placeholder="Название поля (например: Контакт, ID заказа)"
@@ -249,10 +250,10 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
Периодическая синхронизация данных из BILLmanager для аккаунтов с настроенным API.
Два независимых интервала: VPS и платежи обновляются чаще, тарифы реже.
</p>
<form className="row g-3" onSubmit={onSyncSettingsSubmit}>
<form className="row g-3" autoComplete="off" onSubmit={onSyncSettingsSubmit}>
<div className="col-12">
<label className="form-check">
<input
<input {...noBrowserSuggestProps}
className="form-check-input"
type="checkbox"
checked={form.syncEnabled}
@@ -263,7 +264,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
</div>
<div className="col-12 col-md-6">
<label className="form-label">Интервал VPS и платежей (минуты)</label>
<input
<input {...noBrowserSuggestProps}
type="number"
min="15"
className="form-control"
@@ -274,7 +275,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
</div>
<div className="col-12 col-md-6">
<label className="form-label">Интервал тарифов (минуты)</label>
<input
<input {...noBrowserSuggestProps}
type="number"
min="60"
className="form-control"
@@ -305,10 +306,10 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
<p className="text-secondary small mb-3">
Уведомления отправляются в Telegram при периодической синхронизации. Каждый тип можно включить или отключить отдельно.
</p>
<form className="row g-3" onSubmit={onTelegramSubmit}>
<form className="row g-3" autoComplete="off" onSubmit={onTelegramSubmit}>
<div className="col-12">
<label className="form-label">Токен бота</label>
<input
<input {...passwordCredentialInputProps}
type="password"
className="form-control"
value={form.telegramBotToken}
@@ -317,12 +318,11 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
setTelegramTokenEdited(true)
}}
placeholder={current.telegramBotTokenSet ? '••••••••' : 'Токен от @BotFather'}
autoComplete="off"
/>
</div>
<div className="col-12">
<label className="form-label">Chat ID (SuperGroup)</label>
<input
<input {...noBrowserSuggestProps}
type="text"
className="form-control"
value={form.telegramChatId}
@@ -335,7 +335,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
</div>
<div className="col-12">
<label className="form-label">ID топика (цепочки сообщений)</label>
<input
<input {...noBrowserSuggestProps}
type="text"
className="form-control"
value={form.telegramMessageThreadId}
@@ -348,7 +348,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
</div>
<div className="col-12">
<label className="form-check">
<input
<input {...noBrowserSuggestProps}
className="form-check-input"
type="checkbox"
checked={form.notifyPaymentExpiryEnabled}
@@ -359,7 +359,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
</div>
<div className="col-12">
<label className="form-check">
<input
<input {...noBrowserSuggestProps}
className="form-check-input"
type="checkbox"
checked={form.notifyNewTariffsEnabled}
+6 -5
View File
@@ -11,6 +11,7 @@ import {
import { syncAccount } from '../lib/api'
import { EmptyState } from '../components/EmptyState'
import { PageHeader } from '../components/PageHeader'
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
const SORT_COLUMNS = ['name', 'vcpu', 'ramGb', 'diskGb', 'diskType', 'virtualization', 'channel', 'country', 'location', 'price']
@@ -249,7 +250,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
<span className="input-icon-addon">
<IconSearch size={16} />
</span>
<input
<input {...noBrowserSuggestProps}
className="form-control"
placeholder="Поиск по названию, ресурсам..."
value={filters.search}
@@ -260,7 +261,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
</div>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.providerId}
onChange={(e) =>
@@ -280,7 +281,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
</select>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.providerAccountId}
onChange={(e) =>
@@ -299,7 +300,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
</select>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.country}
onChange={(e) =>
@@ -315,7 +316,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
</select>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.orderAvailable}
onChange={(e) =>
+53 -52
View File
@@ -24,6 +24,7 @@ import { ConvertedAmount } from '../components/ConvertedAmount'
import { EmptyState } from '../components/EmptyState'
import { PageHeader } from '../components/PageHeader'
import { ProjectSuggestInput } from '../components/ProjectSuggestInput'
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
const emptyForm = {
ip: '',
@@ -622,7 +623,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<span className="input-icon-addon">
<IconSearch size={16} />
</span>
<input
<input {...noBrowserSuggestProps}
className="form-control"
placeholder="Поиск IP / DNS"
value={filters.search}
@@ -636,7 +637,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.project}
onChange={(e) => setFilters((prev) => ({ ...prev, project: e.target.value }))}
@@ -654,7 +655,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
{showAdvancedFilters ? (
<>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.providerId}
onChange={(e) =>
@@ -674,7 +675,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</select>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.providerAccountId}
onChange={(e) =>
@@ -693,7 +694,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</select>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.status}
onChange={(e) => setFilters((prev) => ({ ...prev, status: e.target.value }))}
@@ -705,7 +706,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</select>
</div>
<div className="col-xl-3 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.environment}
onChange={(e) => setFilters((prev) => ({ ...prev, environment: e.target.value }))}
@@ -718,7 +719,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</select>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.tariffType}
onChange={(e) => setFilters((prev) => ({ ...prev, tariffType: e.target.value }))}
@@ -729,7 +730,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</select>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.monitoring}
onChange={(e) => setFilters((prev) => ({ ...prev, monitoring: e.target.value }))}
@@ -740,7 +741,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</select>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<select
<select autoComplete="off"
className="form-select"
value={filters.backup}
onChange={(e) => setFilters((prev) => ({ ...prev, backup: e.target.value }))}
@@ -755,7 +756,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<span className="input-icon-addon">
<IconFilter size={16} />
</span>
<input
<input {...noBrowserSuggestProps}
className="form-control"
placeholder="Страна"
value={filters.country}
@@ -773,7 +774,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<span className="input-icon-addon">
<IconMapPin size={16} />
</span>
<input
<input {...noBrowserSuggestProps}
className="form-control"
placeholder="Город"
value={filters.city}
@@ -787,7 +788,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<input
<input {...noBrowserSuggestProps}
className="form-control"
placeholder="ДЦ"
value={filters.datacenter}
@@ -795,7 +796,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
/>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<input
<input {...noBrowserSuggestProps}
type="number"
min="0"
className="form-control"
@@ -805,7 +806,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
/>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<input
<input {...noBrowserSuggestProps}
type="number"
min="0"
className="form-control"
@@ -815,7 +816,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
/>
</div>
<div className="col-xl-2 col-lg-4 col-md-6">
<input
<input {...noBrowserSuggestProps}
type="number"
min="0"
className="form-control"
@@ -826,7 +827,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
{customFields.map((f) => (
<div key={f.key} className="col-xl-2 col-lg-4 col-md-6">
<input
<input {...noBrowserSuggestProps}
className="form-control"
placeholder={f.label}
value={filters[f.key] ?? ''}
@@ -840,7 +841,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<div className="row g-2 mt-2 align-items-center flex-wrap">
<div className="col-12 col-sm-auto">
<label className="form-check mb-0">
<input
<input {...noBrowserSuggestProps}
type="checkbox"
className="form-check-input"
checked={filters.groupByProject}
@@ -853,7 +854,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-auto">
<label className="form-check mb-0">
<input
<input {...noBrowserSuggestProps}
type="checkbox"
className="form-check-input"
checked={filters.tableCompact}
@@ -865,7 +866,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</label>
</div>
<div className="col-12 col-sm-auto">
<select
<select autoComplete="off"
className="form-select form-select-sm"
style={{ minWidth: '11rem' }}
defaultValue=""
@@ -1048,7 +1049,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<thead>
<tr>
<th style={{ width: 40 }}>
<input
<input {...noBrowserSuggestProps}
type="checkbox"
className="form-check-input"
checked={filteredVps.length > 0 && selectedIds.size === filteredVps.length}
@@ -1119,7 +1120,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
return (
<tr key={item.id}>
<td>
<input
<input {...noBrowserSuggestProps}
type="checkbox"
className="form-check-input"
checked={selectedIds.has(item.id)}
@@ -1316,13 +1317,13 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
}
>
<form id="vps-form" onSubmit={onSubmit} className="vps-modal-form">
<form id="vps-form" autoComplete="off" onSubmit={onSubmit} className="vps-modal-form">
<section className="vps-form-section">
<h6 className="vps-form-section-title">Сеть</h6>
<div className="row g-3">
<div className="col-12 col-sm-6">
<label className="form-label">IP</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.ip}
onChange={(e) => setForm((prev) => ({ ...prev, ip: e.target.value }))}
@@ -1332,7 +1333,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">DNS</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.dns}
onChange={(e) => setForm((prev) => ({ ...prev, dns: e.target.value }))}
@@ -1341,7 +1342,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">IPv6</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.ipv6}
onChange={(e) => setForm((prev) => ({ ...prev, ipv6: e.target.value }))}
@@ -1350,7 +1351,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Дополнительные IP</label>
<textarea
<textarea {...noBrowserSuggestProps}
className="form-control"
rows={2}
value={form.additionalIpsText}
@@ -1366,7 +1367,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<div className="row g-3">
<div className="col-12 col-sm-6">
<label className="form-label">Хостер</label>
<select
<select autoComplete="off"
className="form-select"
value={form.providerId}
onChange={(e) =>
@@ -1388,7 +1389,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Аккаунт хостера</label>
<select
<select autoComplete="off"
className="form-select"
value={form.providerAccountId}
onChange={(e) => setForm((prev) => ({ ...prev, providerAccountId: e.target.value }))}
@@ -1404,7 +1405,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Страна</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.country}
onChange={(e) => setForm((prev) => ({ ...prev, country: e.target.value }))}
@@ -1413,7 +1414,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Город</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.city}
onChange={(e) => setForm((prev) => ({ ...prev, city: e.target.value }))}
@@ -1422,7 +1423,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">ДЦ / зона</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.datacenter}
onChange={(e) => setForm((prev) => ({ ...prev, datacenter: e.target.value }))}
@@ -1431,7 +1432,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">ОС</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.os}
onChange={(e) => setForm((prev) => ({ ...prev, os: e.target.value }))}
@@ -1446,7 +1447,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<div className="row g-3">
<div className="col-12 col-sm-6 col-md-4">
<label className="form-label">vCPU</label>
<input
<input {...noBrowserSuggestProps}
type="number"
min="1"
step="1"
@@ -1458,7 +1459,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6 col-md-4">
<label className="form-label">RAM (GB)</label>
<input
<input {...noBrowserSuggestProps}
type="number"
min="1"
step="1"
@@ -1470,7 +1471,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6 col-md-4">
<label className="form-label">Диск (GB)</label>
<input
<input {...noBrowserSuggestProps}
type="number"
min="1"
step="1"
@@ -1482,7 +1483,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6 col-md-4">
<label className="form-label">Тип диска</label>
<select
<select autoComplete="off"
className="form-select"
value={form.diskType}
onChange={(e) => setForm((prev) => ({ ...prev, diskType: e.target.value }))}
@@ -1494,7 +1495,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6 col-md-4">
<label className="form-label">Виртуализация</label>
<select
<select autoComplete="off"
className="form-select"
value={form.virtualization}
onChange={(e) => setForm((prev) => ({ ...prev, virtualization: e.target.value }))}
@@ -1508,7 +1509,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6 col-md-4">
<label className="form-label">Трафик (TB)</label>
<input
<input {...noBrowserSuggestProps}
type="number"
min="0"
step="0.1"
@@ -1526,7 +1527,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<div className="row g-3">
<div className="col-12 col-sm-6">
<label className="form-label">Пользователь SSH</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.rootUser}
onChange={(e) => setForm((prev) => ({ ...prev, rootUser: e.target.value }))}
@@ -1534,7 +1535,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">SSH порт</label>
<input
<input {...noBrowserSuggestProps}
type="number"
min="1"
max="65535"
@@ -1545,7 +1546,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Окружение</label>
<select
<select autoComplete="off"
className="form-select"
value={form.environment}
onChange={(e) => setForm((prev) => ({ ...prev, environment: e.target.value }))}
@@ -1572,7 +1573,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Назначение</label>
<input
<input {...noBrowserSuggestProps}
className="form-control"
value={form.purpose}
onChange={(e) => setForm((prev) => ({ ...prev, purpose: e.target.value }))}
@@ -1581,7 +1582,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6 d-flex align-items-end gap-3 pt-2">
<label className="form-check mb-0">
<input
<input {...noBrowserSuggestProps}
className="form-check-input"
type="checkbox"
checked={form.monitoringEnabled}
@@ -1590,7 +1591,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<span className="form-check-label">Мониторинг</span>
</label>
<label className="form-check mb-0">
<input
<input {...noBrowserSuggestProps}
className="form-check-input"
type="checkbox"
checked={form.backupEnabled}
@@ -1607,7 +1608,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<div className="row g-3">
<div className="col-12 col-sm-6 col-md-4">
<label className="form-label">Статус</label>
<select
<select autoComplete="off"
className="form-select"
value={form.status}
onChange={(e) => setForm((prev) => ({ ...prev, status: e.target.value }))}
@@ -1619,7 +1620,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6 col-md-4">
<label className="form-label">Тип тарифа</label>
<select
<select autoComplete="off"
className="form-select"
value={form.tariffType}
onChange={(e) =>
@@ -1639,7 +1640,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<label className="form-label">
{form.tariffType === 'daily' ? 'Суточный тариф' : 'Месячный тариф'}
</label>
<input
<input {...noBrowserSuggestProps}
type="number"
step="0.01"
min="0"
@@ -1657,7 +1658,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Дата создания</label>
<input
<input {...noBrowserSuggestProps}
type="date"
className="form-control"
value={form.createdAt}
@@ -1666,7 +1667,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
</div>
<div className="col-12 col-sm-6">
<label className="form-label">Оплачено до</label>
<input
<input {...noBrowserSuggestProps}
type="date"
className="form-control"
value={form.paidUntil}
@@ -1681,7 +1682,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<h6 className="vps-form-section-title">Заметки</h6>
<div className="row g-3">
<div className="col-12">
<textarea
<textarea {...noBrowserSuggestProps}
className="form-control"
rows={3}
value={form.notes}
@@ -1692,7 +1693,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
{editingId && (form.notes?.includes('bm-') || (db.vps.find((v) => v.id === editingId)?.userOverrides?.length ?? 0) > 0) ? (
<div className="col-12">
<label className="form-check">
<input
<input {...noBrowserSuggestProps}
type="checkbox"
className="form-check-input"
checked={form.resetToApi}
@@ -1712,7 +1713,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
{customFields.map((f) => (
<div key={f.key} className="col-12 col-sm-6">
<label className="form-label">{f.label}</label>
<input
<input {...noBrowserSuggestProps}
type="text"
className="form-control"
value={form[f.key] ?? ''}