blob: 132ebc7809b0d2e273afbd370218bb306579c669 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
<script lang="ts">
import type { AutoReduceNotice } from "../logic/view-model";
import type { RestoreOutcome } from "../logic/types";
let {
notice,
onRestore,
onDismiss,
}: {
/** The auto-reduce banner view (providerId + message + from/current limit). */
notice: AutoReduceNotice;
/**
* "Restore to N" — PUT the limit back to `fromLimit`. Returns the outcome so
* a FAILED restore surfaces an inline error here (the banner owns its error
* display; the parent only refreshes on success).
*/
onRestore: (providerId: string, limit: number) => Promise<RestoreOutcome>;
/** Hide this banner locally (persists hidden while autoReduced stays true). */
onDismiss: (providerId: string) => void;
} = $props();
let restoring = $state(false);
/** Inline restore error (e.g. "Concurrency service not available"); cleared on retry. */
let error = $state<string | null>(null);
async function handleRestore(): Promise<void> {
restoring = true;
error = null;
// The parent PUTs the limit + refreshes status on success; the banner clears
// once the next poll shows autoReduced===false. On failure the outcome is
// bubbled back here so the error shows inline next to the button.
const result = await onRestore(notice.providerId, notice.fromLimit);
restoring = false;
if (!result.ok) {
error = result.error;
}
}
</script>
<div
class="alert alert-warning flex flex-col gap-2 py-2 text-xs"
role="status"
data-testid={`auto-reduce-banner-${notice.providerId}`}
>
<div class="flex items-start gap-2">
<span class="shrink-0">⚠</span>
<div class="flex-1">
<p>{notice.message}</p>
<p class="opacity-70">
Was {notice.fromLimit}, now {notice.currentLimit}.
</p>
</div>
<div class="flex shrink-0 items-center gap-1">
<!-- The "Restore to N" text stays visible while loading (only the spinner is
prepended) so the button keeps its accessible name during the PUT — a
spinner-only button loses its name for screen-reader users. -->
<button
type="button"
class="btn btn-warning btn-xs gap-1"
disabled={restoring}
onclick={handleRestore}
>
{#if restoring}
<span class="loading loading-spinner loading-xs"></span>
{/if}
Restore to {notice.fromLimit}
</button>
<button
type="button"
class="btn btn-ghost btn-xs"
aria-label={`Dismiss auto-reduce notice for ${notice.providerId}`}
onclick={() => onDismiss(notice.providerId)}
>
✕
</button>
</div>
</div>
{#if error}
<p class="font-mono text-error" data-testid={`restore-error-${notice.providerId}`}>{error}</p>
{/if}
</div>
|