diff --git a/src/App.tsx b/src/App.tsx
index 28fceca..8194f92 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -94,6 +94,7 @@ import { createExcelExport, downloadBlob, parseExcelImport } from "./lib/excel";
import {
caffeineFor,
caffeinePerCan,
+ canLimitFromSpend,
currency,
currentStreak,
daysSinceLast,
@@ -656,6 +657,7 @@ function App() {
onThemeChange={setThemeId}
onSave={saveOnboarding}
onClose={() => setSetupOpen(false)}
+ initialLimits={userLimits}
/>
)}
{
if (!onSaveLimits) return;
const lowerDailyLimit = Math.round(stats.avgDailySpend * 0.8 * 100) / 100;
+ const size = userLimits.limitCanSizeMl ?? 250;
onSaveLimits({
...userLimits,
+ limitCanSizeMl: size,
dailySpendLimit: lowerDailyLimit,
+ dailyCanLimit: canLimitFromSpend(lowerDailyLimit, size),
});
};
diff --git a/src/components/DailyLimitsCard.tsx b/src/components/DailyLimitsCard.tsx
index f48e7f3..d6dc987 100644
--- a/src/components/DailyLimitsCard.tsx
+++ b/src/components/DailyLimitsCard.tsx
@@ -17,8 +17,8 @@ export function DailyLimitsCard({ limits, check, onOpenSettings }: DailyLimitsCa
Daily limits
- Set how many cans you want per day, when to stop, and a spend cap. Limits are optional and stored on your
- account.
+ Set your usual can size and daily ceiling. Spend is calculated automatically. Limits are optional and stored
+ on your account.
diff --git a/src/components/LimitsSettingsForm.tsx b/src/components/LimitsSettingsForm.tsx
index ce7d9e9..93efcd4 100644
--- a/src/components/LimitsSettingsForm.tsx
+++ b/src/components/LimitsSettingsForm.tsx
@@ -1,7 +1,13 @@
import { Loader2, Target } from "lucide-react";
import { useEffect, useState, type FormEvent } from "react";
-import type { LimitCheckResult, UserLimits } from "../types";
-import { currency } from "../lib/metrics";
+import {
+ BUILT_IN_SIZES,
+ canLimitFromSpend,
+ currency,
+ priceForLimitSize,
+ spendLimitFromCans,
+} from "../lib/metrics";
+import type { BuiltInSize, LimitCheckResult, UserLimits } from "../types";
type LimitsSettingsFormProps = {
limits: UserLimits;
@@ -11,15 +17,57 @@ type LimitsSettingsFormProps = {
};
export function LimitsSettingsForm({ limits, check, saving, onSave }: LimitsSettingsFormProps) {
+ const [canSizeMl, setCanSizeMl] = useState(limits.limitCanSizeMl ?? 250);
const [canInput, setCanInput] = useState(limits.dailyCanLimit?.toString() ?? "");
const [spendInput, setSpendInput] = useState(limits.dailySpendLimit?.toString() ?? "");
const [stopInput, setStopInput] = useState(limits.stopTime ?? "");
useEffect(() => {
+ setCanSizeMl(limits.limitCanSizeMl ?? 250);
setCanInput(limits.dailyCanLimit?.toString() ?? "");
setSpendInput(limits.dailySpendLimit?.toString() ?? "");
setStopInput(limits.stopTime ?? "");
- }, [limits.dailyCanLimit, limits.dailySpendLimit, limits.stopTime]);
+ }, [limits.dailyCanLimit, limits.dailySpendLimit, limits.limitCanSizeMl, limits.stopTime]);
+
+ function syncFromCans(cans: number, size: BuiltInSize) {
+ setCanInput(cans.toString());
+ setSpendInput(spendLimitFromCans(cans, size).toFixed(2));
+ }
+
+ function handleCanSizeChange(size: BuiltInSize) {
+ setCanSizeMl(size);
+ const canTrim = canInput.trim();
+ if (canTrim) {
+ const cans = Math.max(0.25, Number(canTrim) || 0);
+ syncFromCans(cans, size);
+ }
+ }
+
+ function handleCanInputChange(value: string) {
+ setCanInput(value);
+ const canTrim = value.trim();
+ if (!canTrim) {
+ setSpendInput("");
+ return;
+ }
+ const cans = Math.max(0.25, Number(canTrim) || 0);
+ if (cans > 0) {
+ setSpendInput(spendLimitFromCans(cans, canSizeMl).toFixed(2));
+ }
+ }
+
+ function handleSpendInputChange(value: string) {
+ setSpendInput(value);
+ const spendTrim = value.trim();
+ if (!spendTrim) {
+ setCanInput("");
+ return;
+ }
+ const spend = Math.max(0, Number(spendTrim) || 0);
+ if (spend >= 0) {
+ setCanInput(canLimitFromSpend(spend, canSizeMl).toString());
+ }
+ }
function submit(event: FormEvent) {
event.preventDefault();
@@ -28,13 +76,11 @@ export function LimitsSettingsForm({ limits, check, saving, onSave }: LimitsSett
const canTrim = canInput.trim();
if (canTrim) {
const parsed = Math.max(0.25, Number(canTrim) || 0);
- if (parsed > 0) next.dailyCanLimit = parsed;
- }
-
- const spendTrim = spendInput.trim();
- if (spendTrim) {
- const parsed = Math.max(0, Number(spendTrim) || 0);
- next.dailySpendLimit = parsed;
+ if (parsed > 0) {
+ next.dailyCanLimit = parsed;
+ next.limitCanSizeMl = canSizeMl;
+ next.dailySpendLimit = spendLimitFromCans(parsed, canSizeMl);
+ }
}
if (stopInput.trim()) {
@@ -51,9 +97,39 @@ export function LimitsSettingsForm({ limits, check, saving, onSave }: LimitsSett
if (limits.dailySpendLimit != null) {
previewParts.push(`${currency.format(check.todaySpend)} of ${currency.format(limits.dailySpendLimit)} spent today`);
}
+ if (limits.limitCanSizeMl != null) {
+ previewParts.push(`${limits.limitCanSizeMl}ml cans`);
+ }
return (