summaryrefslogtreecommitdiffhomepage
path: root/src/features/account-usage/ui/DailyCreditsSettings.svelte
blob: 7ed710af6c3c42dd1232da6eb51a569aede0fa4d (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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<script lang="ts">
  import { untrack } from "svelte";
  import type { AccountUsageReport, SaveAccountUsageSettings } from "../logic/types";
  import {
    anchorFromTimeOfDay,
    type DailyCreditsView,
    parseAllowanceInput,
  } from "../logic/view-model";

  let {
    daily,
    save,
    onSaved,
    now = () => Date.now(),
  }: {
    daily: DailyCreditsView;
    save: SaveAccountUsageSettings;
    /** Receives the refreshed report after a successful save. */
    onSaved: (report: AccountUsageReport) => void;
    now?: () => number;
  } = $props();

  let editing = $state(false);
  let allowanceInput = $state("");
  let timeInput = $state("");
  let saving = $state(false);
  let error = $state<string | null>(null);

  const parsedAllowance = $derived(parseAllowanceInput(allowanceInput));
  const timeValid = $derived(timeInput === "" || anchorFromTimeOfDay(timeInput, now()) !== null);

  function startEdit(): void {
    untrack(() => {
      allowanceInput = String(daily.allowance);
      timeInput = daily.resetTime;
    });
    error = null;
    editing = true;
  }

  async function submit(): Promise<void> {
    if (parsedAllowance === null || !timeValid) return;
    saving = true;
    error = null;
    // Empty time clears a MANUAL anchor (a detected one may still apply).
    const resetAnchor = timeInput === "" ? null : anchorFromTimeOfDay(timeInput, now());
    const result = await save("hyper", { dailyAllowance: parsedAllowance, resetAnchor });
    saving = false;
    if (result.ok) {
      editing = false;
      onSaved(result.report);
    } else {
      error = result.error;
    }
  }
</script>

<div class="flex flex-col gap-1 text-[11px]">
  <div class="flex items-center justify-between gap-2">
    <span class="opacity-70">{daily.balanceLabel}</span>
    {#if !editing}
      <button
        type="button"
        class="btn btn-ghost btn-xs"
        onclick={startEdit}
        aria-label="Edit Hyper credit settings">Edit</button
      >
    {/if}
  </div>
  {#if !editing}
    <span class="opacity-60">
      Daily allowance {daily.allowance} · resets {daily.resetTime || "—"} ({daily.resetSourceLabel})
    </span>
  {:else}
    <form
      class="flex flex-col gap-1"
      onsubmit={(e) => {
        e.preventDefault();
        void submit();
      }}
    >
      <label class="flex items-center justify-between gap-2">
        <span>Daily allowance</span>
        <input
          type="text"
          inputmode="numeric"
          class="input input-bordered input-xs w-20 font-mono"
          class:input-error={parsedAllowance === null}
          aria-label="Daily credit allowance"
          bind:value={allowanceInput}
          disabled={saving}
        />
      </label>
      <label class="flex items-center justify-between gap-2">
        <span>Reset time (local)</span>
        <input
          type="time"
          class="input input-bordered input-xs w-24 font-mono"
          class:input-error={!timeValid}
          aria-label="Daily credit reset time"
          bind:value={timeInput}
          disabled={saving}
        />
      </label>
      <span class="opacity-50">Leave the time empty to use the auto-detected refill time.</span>
      {#if error}<span class="text-error">{error}</span>{/if}
      <div class="flex justify-end gap-1">
        <button
          type="button"
          class="btn btn-ghost btn-xs"
          onclick={() => (editing = false)}
          disabled={saving}>Cancel</button
        >
        <button
          type="submit"
          class="btn btn-primary btn-xs"
          disabled={saving || parsedAllowance === null || !timeValid}
          aria-label="Save Hyper credit settings"
        >
          {#if saving}<span class="loading loading-spinner loading-xs"></span>{/if}
          Save
        </button>
      </div>
    </form>
  {/if}
</div>