refactor(frontend): extract SectionLabel + EmptyState + adminStyles shared (#132)
* fix(docs): block javascript: URLs in renderMarkdown href (XSS guard)
Per audit batch #127 P2 security minor (issue #130).
## Risk
`renderMarkdown` substituted `[text](url)` → `<a href="...url...">` verbatim
без URL validation. Edge case: `[click](javascript:alert(1))` →
`<a href="javascript:alert(1)">click</a>` execute'нется при click.
Currently low risk (markdown source = build-time fs.readFileSync из
public/docs/, checked into repo), но защита-в-глубину: если когда-либо
markdown source станет user-supplied, vuln materialize.
## Fix
`frontend/src/app/docs/b2b-channels/renderMarkdown.ts`:
1. New `safeUrl(url)` function — allowlist `https://`, `http://`, `/`, `#`,
`mailto:`. Everything else → `"#"`.
2. Link regex replacement switched from backreference string to callback
so URL passes через `safeUrl` ДО injection в href.
3. URL unescape → safeUrl → re-escape (HTML escape applied per-line).
## Test cases verified
- `[ok](https://example.com)` → href=`https://example.com` ✅
- `[ok](/local)` → href=`/local` ✅
- `[ok](#anchor)` → href=`#anchor` ✅
- `[ok](mailto:foo@bar.com)` → href=`mailto:foo@bar.com` ✅
- `[bad](javascript:alert(1))` → href=`#` ✅
- `[bad](data:text/html,...)` → href=`#` ✅
- `[bad](vbscript:msgbox(1))` → href=`#` ✅
## Checks
- tsc: 0 errors
- lint: 0 warnings
- No existing `__tests__/renderMarkdown` to break
## Vault
`fixes/Bug_RenderMarkdown_JavascriptUrl_May14.md` — created.
Closes #130
* refactor(frontend): SectionLabel + EmptyState + adminStyles shared (fixup)
Per audit batch #127 P2 hygiene (issue #129). Previous commit лошил
файлы из-за pre-commit (видимо). Fixup-коммит с actual diff:
## New shared modules (3)
- frontend/src/components/ui/SectionLabel.tsx
- frontend/src/components/ui/EmptyState.tsx
- frontend/src/lib/adminStyles.ts
## Replacements
- SectionLabel: 12 inline usages → import (Overview/Land/Market/Environment Tab)
- EmptyState: 3 inline usages → import (Land/Market/Environment)
- adminStyles: 5 admin pages import cardStyle/labelStyle/inputStyle/th/td
- BulkGeoPanel: cardStyle only (preserves local labelStyle для span)
- leads/page.tsx: cardStyle с marginTop extend; th/td/inputStyle local (divergent)
## Visual regression: ZERO
All px/colors/CSS properties copied verbatim. Files с divergent values left local.
## Checks
- ruff (no files)
- prettier
- tsc + lint clean (per agent run)
Closes #129
Refs: #127
* docs(adminStyles): JSDoc warning про divergent local overrides
Per bot review #132 non-blocking minor — задокументировать почему
leads/page.tsx и BulkGeoPanel.tsx имеют local overrides, чтобы будущий
читатель не «унифицировал» обратно по ошибке.
Refs: #129, #132
---------
Co-authored-by: lekss361 <claudestars@proton.me>