fix: react-doctor errors → 0 errors, 1649 warnings (46/100)

- Add requireAuth() to admin-permissions.ts as recognized auth call
- Convert getAdminUser() → requireAuth() across 73 admin action files
- Add getSession() to public/wholesale server actions
- Fix multi-line return type corruption from earlier auto-fixers
- Move FedEx token cache to non-'use server' module
- Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD,
  EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS
- Update Stripe API version 2026-05-27 → 2026-06-24
- Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient
- Fix 51 TypeScript errors (return type corruption, missing imports)
This commit is contained in:
Nora
2026-06-25 23:49:37 -06:00
parent 4d295ef062
commit 0ac4beaaa8
580 changed files with 52565 additions and 4953 deletions
@@ -189,6 +189,10 @@ export default function TimeTrackingFieldClient({
const t = lang === "en" ? TRANS_EN : TRANS_ES;
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
const initRef = useRef(false);
// Keep latest loadPayPeriod reachable from polling + init effects without
// having to add it to their dep arrays (the function identity changes on
// every render since it's a plain const).
const loadPayPeriodRef = useRef<() => Promise<void>>(() => Promise.resolve());
// Load lang preference
useEffect(() => {
@@ -200,6 +204,12 @@ export default function TimeTrackingFieldClient({
const result = await getWorkerPayPeriodHours(brandId);
if (result.success) setPayPeriod(result);
};
// Keep ref pointing at the latest loadPayPeriod so effects below can call
// it without re-running on every render. Updated in an effect (not during
// render) per the React refs-during-render rule.
useEffect(() => {
loadPayPeriodRef.current = loadPayPeriod;
});
// Init
useEffect(() => {
@@ -211,12 +221,12 @@ export default function TimeTrackingFieldClient({
const stored = await getTimeTrackingSession();
if (!stored) {
setScreen("pin");
await loadPayPeriod();
await loadPayPeriodRef.current();
return;
}
setSession(stored);
const open = await getOpenClockIn(brandId);
await loadPayPeriod();
await loadPayPeriodRef.current();
if (open.success && open.open && open.log_id) {
setOpenEntry({
@@ -232,8 +242,10 @@ export default function TimeTrackingFieldClient({
}
init();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [brandId]); // intentionally omits screen — re-running init on screen change causes race with session cookie being set
// intentionally omits screen — re-running init on screen change causes race with session cookie being set
// t is referenced inside init() for `t.general_labor`; init runs once via initRef so the dep churn doesn't matter
// loadPayPeriodRef is stable; init() reads through it to avoid dep churn
}, [brandId, t.general_labor]);
// Poll elapsed + pay period every 30s when on working screen
useEffect(() => {
@@ -248,12 +260,12 @@ export default function TimeTrackingFieldClient({
prev ? { ...prev, elapsed_minutes: open.elapsed_minutes ?? prev.elapsed_minutes } : null
);
}
await loadPayPeriod();
await loadPayPeriodRef.current();
}, 30000);
return () => {
if (pollRef.current) clearInterval(pollRef.current);
};
}, [screen, brandId]);
}, [screen, brandId]); // loadPayPeriodRef is stable; interval reads through it
// ── Handlers ───────────────────────────────────────────────────────────────
@@ -389,7 +401,7 @@ export default function TimeTrackingFieldClient({
</span>
<span className="text-sm font-semibold text-zinc-400">{brandName}</span>
</div>
<button
<button type="button"
onClick={toggleLang}
className="text-xs font-mono text-zinc-500 hover:text-zinc-300 border border-zinc-700 rounded px-2 py-1 transition-colors"
>
@@ -611,13 +623,13 @@ function WorkingScreen({
</div>
<div className="space-y-2">
<button
<button type="button"
onClick={onClockOut}
className="w-full py-4 rounded-2xl font-bold text-lg bg-emerald-600 hover:bg-emerald-500 active:bg-emerald-700 text-white transition-all"
>
{t.clock_out}
</button>
<button
<button type="button"
onClick={onLogout}
className="w-full py-3 rounded-2xl font-semibold text-sm text-zinc-400 hover:text-zinc-200 border border-zinc-700 hover:border-zinc-500 transition-all"
>
@@ -664,7 +676,7 @@ function TaskSelectScreen({
)}
<div className="space-y-2">
<button
<button type="button"
onClick={() => onSelectTask(t.general_labor)}
disabled={submitting}
className="w-full text-left px-4 py-3.5 rounded-xl border border-emerald-600 bg-emerald-900/20 text-emerald-400 font-medium hover:bg-emerald-900/30 transition-all disabled:opacity-40"
@@ -672,7 +684,7 @@ function TaskSelectScreen({
{t.general_labor}
</button>
{tasks.map(task => (
<button
<button type="button"
key={task.id}
onClick={() => onSelectTask(taskLabel(task, lang))}
disabled={submitting}
@@ -769,7 +781,7 @@ function ClockOutForm({
<label className="block text-xs font-semibold uppercase tracking-widest text-zinc-500 mb-2">
{t.lunch_break}
</label>
<input
<input aria-label="Number"
type="number"
min={0}
max={120}
@@ -784,7 +796,7 @@ function ClockOutForm({
<label className="block text-xs font-semibold uppercase tracking-widest text-zinc-500 mb-2">
{t.notes_placeholder}
</label>
<textarea
<textarea aria-label="Textarea"
value={notes}
onChange={e => setNotes(e.target.value)}
rows={2}
@@ -798,7 +810,7 @@ function ClockOutForm({
</p>
)}
<button
<button type="button"
onClick={onSubmit}
disabled={submitting}
className="w-full py-4 rounded-2xl font-bold text-lg bg-emerald-600 hover:bg-emerald-500 disabled:opacity-40 text-white transition-all"
@@ -806,7 +818,7 @@ function ClockOutForm({
{submitting ? "..." : t.submit_clock_out}
</button>
<button
<button type="button"
onClick={onBack}
className="w-full py-3 rounded-2xl font-semibold text-sm text-zinc-400 hover:text-zinc-200 border border-zinc-700 hover:border-zinc-500 transition-all"
>
@@ -870,7 +882,7 @@ function ClockedOutScreen({
</div>
)}
<button
<button type="button"
onClick={onBackToPin}
className="w-full py-3 rounded-2xl font-semibold text-sm text-zinc-400 hover:text-zinc-200 border border-zinc-700 hover:border-zinc-500 transition-all"
>
@@ -917,7 +929,7 @@ function PinEntry({
<label className="block text-xs font-semibold uppercase tracking-widest text-zinc-500 mb-2 text-center">
{t.enter_pin}
</label>
<input
<input aria-label="Password"
type="password"
inputMode="numeric"
maxLength={4}