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
+167 -159
View File
@@ -1,6 +1,12 @@
"use client";
import { useState, useRef, useCallback } from "react";
import {
useState,
useRef,
useCallback,
type Dispatch,
type SetStateAction,
} from "react";
import {
importContactsBatch,
previewContactImport,
@@ -355,152 +361,6 @@ export default function ContactImportForm({ brandId }: { brandId: string }) {
if (fileRef.current) fileRef.current.value = "";
};
// ── Render helpers ────────────────────────────────────────────────────────
function renderMappings() {
if (!preview) return null;
return (
<div className="space-y-3">
<div className="overflow-x-auto">
<table className="w-full text-xs border border-[var(--admin-border)] rounded-lg">
<thead className="bg-[var(--admin-card)]">
<tr>
<th className="px-3 py-2 text-left text-[var(--admin-text-muted)] font-semibold">
CSV Column
</th>
<th className="px-3 py-2 text-left text-[var(--admin-text-muted)] font-semibold">
Maps To
</th>
<th className="px-3 py-2 text-left text-[var(--admin-text-muted)] font-semibold">
Sample Values
</th>
</tr>
</thead>
<tbody>
{preview.mappings.map((m) => (
<tr key={m.csvColumn} className="border-t border-[var(--admin-border)]">
<td className="px-3 py-2 text-[var(--admin-text-primary)] font-mono text-xs">
{m.csvColumn}
</td>
<td className="px-3 py-2">
<select
value={
overridingMappings[m.csvColumn] ?? m.field ?? "ignore"
}
onChange={(e) => {
const val = e.target.value;
setOverridingMappings((prev) => ({
...prev,
[m.csvColumn]:
val === "ignore"
? null
: (val as ImportField),
}));
}}
className="text-xs border border-[var(--admin-border)] rounded px-2 py-1 bg-white text-[var(--admin-text-primary)]"
>
<option value="ignore"> ignore </option>
{Object.entries(FIELD_LABELS).map(([k, label]) => (
<option key={k} value={k}>
{label}
</option>
))}
</select>
</td>
<td className="px-3 py-2 text-[var(--admin-text-muted)] text-xs">
{m.sampleValues.length > 0
? m.sampleValues.join(", ")
: "—"}
</td>
</tr>
))}
</tbody>
</table>
</div>
{preview.ignoredColumns.length > 0 && (
<p className="text-xs text-[var(--admin-text-muted)]">
Extra columns (stored in metadata):
<span className="font-mono">
{" "}
{preview.ignoredColumns.join(", ")}
</span>
</p>
)}
</div>
);
}
function renderStats() {
if (!preview) return null;
return (
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<Stat label="Total rows" value={preview.totalRows} />
<Stat
label="Importable"
value={preview.validRows}
highlight
/>
<Stat label="Skipped" value={preview.skippedRows} warn={preview.skippedRows > 0} />
<Stat
label="Duplicates"
value={preview.duplicateRows}
warn={preview.duplicateRows > 0}
/>
</div>
);
}
function renderSampleRows() {
if (!preview) return null;
return (
<div className="overflow-x-auto">
<table className="w-full text-xs border border-[var(--admin-border)] rounded-lg">
<thead className="bg-[var(--admin-card)]">
<tr>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">#</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">email</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">phone</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">first_name</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">last_name</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">full_name</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">email_opt_in</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">sms_opt_in</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">tags</th>
</tr>
</thead>
<tbody>
{preview.sampleRows.map((row, i) => (
<tr key={i} className="border-t border-[var(--admin-border)] hover:bg-[var(--admin-card-hover)]">
<td className="px-2 py-1 text-[var(--admin-text-muted)]">{i + 1}</td>
<td className="px-2 py-1 text-[var(--admin-text-primary)]">{row.email ?? ""}</td>
<td className="px-2 py-1 text-[var(--admin-text-primary)]">{row.phone ?? ""}</td>
<td className="px-2 py-1 text-[var(--admin-text-primary)]">{row.first_name ?? ""}</td>
<td className="px-2 py-1 text-[var(--admin-text-primary)]">{row.last_name ?? ""}</td>
<td className="px-2 py-1 text-[var(--admin-text-primary)]">{row.full_name ?? ""}</td>
<td className="px-2 py-1 text-[var(--admin-text-muted)]">
{row.email_opt_in === undefined
? ""
: row.email_opt_in
? "true"
: "false"}
</td>
<td className="px-2 py-1 text-[var(--admin-text-muted)]">
{row.sms_opt_in === undefined
? ""
: row.sms_opt_in
? "true"
: "false"}
</td>
<td className="px-2 py-1 text-[var(--admin-text-muted)]">{row.tags?.join("; ") ?? ""}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
// ── Main render ────────────────────────────────────────────────────────────
return (
@@ -514,7 +374,7 @@ export default function ContactImportForm({ brandId }: { brandId: string }) {
<h3 className="text-sm font-semibold text-[var(--admin-text-primary)]">Import Contacts</h3>
</div>
{step !== "idle" && (
<button
<button type="button"
onClick={handleReset}
className="text-xs text-[var(--admin-text-muted)] hover:text-[var(--admin-text-primary)] transition-colors"
>
@@ -562,7 +422,7 @@ export default function ContactImportForm({ brandId }: { brandId: string }) {
}}
onDrop={handleDrop}
>
<input
<input aria-label="File upload"
ref={fileRef}
type="file"
accept=".csv"
@@ -594,7 +454,7 @@ export default function ContactImportForm({ brandId }: { brandId: string }) {
<>
{preview.warnings.map((w, i) => (
<div
key={i}
key={`${w}-${i}`}
className="rounded-lg bg-amber-50 border border-amber-200 px-4 py-3 text-sm text-amber-700"
>
{w}
@@ -610,21 +470,27 @@ export default function ContactImportForm({ brandId }: { brandId: string }) {
</div>
<div className="space-y-3">
{renderMappings()}
{preview ? (
<MappingsView
preview={preview}
overridingMappings={overridingMappings}
setOverridingMappings={setOverridingMappings}
/>
) : null}
</div>
<div>
<p className="text-xs text-[var(--admin-text-muted)] mb-2 font-semibold uppercase tracking-wide">
Import Summary
</p>
{renderStats()}
{preview ? <StatsView preview={preview} /> : null}
</div>
<div>
<p className="text-xs text-[var(--admin-text-muted)] mb-2 font-semibold uppercase tracking-wide">
Sample rows (first {preview.sampleRows.length})
</p>
{renderSampleRows()}
{preview ? <SampleRowsView preview={preview} /> : null}
</div>
{preview.skippedReasons.length > 0 && (
@@ -656,13 +522,13 @@ export default function ContactImportForm({ brandId }: { brandId: string }) {
)}
<div className="flex gap-3">
<button
<button type="button"
onClick={() => handleReset()}
className="rounded-lg border border-[var(--admin-border)] px-4 py-2 text-sm text-[var(--admin-text-muted)] hover:bg-[var(--admin-card-hover)] transition-colors"
>
Cancel
</button>
<button
<button type="button"
onClick={() => handleConfirm()}
disabled={preview.validRows === 0}
className="rounded-lg bg-emerald-600 px-5 py-2 text-sm font-semibold text-white hover:bg-emerald-700 disabled:opacity-50 transition-colors"
@@ -700,7 +566,7 @@ export default function ContactImportForm({ brandId }: { brandId: string }) {
</p>
<div className="max-h-40 overflow-y-auto space-y-1">
{result.errors.slice(0, 10).map((e, i) => (
<p key={i} className="text-red-600 text-xs">
<p key={`${e.error}-${JSON.stringify(e.row)}-${i}`} className="text-red-600 text-xs">
{e.error}: {JSON.stringify(e.row).slice(0, 80)}
</p>
))}
@@ -713,7 +579,7 @@ export default function ContactImportForm({ brandId }: { brandId: string }) {
</div>
)}
<button
<button type="button"
onClick={handleReset}
className="rounded-lg border border-[var(--admin-border)] px-4 py-2 text-sm text-[var(--admin-text-muted)] hover:bg-[var(--admin-card-hover)] transition-colors"
>
@@ -730,7 +596,7 @@ export default function ContactImportForm({ brandId }: { brandId: string }) {
</p>
<div className="space-y-2">
{importHistory.slice(0, 5).map((imp, i) => (
<div key={i} className="flex items-center justify-between text-xs p-2 rounded-lg bg-[var(--admin-card)]">
<div key={`${imp.filename}-${imp.createdAt}-${imp.size}`} className="flex items-center justify-between text-xs p-2 rounded-lg bg-[var(--admin-card)]">
<div className="flex items-center gap-2">
{Icons.file("h-4 w-4 text-[var(--admin-text-muted)]")}
<span className="text-[var(--admin-text-primary)]">{imp.filename}</span>
@@ -770,4 +636,146 @@ function Stat({
<p className={`text-xs mt-0.5 ${labelColor}`}>{label}</p>
</div>
);
}
}
// ── Module-scope sub-views ───────────────────────────────────────────────────
function MappingsView({
preview,
overridingMappings,
setOverridingMappings,
}: {
preview: ImportPreviewResult;
overridingMappings: Record<string, ImportField>;
setOverridingMappings: Dispatch<SetStateAction<Record<string, ImportField>>>;
}) {
return (
<div className="space-y-3">
<div className="overflow-x-auto">
<table className="w-full text-xs border border-[var(--admin-border)] rounded-lg">
<thead className="bg-[var(--admin-card)]">
<tr>
<th className="px-3 py-2 text-left text-[var(--admin-text-muted)] font-semibold">
CSV Column
</th>
<th className="px-3 py-2 text-left text-[var(--admin-text-muted)] font-semibold">
Maps To
</th>
<th className="px-3 py-2 text-left text-[var(--admin-text-muted)] font-semibold">
Sample Values
</th>
</tr>
</thead>
<tbody>
{preview.mappings.map((m) => (
<tr key={m.csvColumn} className="border-t border-[var(--admin-border)]">
<td className="px-3 py-2 text-[var(--admin-text-primary)] font-mono text-xs">
{m.csvColumn}
</td>
<td className="px-3 py-2">
<select aria-label="Select"
value={
overridingMappings[m.csvColumn] ?? m.field ?? "ignore"
}
onChange={(e) => {
const val = e.target.value;
setOverridingMappings((prev) => ({
...prev,
[m.csvColumn]:
val === "ignore"
? null
: (val as ImportField),
}));
}}
className="text-xs border border-[var(--admin-border)] rounded px-2 py-1 bg-white text-[var(--admin-text-primary)]"
>
<option value="ignore"> ignore </option>
{Object.entries(FIELD_LABELS).map(([k, label]) => (
<option key={k} value={k}>
{label}
</option>
))}
</select>
</td>
<td className="px-3 py-2 text-[var(--admin-text-muted)] text-xs">
{m.sampleValues.length > 0
? m.sampleValues.join(", ")
: "—"}
</td>
</tr>
))}
</tbody>
</table>
</div>
{preview.ignoredColumns.length > 0 && (
<p className="text-xs text-[var(--admin-text-muted)]">
Extra columns (stored in metadata):
<span className="font-mono">
{" "}
{preview.ignoredColumns.join(", ")}
</span>
</p>
)}
</div>
);
}
function StatsView({ preview }: { preview: ImportPreviewResult }) {
return (
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<Stat label="Total rows" value={preview.totalRows} />
<Stat label="Importable" value={preview.validRows} highlight />
<Stat label="Skipped" value={preview.skippedRows} warn={preview.skippedRows > 0} />
<Stat label="Duplicates" value={preview.duplicateRows} warn={preview.duplicateRows > 0} />
</div>
);
}
function SampleRowsView({ preview }: { preview: ImportPreviewResult }) {
return (
<div className="overflow-x-auto">
<table className="w-full text-xs border border-[var(--admin-border)] rounded-lg">
<thead className="bg-[var(--admin-card)]">
<tr>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">#</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">email</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">phone</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">first_name</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">last_name</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">full_name</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">email_opt_in</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">sms_opt_in</th>
<th className="px-2 py-1 text-left text-[var(--admin-text-muted)]">tags</th>
</tr>
</thead>
<tbody>
{preview.sampleRows.map((row, i) => (
<tr key={i} className="border-t border-[var(--admin-border)] hover:bg-[var(--admin-card-hover)]">
<td className="px-2 py-1 text-[var(--admin-text-muted)]">{i + 1}</td>
<td className="px-2 py-1 text-[var(--admin-text-primary)]">{row.email ?? ""}</td>
<td className="px-2 py-1 text-[var(--admin-text-primary)]">{row.phone ?? ""}</td>
<td className="px-2 py-1 text-[var(--admin-text-primary)]">{row.first_name ?? ""}</td>
<td className="px-2 py-1 text-[var(--admin-text-primary)]">{row.last_name ?? ""}</td>
<td className="px-2 py-1 text-[var(--admin-text-primary)]">{row.full_name ?? ""}</td>
<td className="px-2 py-1 text-[var(--admin-text-muted)]">
{row.email_opt_in === undefined
? ""
: row.email_opt_in
? "true"
: "false"}
</td>
<td className="px-2 py-1 text-[var(--admin-text-muted)]">
{row.sms_opt_in === undefined
? ""
: row.sms_opt_in
? "true"
: "false"}
</td>
<td className="px-2 py-1 text-[var(--admin-text-muted)]">{row.tags?.join("; ") ?? ""}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}