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:
parent
25f25f9171
commit
cead88b0bc
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue