mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
chore(tooling): миграция с ESLint + Prettier на Biome
Один инструмент вместо девяти dev-зависимостей. Замеры на этом репо (483 файла, медиана из 3 прогонов, один и тот же компьютер): - eslint . ~20.4s -> biome lint . ~2s - prettier --check . ~8.8s -> biome format . ~0.2s - суммарно линт+формат ~29s -> biome check . ~2-3s (~10x) Паритет с прежними правилами: - форматирование бинарно совместимо с Prettier-конфигом (biome format --write тронул 9 файлов из ~480 — микроразличия); - запрет window.confirm/alert/open/prompt и navigator.clipboard (Telegram WebView) перенесён GritQL-плагином biome-plugins/telegram-webview-guards.grit c теми же сообщениями и исключениями для src/platform/** и clipboard.ts; плагин сразу нашёл window.open в Wheel.tsx под старым eslint-disable — переведён на biome-ignore; - react-hooks-правила: useExhaustiveDependencies/useHookAtTopLevel; - новые для проекта правила (a11y и пр.) не включались или понижены до warning, чтобы миграция не смешивалась с чисткой кода — включать можно отдельными PR. Ограничения (задокументированы): сортировка tailwind-классов (prettier-plugin-tailwindcss) в Biome пока nursery — не включена; CSS исключён из Biome (парсер спотыкается о Tailwind-синтаксис globals.css) — файл один и правится редко. CI не меняется: имена npm-скриптов lint/format:check сохранены. lint-staged переведён на biome check --write.
This commit is contained in:
@@ -1,6 +0,0 @@
|
|||||||
dist
|
|
||||||
node_modules
|
|
||||||
public
|
|
||||||
*.min.js
|
|
||||||
*.min.css
|
|
||||||
package-lock.json
|
|
||||||
13
.prettierrc
13
.prettierrc
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": false,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"printWidth": 100,
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"arrowParens": "always",
|
|
||||||
"endOfLine": "auto",
|
|
||||||
"jsxSingleQuote": false,
|
|
||||||
"plugins": ["prettier-plugin-tailwindcss"]
|
|
||||||
}
|
|
||||||
31
biome-plugins/telegram-webview-guards.grit
Normal file
31
biome-plugins/telegram-webview-guards.grit
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
language js
|
||||||
|
|
||||||
|
// Cross-platform guard (was eslint no-restricted-properties): these browser
|
||||||
|
// APIs misbehave inside the Telegram WebView. Route them through the platform
|
||||||
|
// abstraction instead. The platform adapters and the clipboard util (the
|
||||||
|
// canonical implementations) are exempted by the filename conditions below.
|
||||||
|
|
||||||
|
file(name=$name, body=$body) where {
|
||||||
|
$name <: not includes "platform",
|
||||||
|
$name <: not includes or {
|
||||||
|
"clipboard.ts",
|
||||||
|
"clipboard.js"
|
||||||
|
},
|
||||||
|
$body <: contains bubble or {
|
||||||
|
`window.confirm` as $use where {
|
||||||
|
register_diagnostic(span=$use, message="Use useNativeDialog().confirm — window.confirm is silently ignored in the Telegram WebView.", severity="error")
|
||||||
|
},
|
||||||
|
`window.alert` as $use where {
|
||||||
|
register_diagnostic(span=$use, message="Use useNativeDialog().alert — window.alert is silently ignored in the Telegram WebView.", severity="error")
|
||||||
|
},
|
||||||
|
`window.open` as $use where {
|
||||||
|
register_diagnostic(span=$use, message="Use usePlatform().openLink / openTelegramLink — window.open is intercepted by the Telegram WebView.", severity="error")
|
||||||
|
},
|
||||||
|
`window.prompt` as $use where {
|
||||||
|
register_diagnostic(span=$use, message="Use usePrompt() (PromptDialogHost) — window.prompt is not supported in the Telegram WebView.", severity="error")
|
||||||
|
},
|
||||||
|
`navigator.clipboard` as $use where {
|
||||||
|
register_diagnostic(span=$use, message="Use copyToClipboard from @/utils/clipboard — it falls back to execCommand when the Clipboard API is unavailable (Telegram WebView).", severity="error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
biome.json
Normal file
52
biome.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.5.3/schema.json",
|
||||||
|
"files": {
|
||||||
|
"includes": ["**", "!dist", "!node_modules", "!public", "!package-lock.json", "!**/*.css"]
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 100,
|
||||||
|
"lineEnding": "lf"
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"jsxQuoteStyle": "double",
|
||||||
|
"semicolons": "always",
|
||||||
|
"trailingCommas": "all",
|
||||||
|
"arrowParentheses": "always",
|
||||||
|
"bracketSpacing": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true,
|
||||||
|
"a11y": {
|
||||||
|
"recommended": false
|
||||||
|
},
|
||||||
|
"correctness": {
|
||||||
|
"noUnusedVariables": "warn",
|
||||||
|
"useExhaustiveDependencies": "warn",
|
||||||
|
"noInnerDeclarations": "warn",
|
||||||
|
"noUnsafeOptionalChaining": "warn"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noExplicitAny": "warn",
|
||||||
|
"noArrayIndexKey": "off",
|
||||||
|
"noImplicitAnyLet": "warn",
|
||||||
|
"useIterableCallbackReturn": "warn"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"noGlobalEval": "error",
|
||||||
|
"noDangerouslySetInnerHtml": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"assist": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"plugins": ["./biome-plugins/telegram-webview-guards.grit"]
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import js from '@eslint/js';
|
|
||||||
import globals from 'globals';
|
|
||||||
import reactHooks from 'eslint-plugin-react-hooks';
|
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh';
|
|
||||||
import tseslint from 'typescript-eslint';
|
|
||||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
|
||||||
|
|
||||||
export default tseslint.config(
|
|
||||||
{
|
|
||||||
ignores: ['dist', 'node_modules', 'public'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: 2020,
|
|
||||||
globals: globals.browser,
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
'react-hooks': reactHooks,
|
|
||||||
'react-refresh': reactRefresh,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...reactHooks.configs.recommended.rules,
|
|
||||||
'react-hooks/set-state-in-effect': 'off',
|
|
||||||
'react-hooks/static-components': 'off',
|
|
||||||
'react-hooks/immutability': 'off',
|
|
||||||
'react-hooks/refs': 'off',
|
|
||||||
'react-hooks/purity': 'off',
|
|
||||||
'react-hooks/variables': 'off',
|
|
||||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
|
||||||
'@typescript-eslint/no-unused-vars': [
|
|
||||||
'warn',
|
|
||||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_' },
|
|
||||||
],
|
|
||||||
'no-empty': ['warn', { allowEmptyCatch: true }],
|
|
||||||
'no-eval': 'error',
|
|
||||||
'no-implied-eval': 'error',
|
|
||||||
'no-new-func': 'error',
|
|
||||||
'no-script-url': 'error',
|
|
||||||
// Cross-platform guard: these browser APIs misbehave inside the Telegram WebView.
|
|
||||||
// Route them through the platform abstraction instead. The platform adapters and
|
|
||||||
// the clipboard util (the canonical implementations) are exempted below.
|
|
||||||
'no-restricted-properties': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
object: 'window',
|
|
||||||
property: 'confirm',
|
|
||||||
message:
|
|
||||||
'Use useNativeDialog().confirm — window.confirm is silently ignored in the Telegram WebView.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
object: 'window',
|
|
||||||
property: 'alert',
|
|
||||||
message:
|
|
||||||
'Use useNativeDialog().alert — window.alert is silently ignored in the Telegram WebView.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
object: 'window',
|
|
||||||
property: 'open',
|
|
||||||
message:
|
|
||||||
'Use usePlatform().openLink / openTelegramLink — window.open is intercepted by the Telegram WebView.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
object: 'window',
|
|
||||||
property: 'prompt',
|
|
||||||
message:
|
|
||||||
'Use usePrompt() (PromptDialogHost) — window.prompt is not supported in the Telegram WebView.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
object: 'navigator',
|
|
||||||
property: 'clipboard',
|
|
||||||
message:
|
|
||||||
'Use copyToClipboard from @/utils/clipboard — it falls back to execCommand when the Clipboard API is unavailable (Telegram WebView).',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Canonical implementations that intentionally call the restricted browser APIs.
|
|
||||||
files: ['src/platform/**/*.{ts,tsx}', 'src/utils/clipboard.ts'],
|
|
||||||
rules: {
|
|
||||||
'no-restricted-properties': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
eslintConfigPrettier,
|
|
||||||
);
|
|
||||||
1644
package-lock.json
generated
1644
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -8,10 +8,11 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "biome lint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "biome lint --write .",
|
||||||
"format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"",
|
"format": "biome format --write .",
|
||||||
"format:check": "prettier --check \"src/**/*.{ts,tsx,css,json}\"",
|
"format:check": "biome format .",
|
||||||
|
"check": "biome check .",
|
||||||
"type-check": "tsc --noEmit",
|
"type-check": "tsc --noEmit",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
@@ -74,35 +75,23 @@
|
|||||||
"zustand": "^5.0.11"
|
"zustand": "^5.0.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.2",
|
"@biomejs/biome": "2.5.3",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
"@types/node": "^25.2.1",
|
"@types/node": "^25.2.1",
|
||||||
"@types/react": "^19.2.13",
|
"@types/react": "^19.2.13",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/react-twemoji": "^0.4.3",
|
"@types/react-twemoji": "^0.4.3",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
"eslint": "^9.39.2",
|
|
||||||
"eslint-config-prettier": "^10.1.8",
|
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
|
||||||
"eslint-plugin-react-refresh": "^0.5.0",
|
|
||||||
"globals": "^17.1.0",
|
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^16.2.7",
|
"lint-staged": "^16.2.7",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"prettier": "^3.8.1",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
||||||
"tailwindcss": "^3.4.19",
|
"tailwindcss": "^3.4.19",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"typescript-eslint": "^8.54.0",
|
|
||||||
"vite": "^7.3.1"
|
"vite": "^7.3.1"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{ts,tsx}": [
|
"*.{ts,tsx,json}": [
|
||||||
"eslint --fix",
|
"biome check --write --no-errors-on-unmatched"
|
||||||
"prettier --write"
|
|
||||||
],
|
|
||||||
"*.{css,json}": [
|
|
||||||
"prettier --write"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,11 +284,7 @@ export const banSystemApi = {
|
|||||||
|
|
||||||
// Users
|
// Users
|
||||||
getUsers: async (
|
getUsers: async (
|
||||||
params: {
|
params: { offset?: number; limit?: number; status?: string } = {},
|
||||||
offset?: number;
|
|
||||||
limit?: number;
|
|
||||||
status?: string;
|
|
||||||
} = {},
|
|
||||||
): Promise<BanUsersListResponse> => {
|
): Promise<BanUsersListResponse> => {
|
||||||
const response = await apiClient.get('/cabinet/admin/ban-system/users', { params });
|
const response = await apiClient.get('/cabinet/admin/ban-system/users', { params });
|
||||||
return response.data;
|
return response.data;
|
||||||
@@ -349,11 +345,7 @@ export const banSystemApi = {
|
|||||||
|
|
||||||
// Agents
|
// Agents
|
||||||
getAgents: async (
|
getAgents: async (
|
||||||
params: {
|
params: { search?: string; health?: string; status?: string } = {},
|
||||||
search?: string;
|
|
||||||
health?: string;
|
|
||||||
status?: string;
|
|
||||||
} = {},
|
|
||||||
): Promise<BanAgentsListResponse> => {
|
): Promise<BanAgentsListResponse> => {
|
||||||
const response = await apiClient.get('/cabinet/admin/ban-system/agents', { params });
|
const response = await apiClient.get('/cabinet/admin/ban-system/agents', { params });
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ const cardVariants = cva(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export interface CardProps
|
export interface CardProps
|
||||||
extends Omit<HTMLMotionProps<'div'>, 'children'>, VariantProps<typeof cardVariants> {
|
extends Omit<HTMLMotionProps<'div'>, 'children'>,
|
||||||
|
VariantProps<typeof cardVariants> {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
haptic?: boolean;
|
haptic?: boolean;
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { buttonTap, buttonHover, springTransition } from '../../motion/transitio
|
|||||||
import { buttonVariants, type ButtonVariants } from './Button.variants';
|
import { buttonVariants, type ButtonVariants } from './Button.variants';
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'disabled'>, ButtonVariants {
|
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'disabled'>,
|
||||||
|
ButtonVariants {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|||||||
@@ -60,9 +60,8 @@ export const DialogOverlay = forwardRef<HTMLDivElement, DialogOverlayProps>(
|
|||||||
DialogOverlay.displayName = 'DialogOverlay';
|
DialogOverlay.displayName = 'DialogOverlay';
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
export interface DialogContentProps extends ComponentPropsWithoutRef<
|
export interface DialogContentProps
|
||||||
typeof DialogPrimitive.Content
|
extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
|
||||||
> {
|
|
||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ export {
|
|||||||
} from '@radix-ui/react-dropdown-menu';
|
} from '@radix-ui/react-dropdown-menu';
|
||||||
|
|
||||||
// SubTrigger
|
// SubTrigger
|
||||||
export interface DropdownMenuSubTriggerProps extends ComponentPropsWithoutRef<
|
export interface DropdownMenuSubTriggerProps
|
||||||
typeof DropdownMenuPrimitive.SubTrigger
|
extends ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> {
|
||||||
> {
|
|
||||||
inset?: boolean;
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,9 +114,8 @@ export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuConten
|
|||||||
DropdownMenuContent.displayName = 'DropdownMenuContent';
|
DropdownMenuContent.displayName = 'DropdownMenuContent';
|
||||||
|
|
||||||
// Item
|
// Item
|
||||||
export interface DropdownMenuItemProps extends ComponentPropsWithoutRef<
|
export interface DropdownMenuItemProps
|
||||||
typeof DropdownMenuPrimitive.Item
|
extends ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> {
|
||||||
> {
|
|
||||||
inset?: boolean;
|
inset?: boolean;
|
||||||
destructive?: boolean;
|
destructive?: boolean;
|
||||||
}
|
}
|
||||||
@@ -210,9 +208,8 @@ export const DropdownMenuRadioItem = forwardRef<HTMLDivElement, DropdownMenuRadi
|
|||||||
DropdownMenuRadioItem.displayName = 'DropdownMenuRadioItem';
|
DropdownMenuRadioItem.displayName = 'DropdownMenuRadioItem';
|
||||||
|
|
||||||
// Label
|
// Label
|
||||||
export interface DropdownMenuLabelProps extends ComponentPropsWithoutRef<
|
export interface DropdownMenuLabelProps
|
||||||
typeof DropdownMenuPrimitive.Label
|
extends ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> {
|
||||||
> {
|
|
||||||
inset?: boolean;
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ export {
|
|||||||
} from '@radix-ui/react-popover';
|
} from '@radix-ui/react-popover';
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
export interface PopoverContentProps extends ComponentPropsWithoutRef<
|
export interface PopoverContentProps
|
||||||
typeof PopoverPrimitive.Content
|
extends ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> {
|
||||||
> {
|
|
||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import { dropdown, dropdownTransition } from '../../motion/transitions';
|
|||||||
export { Root as Select, Group as SelectGroup } from '@radix-ui/react-select';
|
export { Root as Select, Group as SelectGroup } from '@radix-ui/react-select';
|
||||||
|
|
||||||
// Trigger
|
// Trigger
|
||||||
export interface SelectTriggerProps extends ComponentPropsWithoutRef<
|
export interface SelectTriggerProps
|
||||||
typeof SelectPrimitive.Trigger
|
extends ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
|
||||||
> {
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
import { motion, AnimatePresence, useDragControls, PanInfo } from 'framer-motion';
|
import { motion, AnimatePresence, useDragControls, type PanInfo } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
type ComponentPropsWithoutRef,
|
type ComponentPropsWithoutRef,
|
||||||
@@ -97,9 +97,8 @@ export const SheetOverlay = forwardRef<HTMLDivElement, SheetOverlayProps>(
|
|||||||
SheetOverlay.displayName = 'SheetOverlay';
|
SheetOverlay.displayName = 'SheetOverlay';
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
export interface SheetContentProps extends ComponentPropsWithoutRef<
|
export interface SheetContentProps
|
||||||
typeof DialogPrimitive.Content
|
extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
|
||||||
> {
|
|
||||||
showDragHandle?: boolean;
|
showDragHandle?: boolean;
|
||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
enableDragToClose?: boolean;
|
enableDragToClose?: boolean;
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ import { forwardRef, type ComponentPropsWithoutRef } from 'react';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { usePlatform } from '@/platform';
|
import { usePlatform } from '@/platform';
|
||||||
|
|
||||||
export interface SwitchProps extends Omit<
|
export interface SwitchProps
|
||||||
ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>,
|
extends Omit<ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>, 'onChange'> {
|
||||||
'onChange'
|
|
||||||
> {
|
|
||||||
label?: string;
|
label?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
onChange?: (checked: boolean) => void;
|
onChange?: (checked: boolean) => void;
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ export default function Wheel() {
|
|||||||
// Web-only: synchronously pre-open a tab during the user gesture to dodge the
|
// Web-only: synchronously pre-open a tab during the user gesture to dodge the
|
||||||
// popup blocker before the async invoice URL resolves. Not reached in Telegram
|
// popup blocker before the async invoice URL resolves. Not reached in Telegram
|
||||||
// (hasInvoice is true there, so the native invoice flow is used instead).
|
// (hasInvoice is true there, so the native invoice flow is used instead).
|
||||||
// eslint-disable-next-line no-restricted-properties
|
// biome-ignore lint: canonical popup-blocker workaround, see comment above
|
||||||
preOpenedWindowRef.current = window.open('about:blank', '_blank') || null;
|
preOpenedWindowRef.current = window.open('about:blank', '_blank') || null;
|
||||||
}
|
}
|
||||||
starsInvoiceMutation.mutate();
|
starsInvoiceMutation.mutate();
|
||||||
|
|||||||
Reference in New Issue
Block a user