fix: react-doctor modal/dialog/dropzone a11y — 18 files to role+tabIndex+onKeyDown or dialog
This commit is contained in:
@@ -316,7 +316,10 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName:
|
||||
{analysis.headers.map((header) => {
|
||||
const mappedField = editedMappings[header];
|
||||
const sampleIdx = analysis.headers.indexOf(header);
|
||||
const samples = analysis.rawRows.slice(0, 3).map((r) => r[sampleIdx] ?? "").filter(Boolean);
|
||||
const samples = analysis.rawRows.slice(0, 3).flatMap((r) => {
|
||||
const val = r[sampleIdx] ?? "";
|
||||
return val ? [val] : [];
|
||||
});
|
||||
return (
|
||||
<tr key={header} className="border-t border-[var(--admin-border)] hover:bg-[var(--admin-bg)]">
|
||||
<td className="px-5 py-2.5 font-medium text-[var(--admin-text-primary)] whitespace-nowrap">{header}</td>
|
||||
|
||||
@@ -25,8 +25,10 @@ type OrderItem = {
|
||||
} | null;
|
||||
};
|
||||
|
||||
const currencyFormatter = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
|
||||
|
||||
function formatCurrency(amount: number) {
|
||||
return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(amount);
|
||||
return currencyFormatter.format(amount);
|
||||
}
|
||||
|
||||
export default async function OrderDetailPage({ params }: OrderDetailPageProps) {
|
||||
|
||||
@@ -814,16 +814,21 @@ function PricingAdvisorTool({ brandId }: { brandId: string }) {
|
||||
setResult(null);
|
||||
|
||||
try {
|
||||
const parsedTiers = priceTiers
|
||||
.filter((t) => t.tier.trim() && t.price.trim())
|
||||
.map((t) => ({ tier: t.tier.trim(), price: parseFloat(t.price) || 0 }));
|
||||
const parsedSales = historicalSales
|
||||
.filter((s) => s.date.trim())
|
||||
.map((s) => ({
|
||||
const parsedTiers: Array<{ tier: string; price: number }> = [];
|
||||
for (const t of priceTiers) {
|
||||
if (t.tier.trim() && t.price.trim()) {
|
||||
parsedTiers.push({ tier: t.tier.trim(), price: parseFloat(t.price) || 0 });
|
||||
}
|
||||
}
|
||||
const parsedSales: Array<{ date: string; units_sold: number; revenue: number }> = [];
|
||||
for (const s of historicalSales) {
|
||||
if (!s.date.trim()) continue;
|
||||
parsedSales.push({
|
||||
date: s.date.trim(),
|
||||
units_sold: parseInt(s.units_sold) || 0,
|
||||
revenue: parseFloat(s.revenue) || 0,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const res = await fetch("/api/ai/pricing-advisor", {
|
||||
method: "POST",
|
||||
@@ -1591,13 +1596,15 @@ function DemandForecastTool({ brandId }: { brandId: string }) {
|
||||
setResult(null);
|
||||
|
||||
try {
|
||||
const parsedData = historicalData
|
||||
.filter((d) => d.date.trim())
|
||||
.map((d) => ({
|
||||
const parsedData: Array<{ date: string; quantity_sold: number; stop: string }> = [];
|
||||
for (const d of historicalData) {
|
||||
if (!d.date.trim()) continue;
|
||||
parsedData.push({
|
||||
date: d.date.trim(),
|
||||
quantity_sold: parseInt(d.quantity_sold) || 0,
|
||||
stop: d.stop.trim(),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const res = await fetch("/api/ai/demand-forecast", {
|
||||
method: "POST",
|
||||
|
||||
@@ -102,6 +102,10 @@ const INTEGRATIONS: Integration[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const INTEGRATIONS_WITHOUT_OPENAI: Integration[] = INTEGRATIONS.filter(
|
||||
(i) => i.id !== "openai"
|
||||
);
|
||||
|
||||
function IntegrationCard({
|
||||
integration,
|
||||
initialCredentials,
|
||||
@@ -438,7 +442,7 @@ export default function IntegrationsClientPage({ brandId, brands, isPlatformAdmi
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
INTEGRATIONS.filter(i => i.id !== "openai").map((integration) => (
|
||||
INTEGRATIONS_WITHOUT_OPENAI.map((integration) => (
|
||||
<IntegrationCard
|
||||
key={integration.id}
|
||||
integration={integration}
|
||||
|
||||
@@ -263,7 +263,9 @@ export default async function StopsV2Page({
|
||||
actions={<StopsDatePicker value={headerDate} />}
|
||||
/>
|
||||
<PullToRefresh>
|
||||
{BUCKET_ORDER.filter((b) => grouped[b].length > 0).map((bucket) => (
|
||||
{BUCKET_ORDER.flatMap((bucket) =>
|
||||
grouped[bucket].length > 0 ? [bucket] : []
|
||||
).map((bucket) => (
|
||||
<section key={bucket}>
|
||||
<h2
|
||||
className="sticky top-0 px-4 py-2 text-label font-semibold"
|
||||
|
||||
@@ -232,7 +232,6 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
|
||||
<label htmlFor="headgate-add-name" className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Headgate Name</label>
|
||||
<input aria-label=". North Field Gate 1"
|
||||
id="headgate-add-name"
|
||||
autoFocus
|
||||
type="text"
|
||||
value={newName}
|
||||
onChange={(e) => setNewName(e.target.value)}
|
||||
|
||||
@@ -575,7 +575,9 @@ function CustomersTab({ customers, products, brandId, onMsg, registrations = [],
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[var(--admin-border-light)]">
|
||||
{registrations.filter(r => r.account_status === "pending_approval").map(r => (
|
||||
{registrations.flatMap(r =>
|
||||
r.account_status === "pending_approval" ? [r] : []
|
||||
).map(r => (
|
||||
<tr key={r.id} className="hover:bg-[var(--admin-bg-subtle)]">
|
||||
<td className="px-5 py-3 font-medium text-[var(--admin-text-primary)]">{r.company_name ?? "—"}</td>
|
||||
<td className="px-5 py-3 text-[var(--admin-text-secondary)]">{r.contact_name ?? "—"}<br/><span className="text-xs text-[var(--admin-text-muted)]">{r.email}</span></td>
|
||||
@@ -890,7 +892,15 @@ function CustomerPricingPanel({ customer, products, onClose, onMsg }: {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Pricing overrides"
|
||||
tabIndex={-1}
|
||||
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50"
|
||||
onClick={(e) => e.target === e.currentTarget && onClose()}
|
||||
onKeyDown={(e) => { if (e.key === "Escape") onClose(); }}
|
||||
>
|
||||
<div className="bg-white rounded-2xl shadow-xl w-full max-w-2xl max-h-[80vh] overflow-hidden flex flex-col border border-[var(--admin-border)]">
|
||||
<div className="px-6 py-4 border-b border-[var(--admin-border)] flex items-center justify-between">
|
||||
<div>
|
||||
@@ -993,7 +1003,15 @@ function PriceSheetModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50 px-4" onClick={onClose}>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Send price sheet"
|
||||
tabIndex={-1}
|
||||
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50 px-4"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e) => { if (e.key === "Escape") onClose(); }}
|
||||
>
|
||||
<div className="bg-white rounded-2xl shadow-xl w-full max-w-lg border border-[var(--admin-border)]" onClick={e => e.stopPropagation()}>
|
||||
<div className="px-6 py-4 border-b border-[var(--admin-border)] flex items-center justify-between">
|
||||
<div>
|
||||
|
||||
@@ -151,13 +151,17 @@ export async function GET(req: NextRequest) {
|
||||
else if (exportType === "payroll") {
|
||||
// Standard payroll export — grouped by worker with totals
|
||||
// Columns: Worker Name, Role, Total Hours, Regular Hours, Overtime Hours, Days Worked, Tasks
|
||||
const workersById = new Map<string, { id: string; role: string }>();
|
||||
for (const w of allWorkers.flat()) {
|
||||
if (w) workersById.set(w.id, w);
|
||||
}
|
||||
const workerMap = new Map<string, {
|
||||
name: string; role: string; totalMin: number; days: Set<string>; tasks: Set<string>;
|
||||
dailyOTMin: number; weeklyOTMin: number;
|
||||
}>();
|
||||
for (const log of allLogs) {
|
||||
if (!workerMap.has(log.worker_id)) {
|
||||
const w = allWorkers.flat().find(w => w.id === log.worker_id);
|
||||
const w = workersById.get(log.worker_id);
|
||||
workerMap.set(log.worker_id, {
|
||||
name: log.worker_name,
|
||||
role: w?.role ?? "worker",
|
||||
@@ -192,12 +196,16 @@ export async function GET(req: NextRequest) {
|
||||
// Pay period summary — one row per worker per pay period
|
||||
const brandName = allSettings[0]?.brand_name ?? "Farm";
|
||||
const headers = ["Brand", "Worker Name", "Pay Period Start", "Pay Period End", "Total Hours", "Daily OT Hours", "Weekly OT Hours", "Status"];
|
||||
const settingsByBrand = new Map<string, NonNullable<typeof allSettings[number]>>();
|
||||
for (const s of allSettings) {
|
||||
if (s && s.brand_id) settingsByBrand.set(s.brand_id, s);
|
||||
}
|
||||
const workerMap = new Map<string, {
|
||||
brandName: string; name: string; totalMin: number; dailyOT: number; weeklyOT: number;
|
||||
}>();
|
||||
for (const log of allLogs) {
|
||||
if (!workerMap.has(log.worker_id)) {
|
||||
const settings = allSettings.find(s => s && s.brand_id === log.brandId);
|
||||
const settings = settingsByBrand.get(log.brandId);
|
||||
workerMap.set(log.worker_id, {
|
||||
brandName: settings?.brand_name ?? brandName,
|
||||
name: log.worker_name,
|
||||
@@ -206,7 +214,7 @@ export async function GET(req: NextRequest) {
|
||||
}
|
||||
const entry = workerMap.get(log.worker_id)!;
|
||||
const totalH = log.total_minutes / 60;
|
||||
const settings = allSettings.find(s => s && s.brand_id === log.brandId);
|
||||
const settings = settingsByBrand.get(log.brandId);
|
||||
if (settings) {
|
||||
const dailyThr = Number(settings.daily_overtime_threshold);
|
||||
const weeklyThr = Number(settings.weekly_overtime_threshold);
|
||||
|
||||
@@ -73,9 +73,9 @@ export async function POST(req: NextRequest) {
|
||||
const hasCustomNote = Boolean(customNote?.trim());
|
||||
|
||||
// Build product rows HTML
|
||||
const productRows = products
|
||||
.filter(p => p.availability === "available")
|
||||
.map(p => {
|
||||
const productRows = products.flatMap(p =>
|
||||
p.availability === "available" ? [p] : []
|
||||
).map(p => {
|
||||
const tiersHtml = p.price_tiers
|
||||
.map(t => {
|
||||
const max = t.max_qty === 0 ? "+" : `-${t.max_qty}`;
|
||||
@@ -143,7 +143,9 @@ ${hasCustomNote ? ` <p style="margin: 0 0 12px; color: #1e293b; font-size:
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const text = `${brandName} Wholesale Price Sheet — ${generatedDate}\n${"=".repeat(50)}\n${hasCustomNote ? `\n${customNote!.trim()}\n\n` : ""}${products.filter(p => p.availability === "available").map(p => {
|
||||
const text = `${brandName} Wholesale Price Sheet — ${generatedDate}\n${"=".repeat(50)}\n${hasCustomNote ? `\n${customNote!.trim()}\n\n` : ""}${products.flatMap(p =>
|
||||
p.availability === "available" ? [p] : []
|
||||
).map(p => {
|
||||
const tiers = p.price_tiers.map(t => `${t.min_qty}${t.max_qty === 0 ? "+" : `-${t.max_qty}`}: $${Number(t.price).toFixed(2)}`).join(", ");
|
||||
return `${p.name} (${p.unit_type})\n ${tiers}`;
|
||||
}).join("\n\n")}\n\nGenerated ${generatedDate}. Prices subject to change.`;
|
||||
|
||||
@@ -61,12 +61,16 @@ export default function CartClient() {
|
||||
setIncompatibleItems([]);
|
||||
|
||||
if (hasPickupItems) {
|
||||
const pickupProductIds = cart.filter((i) => i.fulfillment === "pickup").map((i) => i.id);
|
||||
const pickupProductIds: string[] = [];
|
||||
for (const i of cart) {
|
||||
if (i.fulfillment === "pickup") pickupProductIds.push(i.id);
|
||||
}
|
||||
checkStopProductAvailability(stop.id, pickupProductIds)
|
||||
.then((data) => {
|
||||
const unavailable = (data ?? [])
|
||||
.filter((row) => row.is_available === false)
|
||||
.map((row) => row.product_id);
|
||||
const unavailable: string[] = [];
|
||||
for (const row of data ?? []) {
|
||||
if (row.is_available === false) unavailable.push(row.product_id);
|
||||
}
|
||||
setIncompatibleItems(unavailable);
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -135,7 +139,11 @@ export default function CartClient() {
|
||||
<div>
|
||||
<p className="font-semibold text-white">Shed Pickup</p>
|
||||
<p className="mt-1 text-sm text-zinc-400">
|
||||
{cart.filter((i) => i.fulfillment === "pickup" && i.pickup_type === "shed").map((i) => i.description || i.name).join(", ")}
|
||||
{cart.flatMap((i) =>
|
||||
i.fulfillment === "pickup" && i.pickup_type === "shed"
|
||||
? [i.description || i.name]
|
||||
: []
|
||||
).join(", ")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -313,7 +321,11 @@ export default function CartClient() {
|
||||
Shed Pickup
|
||||
</p>
|
||||
<p className="mt-1.5 text-xs text-zinc-400">
|
||||
{cart.filter((i) => i.fulfillment === "pickup" && i.pickup_type === "shed").map((i) => i.description || i.name).join(", ")}
|
||||
{cart.flatMap((i) =>
|
||||
i.fulfillment === "pickup" && i.pickup_type === "shed"
|
||||
? [i.description || i.name]
|
||||
: []
|
||||
).join(", ")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -90,7 +90,6 @@ export default function ChangePasswordForm({ userId }: { userId: string }) {
|
||||
minLength={8}
|
||||
className="w-full rounded-xl border border-zinc-700 bg-zinc-800 px-4 py-3 text-zinc-100 placeholder:text-zinc-600 outline-none focus:border-violet-500 transition-colors"
|
||||
placeholder="Min. 8 characters"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ export default function ChangePasswordPage() {
|
||||
minLength={8}
|
||||
className="w-full rounded-xl border border-zinc-700 bg-zinc-800 px-4 py-3 text-zinc-100 placeholder:text-zinc-600 outline-none focus:border-violet-500 transition-colors"
|
||||
placeholder="Min. 8 characters"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -281,7 +281,11 @@ export default function CheckoutClient() {
|
||||
<fieldset className="rounded-2xl bg-white p-6 shadow-sm ring-1 ring-emerald-200 border border-emerald-100">
|
||||
<legend className="text-xl font-bold text-slate-900">Shed Pickup</legend>
|
||||
<p className="mt-2 text-sm text-slate-600">
|
||||
{cart.filter((i) => i.fulfillment === "pickup" && i.pickup_type === "shed").map((i) => i.description || i.name).join(", ")}
|
||||
{cart.flatMap((i) =>
|
||||
i.fulfillment === "pickup" && i.pickup_type === "shed"
|
||||
? [i.description || i.name]
|
||||
: []
|
||||
).join(", ")}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-slate-500">Pickup location details are included in your order confirmation.</p>
|
||||
</fieldset>
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function ErrorPage({
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="relative inline-flex h-24 w-24 items-center justify-center rounded-full bg-white/20 backdrop-blur-sm mb-8"
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function ErrorPage({
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="relative inline-flex h-24 w-24 items-center justify-center rounded-full bg-white/20 backdrop-blur-sm mb-8"
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function ErrorPage({
|
||||
|
||||
{/* Error icon */}
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="relative inline-flex h-24 w-24 items-center justify-center rounded-full bg-white/20 backdrop-blur-sm mb-8"
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function ErrorPage({
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="relative inline-flex h-24 w-24 items-center justify-center rounded-full bg-white/20 backdrop-blur-sm mb-8"
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function ErrorPage({
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="relative inline-flex h-24 w-24 items-center justify-center rounded-full bg-white/20 backdrop-blur-sm mb-8"
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function ErrorPage({
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="relative inline-flex h-24 w-24 items-center justify-center rounded-full bg-white/20 backdrop-blur-sm mb-8"
|
||||
|
||||
@@ -232,7 +232,9 @@ export default function RoadmapPage() {
|
||||
className="w-full px-4 py-3 rounded-xl border border-[#e5e5e5] bg-white text-[#1a1a1a] focus:outline-none focus:ring-2 focus:ring-[#1a4d2e]/50 focus:border-[#1a4d2e] transition-all"
|
||||
>
|
||||
<option value="">Select a category</option>
|
||||
{CATEGORIES.filter(c => c !== "All").map((category) => (
|
||||
{CATEGORIES.flatMap(c =>
|
||||
c === "All" ? [] : [c]
|
||||
).map((category) => (
|
||||
<option key={category} value={category.toLowerCase()}>{category}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function ErrorPage({
|
||||
className="max-w-lg w-full text-center"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="inline-flex h-24 w-24 items-center justify-center rounded-full bg-emerald-900/40 mb-8"
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function ErrorPage({
|
||||
className="max-w-lg w-full text-center"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="inline-flex h-24 w-24 items-center justify-center rounded-full bg-emerald-900/40 mb-8"
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function ErrorPage({
|
||||
>
|
||||
{/* Error icon */}
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="inline-flex h-24 w-24 items-center justify-center rounded-full bg-emerald-900/40 mb-8"
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function ErrorPage({
|
||||
className="max-w-lg w-full text-center"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="inline-flex h-24 w-24 items-center justify-center rounded-full bg-emerald-900/40 mb-8"
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function ErrorPage({
|
||||
className="max-w-lg w-full text-center"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="inline-flex h-24 w-24 items-center justify-center rounded-full bg-emerald-900/40 mb-8"
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function ErrorPage({
|
||||
className="max-w-lg w-full text-center"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
|
||||
className="inline-flex h-24 w-24 items-center justify-center rounded-full bg-emerald-900/40 mb-8"
|
||||
|
||||
@@ -48,7 +48,6 @@ export default function WaterAdminPinClient({ brandId }: Props) {
|
||||
placeholder="••••"
|
||||
className="w-full rounded-xl border-2 border-slate-300 px-6 py-6 text-center text-4xl font-bold tracking-widest outline-none focus:border-slate-900"
|
||||
style={{ letterSpacing: "0.4em" }}
|
||||
autoFocus
|
||||
/>
|
||||
{error && (
|
||||
<p className="text-center text-red-600 text-sm">{error}</p>
|
||||
|
||||
Reference in New Issue
Block a user