fix(error-states): retry CTA on category error, drop duplicate retry on search
Category page: the previous error UI was a single flat red block with no recovery path — users had to refresh the browser. Now it uses the standard alert pattern (heading + detail + action), surfaces the actual error message when one is available, and offers a Tekrar dene button wired to refetch(). Search page: removed the dedicated "Tekrar Dene" button inside the error banner. The main Şase Çöz submit button sits immediately above and remains enabled after an error — having two near-identical CTAs stacked on top of each other was just noise. The error banner is now informational only. Also drops the now-unused handleRetry function and RotateCcw import in the search route. Phase 6/8 of the UX audit follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import { toast } from "@/lib/toast";
|
||||
import { Badge, Button, Input, Separator } from "@sase/ui";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Link, createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { AlertCircle, Car, Clock, Loader2, RotateCcw, Search, Send } from "lucide-react";
|
||||
import { AlertCircle, Car, Clock, Loader2, Search, Send } from "lucide-react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
// ─── HELPERS ──────────────────────────────────────────────────────────────────
|
||||
@@ -188,18 +188,6 @@ function SearchPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleRetry() {
|
||||
const vinToRetry = lastAttemptedVinRef.current;
|
||||
if (!vinToRetry || loading) return;
|
||||
attemptCountRef.current += 1;
|
||||
capture("vin_decode_retry_clicked", {
|
||||
vin: vinToRetry,
|
||||
attempt: attemptCountRef.current,
|
||||
previous_error: error,
|
||||
});
|
||||
runDecode(vinToRetry, attemptCountRef.current);
|
||||
}
|
||||
|
||||
// ─── Submit ────────────────────────────────────────────────────────────────
|
||||
async function handleSearch(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
@@ -436,51 +424,35 @@ function SearchPage() {
|
||||
Şase Çöz
|
||||
</Button>
|
||||
|
||||
{/* Error banner */}
|
||||
{/* Error banner — the main submit button above doubles as the retry,
|
||||
so we don't render a second CTA here (avoids two near-identical
|
||||
buttons stacked on top of each other). */}
|
||||
{error && (
|
||||
<div
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
className="space-y-3 rounded-xl border border-destructive/40 bg-destructive/10 p-4"
|
||||
className="flex items-start gap-3 rounded-xl border border-destructive/40 bg-destructive/10 p-4"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="mt-0.5 size-5 shrink-0 text-destructive" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-medium text-destructive">Şase çözümlenemedi</p>
|
||||
<p className="mt-1 text-sm text-destructive/90">
|
||||
{error.includes("abone olun") ? (
|
||||
<>
|
||||
Aktif aboneliğiniz yok. Araç verilerine erişmek için{" "}
|
||||
<Link
|
||||
to="/dashboard/subscription"
|
||||
className="inline-flex items-center font-semibold underline underline-offset-4 transition hover:text-destructive/80"
|
||||
>
|
||||
abone olun
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
) : (
|
||||
error
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{!error.includes("abone olun") && !error.includes("tanınamadı") && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleRetry}
|
||||
disabled={loading || !lastAttemptedVinRef.current}
|
||||
className="h-11 w-full rounded-xl"
|
||||
data-faro-user-action-name="vin-decode-retry"
|
||||
>
|
||||
{loading ? (
|
||||
<Loader2 className="mr-2 size-4 animate-spin" />
|
||||
<AlertCircle className="mt-0.5 size-5 shrink-0 text-destructive" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-medium text-destructive">Şase çözümlenemedi</p>
|
||||
<p className="mt-1 text-sm text-destructive/90">
|
||||
{error.includes("abone olun") ? (
|
||||
<>
|
||||
Aktif aboneliğiniz yok. Araç verilerine erişmek için{" "}
|
||||
<Link
|
||||
to="/dashboard/subscription"
|
||||
className="inline-flex items-center font-semibold underline underline-offset-4 transition hover:text-destructive/80"
|
||||
>
|
||||
abone olun
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
) : (
|
||||
<RotateCcw className="mr-2 size-4" />
|
||||
error
|
||||
)}
|
||||
Tekrar Dene
|
||||
</Button>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ function VehicleCategoryPage() {
|
||||
const { id, categoryId } = Route.useParams();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { data, isLoading, error } = useCategoryParts(id, categoryId);
|
||||
const { data, isLoading, error, refetch, isFetching } = useCategoryParts(id, categoryId);
|
||||
|
||||
// Vehicle for breadcrumb root — cached if user arrived from /dashboard/vehicles/$id
|
||||
const { data: vehicle } = useQuery({
|
||||
@@ -191,8 +191,27 @@ function VehicleCategoryPage() {
|
||||
|
||||
{/* Error state */}
|
||||
{error && (
|
||||
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive">
|
||||
Veriler yüklenirken bir hata oluştu. Lütfen tekrar deneyin.
|
||||
<div
|
||||
role="alert"
|
||||
className="flex flex-col items-start gap-3 rounded-lg border border-destructive/40 bg-destructive/5 p-5 text-sm"
|
||||
>
|
||||
<div>
|
||||
<p className="font-medium text-destructive">Kategori yüklenemedi</p>
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
{error instanceof Error
|
||||
? error.message
|
||||
: "Veriler yüklenirken bir hata oluştu."}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => refetch()}
|
||||
disabled={isFetching}
|
||||
>
|
||||
{isFetching ? "Yükleniyor…" : "Tekrar dene"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user