fix(web): drop FM square badge from Logo; better 404 diagnostics on MoySklad page

Logo simplified to just "FOOD" (black) + "MARKET" (brand green) text — matches
the app-icon style without the distracting FM badge square.

MoySklad import page now shows actionable error text instead of generic
"Request failed with status code 404":
- 404 → "эндпоинт не существует, API не перезапущен после git pull"
- 401 → "сессия истекла, перелогинься"
- 403 → "нужна роль Admin или SuperAdmin"
- 502/503 → "МойСклад недоступен"
- Otherwise extracts body.error / error_description / title from response

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nurdotnet 2026-04-21 21:16:01 +05:00
parent 25f25f9171
commit cead88b0bc
4 changed files with 34 additions and 24 deletions

View file

@ -55,7 +55,7 @@ export function AppLayout() {
<div className="min-h-screen flex bg-slate-50 dark:bg-slate-950"> <div className="min-h-screen flex bg-slate-50 dark:bg-slate-950">
<aside className="w-60 flex-shrink-0 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 flex flex-col"> <aside className="w-60 flex-shrink-0 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 flex flex-col">
<div className="h-14 flex items-center px-5 border-b border-slate-200 dark:border-slate-800"> <div className="h-14 flex items-center px-5 border-b border-slate-200 dark:border-slate-800">
<Logo size={28} /> <Logo />
</div> </div>
<nav className="flex-1 overflow-y-auto py-3"> <nav className="flex-1 overflow-y-auto py-3">

View file

@ -1,25 +1,17 @@
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
export function Logo({ size = 28, showText = true, className }: { size?: number; showText?: boolean; className?: string }) { export function Logo({ className }: { className?: string }) {
return ( return (
<div className={cn('flex items-center gap-2.5', className)}> <div className={cn('flex flex-col leading-none select-none', className)}>
<div <span className="font-black text-slate-900 dark:text-slate-100 tracking-[0.08em] text-base">
className="flex items-center justify-center rounded-md font-black text-white leading-none" FOOD
style={{ </span>
backgroundColor: 'var(--color-brand)', <span
width: size, className="font-black text-[11px] tracking-[0.24em] mt-0.5"
height: size, style={{ color: 'var(--color-brand)' }}
fontSize: Math.floor(size * 0.38),
}}
> >
FM MARKET
</div> </span>
{showText && (
<div className="leading-tight">
<div className="font-black text-slate-900 dark:text-slate-100 tracking-wide">FOOD</div>
<div className="font-black text-xs tracking-[0.2em]" style={{ color: 'var(--color-brand)' }}>MARKET</div>
</div>
)}
</div> </div>
) )
} }

View file

@ -33,9 +33,9 @@ export function LoginPage() {
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="w-full max-w-md bg-white dark:bg-slate-800 rounded-xl shadow-lg p-8 space-y-5" className="w-full max-w-md bg-white dark:bg-slate-800 rounded-xl shadow-lg p-8 space-y-5"
> >
<div className="space-y-3"> <div>
<Logo size={44} /> <Logo />
<p className="text-sm text-slate-500">Вход в систему</p> <p className="text-sm text-slate-500 mt-2.5">Вход в систему</p>
</div> </div>
<label className="block space-y-1.5"> <label className="block space-y-1.5">

View file

@ -1,11 +1,29 @@
import { useState } from 'react' import { useState } from 'react'
import { useMutation, useQueryClient } from '@tanstack/react-query' import { useMutation, useQueryClient } from '@tanstack/react-query'
import { AlertCircle, CheckCircle, Download, KeyRound } from 'lucide-react' import { AlertCircle, CheckCircle, Download, KeyRound } from 'lucide-react'
import { AxiosError } from 'axios'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { PageHeader } from '@/components/PageHeader' import { PageHeader } from '@/components/PageHeader'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
import { Field, TextInput, Checkbox } from '@/components/Field' import { Field, TextInput, Checkbox } from '@/components/Field'
function formatError(err: unknown): string {
if (err instanceof AxiosError) {
const status = err.response?.status
const body = err.response?.data as { error?: string; error_description?: string; title?: string } | undefined
const detail = body?.error ?? body?.error_description ?? body?.title
if (status === 404) {
return `404 Not Found — эндпоинт не существует. Вероятно, API не перезапущен после git pull. Сделай Ctrl+C → dotnet run.`
}
if (status === 401) return '401 Unauthorized — сессия истекла, перелогинься.'
if (status === 403) return '403 Forbidden — нужна роль Admin или SuperAdmin для этой операции.'
if (status === 502 || status === 503) return `${status} — МойСклад недоступен, попробуй позже.`
return detail ? `${status ?? ''} ${detail}` : err.message
}
if (err instanceof Error) return err.message
return String(err)
}
interface TestResponse { organization: string; inn?: string | null } interface TestResponse { organization: string; inn?: string | null }
interface ImportResponse { interface ImportResponse {
total: number total: number
@ -78,7 +96,7 @@ export function MoySkladImportPage() {
)} )}
{test.error && ( {test.error && (
<div className="text-sm text-red-600"> <div className="text-sm text-red-600">
{(test.error as Error).message} {formatError(test.error)}
</div> </div>
)} )}
</div> </div>
@ -138,7 +156,7 @@ export function MoySkladImportPage() {
{run.error && ( {run.error && (
<div className="mt-5 p-3.5 rounded-md bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-900 text-sm text-red-700 dark:text-red-300"> <div className="mt-5 p-3.5 rounded-md bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-900 text-sm text-red-700 dark:text-red-300">
Ошибка: {(run.error as Error).message} Ошибка: {formatError(run.error)}
</div> </div>
)} )}
</div> </div>