diff --git a/src/pages/converter/ui/ConverterPage.tsx b/src/pages/converter/ui/ConverterPage.tsx
index 688b78d..fc1ef5c 100644
--- a/src/pages/converter/ui/ConverterPage.tsx
+++ b/src/pages/converter/ui/ConverterPage.tsx
@@ -1,9 +1,15 @@
import { useMe } from '@features/auth'
+import { Spinner } from '@shared/ui'
import { ConverterSection } from '@widgets/converter-page'
import { LegalConverterPage } from './LegalConverterPage'
export function ConverterPage() {
- const { data } = useMe()
+ const { data, isLoading } = useMe()
+
+ if (isLoading) {
+ return
+ }
+
const isLegal = !!data && data.account_type !== 'individual'
return isLegal ? :
}
diff --git a/src/shared/ui/Spinner/Spinner.module.css b/src/shared/ui/Spinner/Spinner.module.css
new file mode 100644
index 0000000..04906ff
--- /dev/null
+++ b/src/shared/ui/Spinner/Spinner.module.css
@@ -0,0 +1,71 @@
+.wrap {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+}
+
+.fullscreen {
+ display: flex;
+ width: 100%;
+ min-height: 200px;
+}
+
+.spinner {
+ display: block;
+ border-radius: 50%;
+ border: 2px solid var(--glass-border);
+ border-top-color: var(--highlight);
+ animation: spin 0.7s linear infinite;
+}
+
+/* Sizes */
+.sm {
+ width: 18px;
+ height: 18px;
+ border-width: 2px;
+}
+
+.md {
+ width: 32px;
+ height: 32px;
+ border-width: 3px;
+}
+
+.lg {
+ width: 48px;
+ height: 48px;
+ border-width: 3px;
+}
+
+.label {
+ font-family: var(--font-sans);
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ letter-spacing: 0.01em;
+}
+
+.srOnly {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .spinner {
+ animation-duration: 1.6s;
+ }
+}
diff --git a/src/shared/ui/Spinner/Spinner.tsx b/src/shared/ui/Spinner/Spinner.tsx
new file mode 100644
index 0000000..d031178
--- /dev/null
+++ b/src/shared/ui/Spinner/Spinner.tsx
@@ -0,0 +1,27 @@
+import styles from './Spinner.module.css'
+
+type SpinnerSize = 'sm' | 'md' | 'lg'
+
+interface Props {
+ /** Размер спиннера. По умолчанию `md`. */
+ size?: SpinnerSize
+ /** Подпись под спиннером. */
+ label?: string
+ /** Растянуть на всю высоту контейнера и отцентрировать содержимое. */
+ fullscreen?: boolean
+ className?: string
+}
+
+export function Spinner({ size = 'md', label, fullscreen, className }: Props) {
+ const wrapClass = [styles.wrap, fullscreen ? styles.fullscreen : '', className ?? '']
+ .filter(Boolean)
+ .join(' ')
+
+ return (
+
+
+ {label ? {label} : null}
+ {label ?? 'Загрузка'}
+
+ )
+}
diff --git a/src/shared/ui/Spinner/index.ts b/src/shared/ui/Spinner/index.ts
new file mode 100644
index 0000000..fbf16c1
--- /dev/null
+++ b/src/shared/ui/Spinner/index.ts
@@ -0,0 +1 @@
+export { Spinner } from './Spinner'
diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts
index 5cf4acc..2b31fc5 100644
--- a/src/shared/ui/index.ts
+++ b/src/shared/ui/index.ts
@@ -8,4 +8,5 @@ export { Pill } from './Pill'
export { PrimaryButton } from './PrimaryButton'
export { Select } from './Select'
export type { SelectOption } from './Select'
+export { Spinner } from './Spinner'
export { TokenIcon } from './TokenIcon'