React Doctor v0.5.8 ✔ Scanned 678 files in 23.8s [~20 workers] ✖ Bugs: Unauthenticated server action can be called directly ×95 Learn more: https://react.doctor/docs/rules/react-doctor/server-auth-actions Anyone can call server action "logAdminAction" without logging in, since it has no auth check. → Check auth before touching data because exported server actions can be called directly by unauthenticated clients. src/actions/admin/audit.ts:22 ┌──────────────────────────────────────────────────────────────┐ │ 21 | │ │ > 22 | export async function logAdminAction(payload: AdminA… │ │ | ^ │ │ 23 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/admin/audit.ts:39 ┌──────────────────────────────────────────────────────────────┐ │ 38 | │ │ > 39 | export async function logUserActivity(payload: UserA… │ │ | ^ │ │ 40 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/admin/users.ts:158 ┌──────────────────────────────────────────────────────────────┐ │ 157 | │ │ > 158 | export async function getAdminUsers(brandId?: strin… │ │ | ^ │ │ 159 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/admin/users.ts:510 ┌──────────────────────────────────────────────────────────────┐ │ 509 | │ │ > 510 | export async function updateAdminUser(input: Update… │ │ | ^ │ │ 511 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/admin/users.ts:546 ┌──────────────────────────────────────────────────────────────┐ │ 545 | │ │ > 546 | export async function deleteAdminUser(id: string): … │ │ | ^ │ │ 547 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/admin/users.ts:556 ┌──────────────────────────────────────────────────────────────┐ │ 555 | │ │ > 556 | export async function setMustChangePassword(userId:… │ │ | ^ │ │ 557 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/admin/users.ts:624 ┌──────────────────────────────────────────────────────────────┐ │ 623 | │ │ > 624 | export async function getBrands(): Promise<{ brands… │ │ | ^ │ │ 625 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/auth-actions.ts:10 ┌──────────────────────────────────────────────────────────────┐ │ 9 | */ │ │ > 10 | export async function signOutAction(): Promise… │ │ | ^ │ │ 11 | console.log("[auth/sign-out] Signing out"); │ └──────────────────────────────────────────────────────────────┘ src/actions/auth-actions.ts:27 ┌──────────────────────────────────────────────────────────────┐ │ 26 | */ │ │ > 27 | export async function signInWithGoogleAction(input: { │ │ | ^ │ │ 28 | callbackURL?: string; │ └──────────────────────────────────────────────────────────────┘ src/actions/billing/retail-checkout.ts:15 ┌──────────────────────────────────────────────────────────────┐ │ 14 | │ │ > 15 | export async function createRetailStripeCheckoutSess… │ │ | ^ │ │ 16 | items: LineItem[], │ └──────────────────────────────────────────────────────────────┘ src/actions/billing/retail-payment-intent.ts:35 ┌──────────────────────────────────────────────────────────────┐ │ 34 | │ │ > 35 | export async function createRetailPaymentIntent( │ │ | ^ │ │ 36 | items: LineItem[], │ └──────────────────────────────────────────────────────────────┘ src/actions/billing/stripe-checkout.ts:82 ┌──────────────────────────────────────────────────────────────┐ │ 81 | │ │ > 82 | export async function createPlanUpgradeCheckout( │ │ | ^ │ │ 83 | brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/billing/stripe-checkout.ts:100 ┌──────────────────────────────────────────────────────────────┐ │ 99 | │ │ > 100 | export async function createAddonCheckoutSession( │ │ | ^ │ │ 101 | brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/billing/stripe-portal.ts:100 ┌──────────────────────────────────────────────────────────────┐ │ 99 | │ │ > 100 | export async function getBrandPlanInfo(brandId: str… │ │ | ^ │ │ 101 | // Replicate get_brand_plan_info via a JOIN on te… │ └──────────────────────────────────────────────────────────────┘ src/actions/billing/stripe-portal.ts:134 ┌──────────────────────────────────────────────────────────────┐ │ 133 | │ │ > 134 | export async function getEnabledAddons(brandId: str… │ │ | ^ │ │ 135 | // get_brand_features returns JSONB — a single ob… │ └──────────────────────────────────────────────────────────────┘ src/actions/billing/stripe-portal.ts:149 ┌──────────────────────────────────────────────────────────────┐ │ 148 | │ │ > 149 | export async function getRecentWholesaleOrders(bran… │ │ | ^ │ │ 150 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/brand-settings.ts:234 ┌──────────────────────────────────────────────────────────────┐ │ 233 | │ │ > 234 | export async function getBrandSettings(brandId: str… │ │ | ^ │ │ 235 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/brand-settings.ts:255 ┌──────────────────────────────────────────────────────────────┐ │ 254 | // has the same shape and never fails because of a … │ │ > 255 | export async function getBrandSettingsPublic(brandS… │ │ | ^ │ │ 256 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/checkout.ts:57 ┌──────────────────────────────────────────────────────────────┐ │ 56 | │ │ > 57 | export async function createOrder( │ │ | ^ │ │ 58 | idempotencyKey: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/checkout.ts:164 ┌──────────────────────────────────────────────────────────────┐ │ 163 | │ │ > 164 | export async function getServerCart(userId: string)… │ │ | ^ │ │ 165 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/checkout.ts:177 ┌──────────────────────────────────────────────────────────────┐ │ 176 | │ │ > 177 | export async function mergeLocalCart( │ │ | ^ │ │ 178 | localCart: CartItem[], │ └──────────────────────────────────────────────────────────────┘ src/actions/checkout.ts:235 ┌──────────────────────────────────────────────────────────────┐ │ 234 | │ │ > 235 | export async function clearServerCart(userId: strin… │ │ | ^ │ │ 236 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/checkout.ts:258 ┌──────────────────────────────────────────────────────────────┐ │ 257 | │ │ > 258 | export async function getPublicStopsForBrand(brandI… │ │ | ^ │ │ 259 | const { rows } = await pool.query( │ └──────────────────────────────────────────────────────────────┘ src/actions/checkout.ts:274 ┌──────────────────────────────────────────────────────────────┐ │ 273 | │ │ > 274 | export async function checkStopProductAvailability( │ │ | ^ │ │ 275 | stopId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/communications/contacts.ts:551 ┌──────────────────────────────────────────────────────────────┐ │ 550 | │ │ > 551 | export async function previewContactImport( │ │ | ^ │ │ 552 | csvText: string │ └──────────────────────────────────────────────────────────────┘ src/actions/communications/settings.ts:48 ┌──────────────────────────────────────────────────────────────┐ │ 47 | │ │ > 48 | export async function getCommunicationSettings(brand… │ │ | ^ │ │ 49 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/email-automation/abandoned-cart.ts:180 ┌──────────────────────────────────────────────────────────────┐ │ 179 | │ │ > 180 | export async function sendAbandonedCartEmail( │ │ | ^ │ │ 181 | cart: AbandonedCart, │ └──────────────────────────────────────────────────────────────┘ src/actions/email-automation/abandoned-cart.ts:246 ┌──────────────────────────────────────────────────────────────┐ │ 245 | │ │ > 246 | export async function markCartRecovered(cartId: str… │ │ | ^ │ │ 247 | void cartId; │ └──────────────────────────────────────────────────────────────┘ src/actions/email-automation/welcome-sequence.ts:181 ┌──────────────────────────────────────────────────────────────┐ │ 180 | │ │ > 181 | export async function sendWelcomeEmail( │ │ | ^ │ │ 182 | entry: WelcomeSequenceEntry, │ └──────────────────────────────────────────────────────────────┘ src/actions/integrations/ai-providers.ts:35 ┌──────────────────────────────────────────────────────────────┐ │ 34 | │ │ > 35 | export async function getAIProviderSettings(brandId:… │ │ | ^ │ │ 36 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/integrations/ai-providers.ts:98 ┌──────────────────────────────────────────────────────────────┐ │ 97 | │ │ > 98 | export async function getAIClient(brandId: string): … │ │ | ^ │ │ 99 | provider: AIProvider; │ └──────────────────────────────────────────────────────────────┘ src/actions/integrations/ai-providers.ts:195 ┌──────────────────────────────────────────────────────────────┐ │ 194 | │ │ > 195 | export async function getCustomIntegrations(brandId… │ │ | ^ │ │ 196 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/integrations/credentials.ts:26 ┌──────────────────────────────────────────────────────────────┐ │ 25 | │ │ > 26 | export async function getResendCredentials(brandId: … │ │ | ^ │ │ 27 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/integrations/credentials.ts:87 ┌──────────────────────────────────────────────────────────────┐ │ 86 | │ │ > 87 | export async function getTwilioCredentials(brandId: … │ │ | ^ │ │ 88 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/integrations/credentials.ts:148 ┌──────────────────────────────────────────────────────────────┐ │ 147 | │ │ > 148 | export async function testResendConnection(apiKey: … │ │ | ^ │ │ 149 | ok: boolean; │ └──────────────────────────────────────────────────────────────┘ src/actions/integrations/credentials.ts:176 ┌──────────────────────────────────────────────────────────────┐ │ 175 | │ │ > 176 | export async function testTwilioConnection( │ │ | ^ │ │ 177 | accountSid: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/locations.ts:224 ┌──────────────────────────────────────────────────────────────┐ │ 223 | │ │ > 224 | export async function getPublicLocationsForBrand( │ │ | ^ │ │ 225 | brandSlug: string │ └──────────────────────────────────────────────────────────────┘ src/actions/offline-dispatcher.ts:48 ┌──────────────────────────────────────────────────────────────┐ │ 47 | │ │ > 48 | export async function listClientActions(): Promise 57 | export async function dispatchClientAction( │ │ | ^ │ │ 58 | actionName: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/payments.ts:28 ┌──────────────────────────────────────────────────────────────┐ │ 27 | │ │ > 28 | export async function getPaymentSettings(brandId: st… │ │ | ^ │ │ 29 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/route-trace/lots.ts:345 ┌──────────────────────────────────────────────────────────────┐ │ 344 | */ │ │ > 345 | export async function getLotIdByNumber(_lotNumber: … │ │ | ^ │ │ 346 | return null; │ └──────────────────────────────────────────────────────────────┘ src/actions/stops.ts:153 ┌──────────────────────────────────────────────────────────────┐ │ 152 | │ │ > 153 | export async function getActiveStopsForSitemap(): P… │ │ | ^ │ │ 154 | // Wrapped in try/catch so a build-time outage (E… │ └──────────────────────────────────────────────────────────────┘ src/actions/stops.ts:186 ┌──────────────────────────────────────────────────────────────┐ │ 185 | │ │ > 186 | export async function getPublicStopsForBrand( │ │ | ^ │ │ 187 | brandSlug: string │ └──────────────────────────────────────────────────────────────┘ src/actions/storefront.ts:60 ┌──────────────────────────────────────────────────────────────┐ │ 59 | */ │ │ > 60 | export async function getStorefrontBrandBySlug( │ │ | ^ │ │ 61 | slug: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/storefront.ts:77 ┌──────────────────────────────────────────────────────────────┐ │ 76 | */ │ │ > 77 | export async function getStorefrontStops(brandId: st… │ │ | ^ │ │ 78 | const { rows } = await pool.query( │ └──────────────────────────────────────────────────────────────┘ src/actions/storefront.ts:101 ┌──────────────────────────────────────────────────────────────┐ │ 100 | */ │ │ > 101 | export async function getStorefrontProducts( │ │ | ^ │ │ 102 | brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/storefront.ts:126 ┌──────────────────────────────────────────────────────────────┐ │ 125 | */ │ │ > 126 | export async function getStorefrontData(slug: strin… │ │ | ^ │ │ 127 | const brand = await getStorefrontBrandBySlug(slug… │ └──────────────────────────────────────────────────────────────┘ src/actions/storefront.ts:149 ┌──────────────────────────────────────────────────────────────┐ │ 148 | */ │ │ > 149 | export async function getStorefrontWholesaleSetting… │ │ | ^ │ │ 150 | slug: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/storefront.ts:175 ┌──────────────────────────────────────────────────────────────┐ │ 174 | */ │ │ > 175 | export async function getWholesalePortalConfig( │ │ | ^ │ │ 176 | brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/storefront.ts:195 ┌──────────────────────────────────────────────────────────────┐ │ 194 | */ │ │ > 195 | export async function getStorefrontBrandName( │ │ | ^ │ │ 196 | brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/storefront.ts:216 ┌──────────────────────────────────────────────────────────────┐ │ 215 | */ │ │ > 216 | export async function getStorefrontStopById(id: str… │ │ | ^ │ │ 217 | stop: StorefrontStop; │ └──────────────────────────────────────────────────────────────┘ src/actions/stripe-connect.ts:173 ┌──────────────────────────────────────────────────────────────┐ │ 172 | */ │ │ > 173 | export async function saveStripeConnectAccount(bran… │ │ | ^ │ │ 174 | success: boolean; │ └──────────────────────────────────────────────────────────────┘ src/actions/tax.ts:27 ┌──────────────────────────────────────────────────────────────┐ │ 26 | │ │ > 27 | export async function calculateOrderTax(params: { │ │ | ^ │ │ 28 | brandId: string; │ └──────────────────────────────────────────────────────────────┘ src/actions/tax.ts:196 ┌──────────────────────────────────────────────────────────────┐ │ 195 | │ │ > 196 | export async function getTaxSummaryAction(params: { │ │ | ^ │ │ 197 | brandId: string; │ └──────────────────────────────────────────────────────────────┘ src/actions/tax.ts:231 ┌──────────────────────────────────────────────────────────────┐ │ 230 | │ │ > 231 | export async function getTaxableOrdersAction(params… │ │ | ^ │ │ 232 | brandId: string; │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/field.ts:55 ┌──────────────────────────────────────────────────────────────┐ │ 54 | │ │ > 55 | export async function verifyTimeTrackingPin( │ │ | ^ │ │ 56 | _brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/field.ts:107 ┌──────────────────────────────────────────────────────────────┐ │ 106 | │ │ > 107 | export async function logoutTimeTracking(): Promise… │ │ | ^ │ │ 108 | const cookieStore = await cookies(); │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/field.ts:114 ┌──────────────────────────────────────────────────────────────┐ │ 113 | │ │ > 114 | export async function getTimeTrackingSession(): Pro… │ │ | ^ │ │ 115 | const cookieStore = await cookies(); │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/field.ts:132 ┌──────────────────────────────────────────────────────────────┐ │ 131 | │ │ > 132 | export async function getTimeTrackingTasksField( │ │ | ^ │ │ 133 | _brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/index.ts:66 ┌──────────────────────────────────────────────────────────────┐ │ 65 | │ │ > 66 | export async function getTimeTrackingWorkers(_brandI… │ │ | ^ │ │ 67 | // Time tracking tables not in SaaS rebuild — retu… │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/index.ts:108 ┌──────────────────────────────────────────────────────────────┐ │ 107 | │ │ > 108 | export async function getTimeTrackingTasks(_brandId… │ │ | ^ │ │ 109 | return []; │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/index.ts:145 ┌──────────────────────────────────────────────────────────────┐ │ 144 | │ │ > 145 | export async function getWorkerTimeLogs( │ │ | ^ │ │ 146 | _brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/index.ts:178 ┌──────────────────────────────────────────────────────────────┐ │ 177 | │ │ > 178 | export async function getTimeTrackingSummary( │ │ | ^ │ │ 179 | _brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/index.ts:207 ┌──────────────────────────────────────────────────────────────┐ │ 206 | │ │ > 207 | export async function getTimeTrackingSettings(_bran… │ │ | ^ │ │ 208 | // Real RPC not in SaaS rebuild. │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/index.ts:253 ┌──────────────────────────────────────────────────────────────┐ │ 252 | │ │ > 253 | export async function getTimeTrackingNotificationLo… │ │ | ^ │ │ 254 | _brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/water-log/admin.ts:127 ┌──────────────────────────────────────────────────────────────┐ │ 126 | */ │ │ > 127 | export async function requireWaterAdminSession(): P… │ │ | ^ │ │ 128 | | { ok: true; adminUserId: string; brandId: strin… │ └──────────────────────────────────────────────────────────────┘ src/actions/water-log/admin.ts:482 ┌──────────────────────────────────────────────────────────────┐ │ 481 | │ │ > 482 | export async function createWaterIrrigator( │ │ | ^ │ │ 483 | brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/water-log/field.ts:68 ┌──────────────────────────────────────────────────────────────┐ │ 67 | │ │ > 68 | export async function getWaterHeadgates( │ │ | ^ │ │ 69 | _brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/water-log/field.ts:98 ┌──────────────────────────────────────────────────────────────┐ │ 97 | │ │ > 98 | export async function verifyWaterPin( │ │ | ^ │ │ 99 | _brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/water-log/field.ts:281 ┌──────────────────────────────────────────────────────────────┐ │ 280 | │ │ > 281 | export async function requireFieldSession(): Promis… │ │ | ^ │ │ 282 | const cookieStore = await cookies(); │ └──────────────────────────────────────────────────────────────┘ src/actions/water-log/field.ts:342 ┌──────────────────────────────────────────────────────────────┐ │ 341 | │ │ > 342 | export async function logoutWater(): Promise { │ │ | ^ │ │ 343 | const cookieStore = await cookies(); │ └──────────────────────────────────────────────────────────────┘ src/actions/water-log/field.ts:358 ┌──────────────────────────────────────────────────────────────┐ │ 357 | │ │ > 358 | export async function logoutWaterAdmin(): Promise 375 | export async function getWaterSession(): Promise 389 | export async function getWaterAdminSession(): Promi… │ │ | ^ │ │ 390 | sessionId: string; │ └──────────────────────────────────────────────────────────────┘ src/actions/water-log/field.ts:421 ┌──────────────────────────────────────────────────────────────┐ │ 420 | │ │ > 421 | export async function setWaterLang(lang: string): P… │ │ | ^ │ │ 422 | if (!["en", "es"].includes(lang)) return; │ └──────────────────────────────────────────────────────────────┘ src/actions/water-log/settings.ts:188 ┌──────────────────────────────────────────────────────────────┐ │ 187 | */ │ │ > 188 | export async function verifyWaterAdminPin( │ │ | ^ │ │ 189 | brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-auth.ts:33 ┌──────────────────────────────────────────────────────────────┐ │ 32 | │ │ > 33 | export async function wholesaleLoginAction(_formData… │ │ | ^ │ │ 34 | return { success: false, error: NOT_AVAILABLE_ERRO… │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-auth.ts:37 ┌──────────────────────────────────────────────────────────────┐ │ 36 | │ │ > 37 | export async function wholesaleLogoutAction(): Promi… │ │ | ^ │ │ 38 | const cookieStore = await cookies(); │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-register.ts:7 ┌──────────────────────────────────────────────────────────────┐ │ 6 | │ │ > 7 | export async function registerWholesaleCustomer(param… │ │ | ^ │ │ 8 | brandId: string; │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-register.ts:88 ┌──────────────────────────────────────────────────────────────┐ │ 87 | │ │ > 88 | export async function getWholesaleCustomerByUser( │ │ | ^ │ │ 89 | brandId: string, │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-register.ts:124 ┌──────────────────────────────────────────────────────────────┐ │ 123 | // Fetch a wholesale customer directly by their cus… │ │ > 124 | export async function getWholesaleCustomer( │ │ | ^ │ │ 125 | customerId: string │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-register.ts:161 ┌──────────────────────────────────────────────────────────────┐ │ 160 | │ │ > 161 | export async function getWholesaleProducts(brandId:… │ │ | ^ │ │ 162 | // Uses SECURITY DEFINER RPC — no auth required f… │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-register.ts:174 ┌──────────────────────────────────────────────────────────────┐ │ 173 | │ │ > 174 | export async function submitWholesaleOrder(params: { │ │ | ^ │ │ 175 | brandId: string; │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-register.ts:261 ┌──────────────────────────────────────────────────────────────┐ │ 260 | │ │ > 261 | export async function enqueueWholesaleWebhookForOrd… │ │ | ^ │ │ 262 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-register.ts:316 ┌──────────────────────────────────────────────────────────────┐ │ 315 | │ │ > 316 | export async function getWholesaleCustomerPricing(c… │ │ | ^ │ │ 317 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-register.ts:328 ┌──────────────────────────────────────────────────────────────┐ │ 327 | │ │ > 328 | export async function upsertWholesaleCustomerPricin… │ │ | ^ │ │ 329 | customerId: string; │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-register.ts:344 ┌──────────────────────────────────────────────────────────────┐ │ 343 | │ │ > 344 | export async function deleteWholesaleCustomerPricin… │ │ | ^ │ │ 345 | customerId: string; │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale-register.ts:359 ┌──────────────────────────────────────────────────────────────┐ │ 358 | │ │ > 359 | export async function getWholesaleCustomerOrders(cu… │ │ | ^ │ │ 360 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale/notifications.ts:7 ┌──────────────────────────────────────────────────────────────┐ │ 6 | │ │ > 7 | export async function getWholesaleNotificationStats( │ │ | ^ │ │ 8 | brandId: string │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale/notifications.ts:59 ┌──────────────────────────────────────────────────────────────┐ │ 58 | │ │ > 59 | export async function enqueueWholesaleNotification(p… │ │ | ^ │ │ 60 | brandId: string; │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale/orders.ts:42 ┌──────────────────────────────────────────────────────────────┐ │ 41 | │ │ > 42 | export async function getWholesaleDashboardStats(bra… │ │ | ^ │ │ 43 | const orders = await getWholesaleOrders(brandId); │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale/settings.ts:27 ┌──────────────────────────────────────────────────────────────┐ │ 26 | │ │ > 27 | export async function getWholesaleSettingsPublic(bra… │ │ | ^ │ │ 28 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale/webhooks.ts:7 ┌──────────────────────────────────────────────────────────────┐ │ 6 | │ │ > 7 | export async function getWebhookSettings(brandId: str… │ │ | ^ │ │ 8 | try { │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale/webhooks.ts:47 ┌──────────────────────────────────────────────────────────────┐ │ 46 | │ │ > 47 | export async function enqueueWholesaleWebhook( │ │ | ^ │ │ 48 | eventType: "order_created" | "order_fulfilled" | "… │ └──────────────────────────────────────────────────────────────┘ src/actions/wholesale/webhooks.ts:65 ┌──────────────────────────────────────────────────────────────┐ │ 64 | │ │ > 65 | export async function getRecentWebhookActivity(brand… │ │ | ^ │ │ 66 | id: string; │ └──────────────────────────────────────────────────────────────┘ ✖ Security: socket/low-supply-chain-score `vitest@2.1.9 (lowest version "^2.1.9" allows)` scored 25/100 on Socket's vulnerability axis (minimum 50). This points to known security vulnerabilities (CVEs) affecting this version. Other axes — supply chain 97, maintenance 98, quality 78, license 100. → Upgrade to a version with no known advisories (run `npm audit` to find one), or replace it; update `"vitest": "^2.1.9"` in package.json. Full report: https://socket.dev/npm/package/vitest/overview/2.1.9. If you've reviewed and accepted this package, raise `supplyChain.minScore` (currently 50) or set `supplyChain.severity: "warning"`. package.json:86 ┌──────────────────────────────────────────────────────────────┐ │ 85 | "vite-tsconfig-paths": "^5.1.4", │ │ > 86 | "vitest": "^2.1.9" │ │ | ^ │ │ 87 | }, │ └──────────────────────────────────────────────────────────────┘ ✖ Security: Side effect in GET handler ×2 Learn more: https://react.doctor/docs/rules/react-doctor/nextjs-no-side-effect-in-get-handler This GET handler's side effect (fetch() with method POST) is prone to CSRF vulnerabilities, since prefetching or a forged request can trigger it. → GET requests can be prefetched and are open to CSRF. Move the side effect to a POST handler. src/app/api/square/oauth/callback/route.ts:5 ┌──────────────────────────────────────────────────────────────┐ │ 4 | │ │ > 5 | export async function GET(request: Request) { │ │ | ^ │ │ 6 | const { searchParams } = new URL(request.url); │ └──────────────────────────────────────────────────────────────┘ src/app/api/stripe/oauth/callback/route.ts:5 ┌──────────────────────────────────────────────────────────────┐ │ 4 | │ │ > 5 | export async function GET(req: Request) { │ │ | ^ │ │ 6 | const adminUser = await getAdminUser(); │ └──────────────────────────────────────────────────────────────┘ ⚠ Security: HTML injection sink with dynamic content ×6 Learn more: https://react.doctor/docs/rules/react-doctor/dangerous-html-sink HTML is injected from a dynamic-looking source, which can become XSS if the value is user-controlled or unsanitized. → Prefer rendering structured React nodes. If HTML is required, sanitize with a well-reviewed sanitizer and keep the trust boundary close to the sink. src/app/admin/water-log/headgates/HeadgatesManager.tsx:176 src/app/admin/water-log/headgates/HeadgatesManager.tsx:475 src/app/wholesale/employee/page.tsx:244 src/components/admin/TemplateEditForm.tsx:652 src/components/wholesale/admin/OrdersTab.tsx:281 src/components/wholesale/admin/OrdersTab.tsx:404 ✖ Bugs: Mutable module state on the server ×8 Learn more: https://react.doctor/docs/rules/react-doctor/server-no-mutable-module-state Module-scoped const "PRICE_KEYS = {}" leaks state between your users, since every request shares it. → Keep per-request data inside the action, or in headers, cookies, or `React.cache`. Module-scope `let`/`var` is shared by every request. src/actions/billing/stripe-checkout.ts:12 ┌──────────────────────────────────────────────────────────────┐ │ 11 | │ │ > 12 | const PRICE_KEYS: Record… │ │ | ^ │ │ 13 | starter: process.env.STRIPE_PRICE_STARTER, │ └──────────────────────────────────────────────────────────────┘ src/actions/dashboard.ts:47 ┌──────────────────────────────────────────────────────────────┐ │ 46 | │ │ > 47 | const EMPTY_MOBILE_DASHBOARD: MobileDashboardSummary… │ │ | ^ │ │ 48 | orders_today: 0, │ └──────────────────────────────────────────────────────────────┘ src/actions/email-automation/abandoned-cart.ts:43 ┌──────────────────────────────────────────────────────────────┐ │ 42 | │ │ > 43 | const LOCALE_CART_SUBJECT: Record 44 | const WELCOME_EMAILS: Record 32 | let cachedToken: FedExAuthToken | null = null; │ │ | ^ │ │ 33 | │ └──────────────────────────────────────────────────────────────┘ src/actions/shipping/fedex-rates.ts:63 ┌──────────────────────────────────────────────────────────────┐ │ 62 | │ │ > 63 | let cachedToken: FedExAuthToken | null = null; │ │ | ^ │ │ 64 | │ └──────────────────────────────────────────────────────────────┘ src/actions/shipping/settings.ts:57 ┌──────────────────────────────────────────────────────────────┐ │ 56 | │ │ > 57 | let cachedToken: FedExAuthToken | null = null; │ │ | ^ │ │ 58 | │ └──────────────────────────────────────────────────────────────┘ src/actions/time-tracking/field.ts:157 ┌──────────────────────────────────────────────────────────────┐ │ 156 | │ │ > 157 | const EMPTY_PAY_PERIOD: PayPeriodHours = { │ │ | ^ │ │ 158 | success: false, │ └──────────────────────────────────────────────────────────────┘ ⚠ Security: Raw SQL built outside parameter binding ×4 Learn more: https://react.doctor/docs/rules/react-doctor/raw-sql-injection-risk Code uses a raw SQL escape hatch or string-built query shape that can bypass parameter binding. → Keep user input in driver parameters or ORM bind variables. Avoid unsafe/raw SQL helpers and string interpolation for queries. src/actions/brand-settings.ts:175 src/actions/orders/update-order.ts:75 src/app/admin/stops/page.tsx:57 src/app/api/ai/customer-insights/route.ts:105 ✖ Bugs: Effect subscription or timer never cleaned up Learn more: https://react.doctor/docs/rules/react-doctor/effect-needs-cleanup `setTimeout` creates a timer in useEffect without returning cleanup. Return a cleanup function so it does not leak after unmount. → Return a cleanup function that stops the subscription or timer: `return () => target.removeEventListener(name, handler)` for listeners, `return () => clearInterval(id)` or `clearTimeout(id)` for timers, or `return unsubscribe` if the subscribe call already gave you one. src/components/legal/CookieConsentBanner.tsx:23 ┌──────────────────────────────────────────────────────────────┐ │ 22 | │ │ > 23 | useEffect(() => { │ │ | ^ │ │ 24 | // Check if consent was already given │ └──────────────────────────────────────────────────────────────┘ ✖ Security: Permissive Supabase RLS policy Learn more: https://react.doctor/docs/rules/react-doctor/supabase-rls-policy-risk Supabase policy SQL disables RLS, permits writes broadly, or references a service-role bypass. → Keep public-read policies explicit, but gate inserts, updates, deletes, and service-role bypasses behind `auth.uid()` plus trusted tenant membership. db/migrations/0001_init.sql:1758 ⚠ Performance: Component defined inside a component Learn more: https://react.doctor/docs/rules/react-doctor/no-unstable-nested-components Your users lose this component's state on every render because it's defined inside another component (`StopTableClient`). → Move nested components to module scope so React does not remount them and lose state on every render. src/components/admin/StopTableClient.tsx:259 ⚠ Bugs: Array index used as a key ×82 Learn more: https://react.doctor/docs/rules/react-doctor/no-array-index-as-key Your users can see & submit the wrong data when this list reorders or filters, so use a stable id like `key={item.id}`, not the array index "i". → Use a stable id from the item, like `key={item.id}` or `key={item.slug}`. Index keys break when the list reorders or filters. src/app/admin/import/ImportCenterClient.tsx:283 src/app/admin/products/import/page.tsx:230 src/app/admin/sales/import/page.tsx:184 src/app/admin/settings/ai/AIClient.tsx:544 src/app/admin/settings/ai/AIClient.tsx:748 src/app/admin/settings/ai/AIClient.tsx:759 src/app/admin/settings/ai/AIClient.tsx:887 src/app/admin/settings/ai/AIClient.tsx:928 src/app/admin/settings/ai/AIClient.tsx:986 src/app/admin/settings/ai/AIClient.tsx:1015 src/app/admin/settings/ai/AIClient.tsx:1025 src/app/admin/settings/ai/AIClient.tsx:1162 src/app/admin/settings/ai/AIClient.tsx:1171 src/app/admin/settings/ai/AIClient.tsx:1304 src/app/admin/settings/ai/AIClient.tsx:1413 src/app/admin/settings/ai/AIClient.tsx:1513 src/app/admin/settings/ai/AIClient.tsx:1531 src/app/admin/settings/ai/AIClient.tsx:1537 src/app/admin/settings/ai/AIClient.tsx:1648 src/app/admin/settings/ai/AIClient.tsx:1733 src/app/admin/settings/ai/AIClient.tsx:1739 src/app/admin/settings/billing/BillingClientPage.tsx:215 src/app/blog/page.tsx:216 src/app/changelog/page.tsx:154 src/app/checkout/success/page.tsx:244 src/app/checkout/success/page.tsx:265 src/app/checkout/success/page.tsx:284 src/app/indian-river-direct/page.tsx:425 src/app/pricing/PricingClientPage.tsx:150 src/app/pricing/PricingClientPage.tsx:294 src/app/pricing/PricingClientPage.tsx:317 src/app/security/page.tsx:101 src/app/security/page.tsx:130 src/app/tuxedo/page.tsx:878 src/app/waitlist/page.tsx:102 src/app/wholesale/employee/page.tsx:490 src/app/wholesale/portal/page.tsx:671 src/components/admin/AbandonedCartDashboard.tsx:229 src/components/admin/AdminOrdersPanel.tsx:997 src/components/admin/AdvancedSettingsClient.tsx:175 src/components/admin/AnalyticsDashboard.tsx:105 src/components/admin/AnalyticsDashboard.tsx:171 src/components/admin/AnalyticsDashboard.tsx:226 src/components/admin/AnalyticsDashboard.tsx:316 src/components/admin/ContactImportForm.tsx:597 src/components/admin/ContactImportForm.tsx:703 src/components/admin/ContactImportForm.tsx:733 src/components/admin/ProductSyncBanner.tsx:89 src/components/admin/ReportsDashboard.tsx:687 src/components/admin/ReportsDashboard.tsx:698 src/components/admin/ScheduleImportModal.tsx:225 src/components/admin/ScheduleImportModal.tsx:254 src/components/admin/SettingsSections.tsx:319 src/components/admin/StatsStrip.tsx:18 src/components/admin/TimeTrackingSettingsClient.tsx:528 src/components/admin/UpgradePlanModal.tsx:257 src/components/admin/design-system/AdminActionMenu.tsx:55 src/components/admin/design-system/AdminStatsBar.tsx:24 src/components/admin/design-system/PageHeader.tsx:35 src/components/admin/shared/FilterBar.tsx:57 src/components/admin/shared/PageHeader.tsx:18 src/components/admin/stops/StopsCalendar.tsx:252 src/components/landing/FeaturesAndStats.tsx:489 src/components/landing/FeaturesAndStats.tsx:616 src/components/landing/HeroSection.tsx:392 src/components/landing/HeroSection.tsx:761 src/components/landing/HeroSection.tsx:862 src/components/landing/TestimonialsAndCTA.tsx:175 src/components/landing/TestimonialsAndCTA.tsx:295 src/components/onboarding/OnboardingFlow.tsx:289 src/components/referral/ReferralSystem.tsx:126 src/components/route-trace/FsmaReportModal.tsx:526 src/components/route-trace/RouteTraceDashboard.tsx:467 src/components/shared/LoadingSkeleton.tsx:83 src/components/shared/LoadingSkeleton.tsx:95 src/components/shared/LoadingSkeleton.tsx:104 src/components/shared/LoadingSkeleton.tsx:113 src/components/storefront/BreadcrumbNav.tsx:49 src/components/storefront/TuxedoVideoHero.tsx:282 src/components/storefront/TuxedoVideoHero.tsx:367 src/components/wholesale/OrderDetailsModal.tsx:90 src/components/wholesale/admin/SettingsTab.tsx:238 ⚠ Bugs: Missing effect dependencies ×12 Learn more: https://react.doctor/docs/rules/react-doctor/exhaustive-deps `useCallback` can run with a stale `setSelectedStop` & show your users old data. → Don't blindly add missing dependencies. Read the hook callback first. Bad: useEffect(() => { setCount(count + 1); }, [count]); Better: useEffect(() => { setCount((currentCount) => currentCount + 1); }, []); If the missing value is recreated every render, move it inside the hook or stabilize it before adding it to deps. src/app/cart/CartClient.tsx:86 src/app/checkout/CheckoutClient.tsx:141 src/components/admin/CartHydration.tsx:15 ↳ oxlint matches plugin rules only by their full name, so `react-hooks/exhaustive-deps` in your eslint-disable comment does not silence `react-doctor/exhaustive-deps` — change it to `react-doctor/exhaustive-deps`. src/components/admin/MessageLogPanel.tsx:242 src/components/admin/ReportsDashboard.tsx:315 ↳ oxlint matches plugin rules only by their full name, so `react-hooks/exhaustive-deps` in your eslint-disable comment does not silence `react-doctor/exhaustive-deps` — change it to `react-doctor/exhaustive-deps`. src/components/route-trace/FsmaReportModal.tsx:184 src/components/route-trace/QRScanModal.tsx:179 src/components/storefront/StripeExpressCheckout.tsx:150 ↳ oxlint matches plugin rules only by their full name, so `react-hooks/exhaustive-deps` in your eslint-disable comment does not silence `react-doctor/exhaustive-deps` — change it to `react-doctor/exhaustive-deps`. src/components/storefront/StripeExpressCheckout.tsx:156 src/components/time-tracking/TimeTrackingFieldClient.tsx:236 ↳ oxlint matches plugin rules only by their full name, so `react-hooks/exhaustive-deps` in your eslint-disable comment does not silence `react-doctor/exhaustive-deps` — change it to `react-doctor/exhaustive-deps`. src/components/time-tracking/TimeTrackingFieldClient.tsx:256 src/components/water/WaterFieldClient.tsx:166 ↳ oxlint matches plugin rules only by their full name, so `react-hooks/exhaustive-deps` in your eslint-disable comment does not silence `react-doctor/exhaustive-deps` — change it to `react-doctor/exhaustive-deps`. ⚠ Bugs: Time or random value in JSX ×23 Learn more: https://react.doctor/docs/rules/react-doctor/rendering-hydration-mismatch-time This can cause a hydration mismatch because new Date() reached from JSX gives a different value on the server than in the browser. Move it into useEffect+useState to run only in the browser, or add suppressHydrationWarning to the parent if it's on purpose. → Move time or random values into useEffect+useState so they only run in the browser, or add suppressHydrationWarning to the parent if it's intentional src/app/admin/v2/page.tsx:88 src/app/admin/v2/page.tsx:133 src/app/blog/page.tsx:263 src/app/brands/page.tsx:215 src/app/changelog/page.tsx:211 src/app/contact/ContactClientPage.tsx:285 src/app/privacy-policy/page.tsx:67 src/app/privacy-policy/page.tsx:129 src/app/roadmap/page.tsx:249 src/app/security/page.tsx:260 src/app/terms-and-conditions/page.tsx:67 src/app/terms-and-conditions/page.tsx:131 src/app/waitlist/page.tsx:121 src/app/wholesale/portal/page.tsx:822 src/components/admin/CampaignListPanel.tsx:668 src/components/admin/HarvestReach/AnalyticsDashboard.tsx:432 src/components/admin/HarvestReach/CampaignComposerPage.tsx:781 src/components/admin/ReportsDashboard.tsx:387 src/components/admin/TaxDashboard.tsx:312 src/components/admin/WaterLogAdminPanel.tsx:381 src/components/landing/LandingPageWrapper.tsx:299 src/components/layout/SiteFooter.tsx:37 src/components/storefront/StorefrontFooter.tsx:201 ⚠ Bugs: Sequential independent awaits ×29 Learn more: https://react.doctor/docs/rules/react-doctor/server-sequential-independent-await This await doesn't use the previous result, so your users wait twice as long for nothing. → These two awaits don't depend on each other. Wrap them in `Promise.all([...])` so they run at the same time. src/actions/communications/stop-blast.ts:59 src/actions/communications/stop-messaging.ts:81 src/actions/harvest-reach/segments.ts:238 src/actions/shipping/fedex-rates.ts:196 src/actions/stops/get-stop-details.ts:88 src/actions/water-log/field.ts:149 src/app/admin/pickup/page.tsx:13 src/app/admin/route-trace/lots/[id]/page.tsx:25 src/app/admin/settings/page.tsx:37 src/app/admin/stops/[id]/page.tsx:58 src/app/admin/stops/[id]/page.tsx:139 src/app/admin/stops/new/page.tsx:36 src/app/admin/stops/page.tsx:22 src/app/admin/v2/orders/[id]/page.tsx:35 src/app/admin/v2/products/page.tsx:81 src/app/admin/v2/stops/page.tsx:133 src/app/admin/water-log/entries/[id]/page.tsx:18-21 src/app/admin/water-log/headgates/[id]/page.tsx:19-22 src/app/admin/water-log/users/[id]/page.tsx:19-22 src/app/api/indian-river-direct/schedule-pdf/route.ts:25 src/app/api/indian-river-direct/schedule-pdf/route.ts:77 src/app/api/tuxedo/schedule-pdf/route.ts:25 src/app/api/tuxedo/schedule-pdf/route.ts:77 src/app/api/wholesale/invoice/[orderId]/pdf/route.ts:122 src/app/api/wholesale/invoice/[orderId]/route.ts:115 src/components/admin/HarvestReach/CampaignComposerPage.tsx:388 ⚠ Accessibility: Label missing associated control ×284 Learn more: https://react.doctor/docs/rules/react-doctor/label-has-associated-control Screen reader users can't tell which input this label names because it's tied to none, so add `htmlFor` or wrap the input inside it. → Tie every label to a control with `htmlFor`, or by nesting the input. src/app/admin/import/ImportCenterClient.tsx:164 src/app/admin/settings/ai/AIClient.tsx:883 src/app/admin/settings/ai/AIClient.tsx:917 src/app/admin/settings/ai/AIClient.tsx:1102 src/app/admin/settings/ai/AIClient.tsx:1106 src/app/admin/settings/ai/AIClient.tsx:1110 src/app/admin/settings/ai/AIClient.tsx:1114 src/app/admin/settings/ai/AIClient.tsx:1232 src/app/admin/settings/ai/AIClient.tsx:1390 src/app/admin/settings/ai/AIClient.tsx:1403 src/app/admin/settings/ai/AIClient.tsx:1424 src/app/admin/settings/ai/AIClient.tsx:1435 src/app/admin/settings/ai/AIClient.tsx:1446 src/app/admin/settings/ai/AIClient.tsx:1457 src/app/admin/settings/ai/AIClient.tsx:1469 src/app/admin/settings/ai/AIClient.tsx:1625 src/app/admin/settings/ai/AIClient.tsx:1629 src/app/admin/settings/ai/AIClient.tsx:1637 src/app/admin/settings/integrations/IntegrationsClient.tsx:311 src/app/admin/settings/integrations/IntegrationsClient.tsx:396 src/app/admin/settings/integrations/IntegrationsClient.tsx:552 src/app/change-password/ChangePasswordForm.tsx:84 src/app/change-password/ChangePasswordForm.tsx:100 src/app/change-password/page.tsx:75 src/app/change-password/page.tsx:91 src/app/indian-river-direct/contact/ContactClientPage.tsx:164 src/app/indian-river-direct/contact/ContactClientPage.tsx:175 src/app/indian-river-direct/contact/ContactClientPage.tsx:188 src/app/indian-river-direct/contact/ContactClientPage.tsx:201 src/app/reset-password/page.tsx:99 src/app/reset-password/page.tsx:114 src/app/tuxedo/contact/ContactClientPage.tsx:133 src/app/tuxedo/contact/ContactClientPage.tsx:143 src/app/tuxedo/contact/ContactClientPage.tsx:155 src/app/tuxedo/contact/ContactClientPage.tsx:168 src/app/wholesale/portal/page.tsx:814 src/components/admin/AIProviderPanel.tsx:229 src/components/admin/AIProviderPanel.tsx:251 src/components/admin/AIProviderPanel.tsx:281 src/components/admin/AIProviderPanel.tsx:296 src/components/admin/AddLocationModal.tsx:121 src/components/admin/AddLocationModal.tsx:136 src/components/admin/AddLocationModal.tsx:151 src/components/admin/AddLocationModal.tsx:164 src/components/admin/AddLocationModal.tsx:178 src/components/admin/AddLocationModal.tsx:194 src/components/admin/AddLocationModal.tsx:207 src/components/admin/AddLocationModal.tsx:222 src/components/admin/AddLocationModal.tsx:236 src/components/admin/AddStopModal.tsx:309 src/components/admin/AdminOrdersPanel.tsx:941 src/components/admin/AdvancedAIPanel.tsx:390 src/components/admin/AdvancedAIPanel.tsx:418 src/components/admin/AdvancedAIPanel.tsx:446 src/components/admin/AdvancedAIPanel.tsx:463 src/components/admin/AdvancedIntegrations.tsx:198 src/components/admin/AdvancedIntegrations.tsx:221 src/components/admin/AdvancedIntegrations.tsx:235 src/components/admin/AdvancedIntegrations.tsx:293 src/components/admin/AdvancedIntegrations.tsx:307 src/components/admin/AdvancedIntegrations.tsx:330 src/components/admin/AdvancedShipping.tsx:108 src/components/admin/AdvancedShipping.tsx:123 src/components/admin/AdvancedShipping.tsx:135 src/components/admin/AdvancedSquareSync.tsx:99 src/components/admin/AdvancedSquareSync.tsx:109 src/components/admin/AdvancedSquareSync.tsx:119 src/components/admin/BrandSettingsForm.tsx:491 src/components/admin/BrandSettingsForm.tsx:510 src/components/admin/BrandSettingsForm.tsx:529 src/components/admin/BrandSettingsForm.tsx:548 src/components/admin/BrandSettingsForm.tsx:614 src/components/admin/CampaignListPanel.tsx:149 src/components/admin/CampaignListPanel.tsx:163 src/components/admin/CampaignListPanel.tsx:482 src/components/admin/CampaignListPanel.tsx:493 src/components/admin/CampaignListPanel.tsx:505 src/components/admin/CampaignListPanel.tsx:545 src/components/admin/CampaignListPanel.tsx:563 src/components/admin/CampaignListPanel.tsx:573 src/components/admin/CampaignListPanel.tsx:582 src/components/admin/CampaignListPanel.tsx:616 src/components/admin/CampaignListPanel.tsx:626 src/components/admin/CampaignListPanel.tsx:662 src/components/admin/CreateUserModal.tsx:377 src/components/admin/CreateUserModal.tsx:420 src/components/admin/EditLocationModal.tsx:140 src/components/admin/EditLocationModal.tsx:154 src/components/admin/EditLocationModal.tsx:168 src/components/admin/EditLocationModal.tsx:180 src/components/admin/EditLocationModal.tsx:193 src/components/admin/EditLocationModal.tsx:208 src/components/admin/EditLocationModal.tsx:220 src/components/admin/EditLocationModal.tsx:234 src/components/admin/EditLocationModal.tsx:247 src/components/admin/EditStopModal.tsx:350 src/components/admin/HarvestReach/CampaignComposerPage.tsx:503 src/components/admin/HarvestReach/CampaignComposerPage.tsx:515 src/components/admin/HarvestReach/CampaignComposerPage.tsx:528 src/components/admin/HarvestReach/CampaignComposerPage.tsx:567 src/components/admin/HarvestReach/CampaignComposerPage.tsx:580 src/components/admin/HarvestReach/CampaignComposerPage.tsx:705 src/components/admin/HarvestReach/CampaignComposerPage.tsx:735 src/components/admin/HarvestReach/CampaignComposerPage.tsx:775 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:330 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:339 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:369 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:425 src/components/admin/HarvestReach/SegmentEditModal.tsx:77 src/components/admin/HarvestReach/SegmentEditModal.tsx:89 src/components/admin/MessageCustomersSection.tsx:170 src/components/admin/MessageCustomersSection.tsx:193 src/components/admin/MessageCustomersSection.tsx:207 src/components/admin/NewProductForm.tsx:240 src/components/admin/NewStopForm.tsx:128 src/components/admin/NewStopForm.tsx:153 src/components/admin/NewStopForm.tsx:179 src/components/admin/NewStopForm.tsx:205 src/components/admin/NewStopForm.tsx:229 src/components/admin/NewStopForm.tsx:255 src/components/admin/NewStopForm.tsx:282 src/components/admin/NewStopForm.tsx:295 src/components/admin/NewStopForm.tsx:306 src/components/admin/NewStopForm.tsx:319 src/components/admin/OrderEditForm.tsx:267 src/components/admin/OrderEditForm.tsx:279 src/components/admin/OrderEditForm.tsx:328 src/components/admin/OrderEditForm.tsx:357 src/components/admin/OrderEditForm.tsx:395 src/components/admin/OrderEditForm.tsx:444 src/components/admin/OrderEditForm.tsx:462 src/components/admin/OrderPaymentSection.tsx:173 src/components/admin/OrderPaymentSection.tsx:189 src/components/admin/OrderPaymentSection.tsx:205 src/components/admin/OrderPaymentSection.tsx:277 src/components/admin/OrderPaymentSection.tsx:303 src/components/admin/ProductEditForm.tsx:209 src/components/admin/ProductEditForm.tsx:223 src/components/admin/ProductEditForm.tsx:253 src/components/admin/SettingsSections.tsx:316 src/components/admin/SettingsSections.tsx:323 src/components/admin/SettingsSections.tsx:332 src/components/admin/SettingsSections.tsx:341 src/components/admin/SettingsSections.tsx:397 src/components/admin/SettingsSections.tsx:420 src/components/admin/SettingsSections.tsx:623 src/components/admin/SettingsSections.tsx:630 src/components/admin/SettingsSections.tsx:638 src/components/admin/SettingsSections.tsx:688 src/components/admin/SettingsSections.tsx:694 src/components/admin/SettingsSections.tsx:701 src/components/admin/SettingsSections.tsx:710 src/components/admin/StopEditForm.tsx:121 src/components/admin/StopEditForm.tsx:146 src/components/admin/StopEditForm.tsx:173 src/components/admin/StopEditForm.tsx:197 src/components/admin/StopEditForm.tsx:209 src/components/admin/StopEditForm.tsx:221 src/components/admin/StopEditForm.tsx:232 src/components/admin/StopEditForm.tsx:265 src/components/admin/StopMessagingForm.tsx:124 src/components/admin/StopMessagingForm.tsx:146 src/components/admin/StopMessagingForm.tsx:168 src/components/admin/TemplateEditForm.tsx:168 src/components/admin/TemplateEditForm.tsx:182 src/components/admin/TemplateEditForm.tsx:448 src/components/admin/TemplateEditForm.tsx:459 src/components/admin/TemplateEditForm.tsx:471 src/components/admin/TemplateEditForm.tsx:490 src/components/admin/TemplateEditForm.tsx:551 src/components/admin/TemplateEditForm.tsx:561 src/components/admin/TemplateEditForm.tsx:574 src/components/admin/TimeTrackingAdminPanel.tsx:419 src/components/admin/TimeTrackingAdminPanel.tsx:428 src/components/admin/TimeTrackingAdminPanel.tsx:654 src/components/admin/TimeTrackingAdminPanel.tsx:663 src/components/admin/TimeTrackingAdminPanel.tsx:672 src/components/admin/TimeTrackingAdminPanel.tsx:685 src/components/admin/TimeTrackingAdminPanel.tsx:801 src/components/admin/TimeTrackingAdminPanel.tsx:805 src/components/admin/TimeTrackingAdminPanel.tsx:813 src/components/admin/TimeTrackingAdminPanel.tsx:886 src/components/admin/TimeTrackingAdminPanel.tsx:890 src/components/admin/TimeTrackingAdminPanel.tsx:894 src/components/admin/TimeTrackingSettingsClient.tsx:525 src/components/admin/TimeTrackingSettingsClient.tsx:532 src/components/admin/TimeTrackingSettingsClient.tsx:541 src/components/admin/TimeTrackingSettingsClient.tsx:550 src/components/admin/TimeTrackingSettingsClient.tsx:613 src/components/admin/TimeTrackingSettingsClient.tsx:632 src/components/admin/TimeTrackingSettingsClient.tsx:682 src/components/admin/TimeTrackingSettingsClient.tsx:689 src/components/admin/TimeTrackingSettingsClient.tsx:697 src/components/admin/TimeTrackingSettingsClient.tsx:741 src/components/admin/TimeTrackingSettingsClient.tsx:747 src/components/admin/TimeTrackingSettingsClient.tsx:754 src/components/admin/TimeTrackingSettingsClient.tsx:763 src/components/admin/UsersPage.tsx:542 src/components/admin/UsersPage.tsx:556 src/components/admin/UsersPage.tsx:571 src/components/admin/UsersPage.tsx:584 src/components/admin/UsersPage.tsx:597 src/components/admin/UsersPage.tsx:625 src/components/admin/UsersPage.tsx:657 src/components/admin/WaterLogEntryEditForm.tsx:74 src/components/admin/WaterLogEntryEditForm.tsx:78 src/components/admin/WaterLogEntryEditForm.tsx:100 src/components/admin/WaterLogEntryEditForm.tsx:104 src/components/admin/WaterUserEditForm.tsx:90 src/components/admin/WaterUserEditForm.tsx:100 src/components/admin/WaterUserEditForm.tsx:116 src/components/admin/WaterUserEditForm.tsx:130 src/components/admin/stops/StopsLocations.tsx:194 src/components/route-trace/FsmaReportModal.tsx:297 src/components/route-trace/FsmaReportModal.tsx:308 src/components/route-trace/LotCreateForm.tsx:97 src/components/route-trace/LotCreateForm.tsx:108 src/components/route-trace/LotCreateForm.tsx:120 src/components/route-trace/LotCreateForm.tsx:135 src/components/route-trace/LotCreateForm.tsx:147 src/components/route-trace/LotCreateForm.tsx:157 src/components/route-trace/LotCreateForm.tsx:175 src/components/route-trace/LotCreateForm.tsx:187 src/components/route-trace/LotCreateForm.tsx:231 src/components/route-trace/LotCreateForm.tsx:241 src/components/route-trace/LotCreateForm.tsx:253 src/components/route-trace/LotCreateForm.tsx:264 src/components/route-trace/LotCreateModal.tsx:149 src/components/route-trace/LotCreateModal.tsx:165 src/components/route-trace/LotCreateModal.tsx:182 src/components/route-trace/LotCreateModal.tsx:202 src/components/route-trace/LotCreateModal.tsx:219 src/components/route-trace/LotCreateModal.tsx:234 src/components/route-trace/LotCreateModal.tsx:257 src/components/route-trace/LotCreateModal.tsx:274 src/components/route-trace/LotCreateModal.tsx:329 src/components/route-trace/LotCreateModal.tsx:344 src/components/route-trace/LotCreateModal.tsx:361 src/components/route-trace/LotCreateModal.tsx:377 src/components/route-trace/LotDetailPanel.tsx:753 src/components/route-trace/LotDetailPanel.tsx:772 src/components/route-trace/LotDetailPanel.tsx:782 src/components/route-trace/LotDetailPanel.tsx:823 src/components/route-trace/LotDetailPanel.tsx:834 src/components/route-trace/LotDetailPanel.tsx:877 src/components/route-trace/LotDetailPanel.tsx:888 src/components/route-trace/LotDetailPanel.tsx:899 src/components/route-trace/QRScanModal.tsx:276 src/components/route-trace/QuickNewLotModal.tsx:82 src/components/route-trace/QuickNewLotModal.tsx:97 src/components/route-trace/QuickNewLotModal.tsx:109 src/components/route-trace/QuickNewLotModal.tsx:123 src/components/route-trace/QuickNewLotModal.tsx:135 src/components/route-trace/QuickNewLotModal.tsx:149 src/components/route-trace/RouteTraceSettings.tsx:68 src/components/route-trace/RouteTraceSettings.tsx:94 src/components/route-trace/RouteTraceSettings.tsx:120 src/components/water/WaterFieldClient.tsx:635 src/components/wholesale/DepositModal.tsx:58 src/components/wholesale/DepositModal.tsx:70 src/components/wholesale/admin/OrdersTab.tsx:212 src/components/wholesale/admin/OrdersTab.tsx:223 src/components/wholesale/admin/OrdersTab.tsx:228 src/components/wholesale/admin/OrdersTab.tsx:233 src/components/wholesale/admin/OrdersTab.tsx:467 src/components/wholesale/admin/OrdersTab.tsx:472 src/components/wholesale/admin/OrdersTab.tsx:501 src/components/wholesale/admin/OrdersTab.tsx:506 src/components/wholesale/admin/ProductsTab.tsx:145 src/components/wholesale/admin/ProductsTab.tsx:154 src/components/wholesale/admin/ProductsTab.tsx:159 src/components/wholesale/admin/SettingsTab.tsx:173 src/components/wholesale/admin/SettingsTab.tsx:178 src/components/wholesale/admin/SettingsTab.tsx:183 src/components/wholesale/admin/SettingsTab.tsx:188 src/components/wholesale/admin/SettingsTab.tsx:193 src/components/wholesale/admin/SettingsTab.tsx:198 src/components/wholesale/admin/SettingsTab.tsx:203 src/components/wholesale/admin/SettingsTab.tsx:208 src/components/wholesale/admin/SettingsTab.tsx:213 src/components/wholesale/admin/SettingsTab.tsx:220 src/components/wholesale/admin/SettingsTab.tsx:270 src/components/wholesale/admin/WebhookSettingsSection.tsx:117 src/components/wholesale/admin/WebhookSettingsSection.tsx:128 ⚠ Performance: Independent awaits run sequentially ×5 Learn more: https://react.doctor/docs/rules/react-doctor/async-parallel These 3 sequential await statements run one after another even though they look independent, so the page waits longer than it needs to. Run them together with Promise.all() instead → Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` so independent calls run at the same time src/actions/water-log/settings.ts:162 src/app/admin/water-log/entries/[id]/page.tsx:17 src/app/admin/water-log/headgates/[id]/page.tsx:18 src/app/admin/water-log/users/[id]/page.tsx:18 src/components/admin/HarvestReach/CampaignComposerPage.tsx:387 ⚠ Bugs: Derived state stored in an effect ×5 Learn more: https://react.doctor/docs/rules/react-doctor/no-derived-state-effect Your users briefly see stale state on every prop change. → Work out derived values while rendering: `const x = fn(dep)`. To reset a component's state when a prop changes, give it a key prop: ``. See https://react.dev/learn/you-might-not-need-an-effect src/components/admin/AbandonedCartDashboard.tsx:61 src/components/admin/AdminHeader.tsx:74 src/components/admin/CommandPalette.tsx:219 src/components/admin/TimeTrackingAdminPanel.tsx:201 src/components/admin/WelcomeSequenceDashboard.tsx:50 ⚠ Performance: await inside a loop ×25 Learn more: https://react.doctor/docs/rules/react-doctor/async-await-in-loop This makes the for…of loop slow because each await runs one after another, so collect the independent calls & run them together with `await Promise.all(items.map(...))` → Collect the items, then use `await Promise.all(items.map(...))` so independent work runs at the same time src/actions/communications/contacts.ts:347 src/actions/import-products.ts:48 src/actions/square-orders.ts:108 src/app/admin/page.tsx:74 src/app/api/cron/send-scheduled/route.ts:47 src/app/api/cron/send-scheduled/route.ts:71 src/app/api/email-automation/abandoned-cart/route.ts:30 src/app/api/email-automation/abandoned-cart/route.ts:56 src/app/api/email-automation/welcome-sequence/route.ts:27 src/app/api/email-automation/welcome-sequence/route.ts:53 src/app/api/time-tracking/export/route.ts:81 src/app/api/time-tracking/notify/route.ts:246 src/app/api/water-qr-sheet/route.ts:29 src/app/api/wholesale/notifications/send/route.ts:79 src/app/api/wholesale/notifications/send/route.ts:121 src/app/api/wholesale/webhooks/dispatch/route.ts:98 src/components/admin/AdminOrdersPanel.tsx:305 src/components/admin/OrderEditForm.tsx:142 src/components/admin/OrderEditForm.tsx:157 src/components/admin/StopProductAssignment.tsx:178 src/components/admin/StopTableClient.tsx:231 src/components/route-trace/LotListTable.tsx:165 src/lib/billing.ts:82 src/lib/billing.ts:210 src/lib/offline/sync.ts:47 ⚠ Bugs: Prop derived into useState ×40 Learn more: https://react.doctor/docs/rules/react-doctor/no-derived-useState Your users see a stale value when prop "currentModel" changes because useState copies it once. → Compute the value inline so prop changes do not leave `useState` holding a stale copy. src/app/admin/settings/ai/AIClient.tsx:332 src/app/admin/settings/ai/AIClient.tsx:1806-1807 src/app/admin/settings/billing/BillingClient.tsx:19 src/app/admin/settings/billing/BillingClientPage.tsx:58 src/app/admin/settings/integrations/IntegrationsClient.tsx:467 src/app/admin/settings/integrations/IntegrationsClient.tsx:475 src/app/admin/settings/integrations/IntegrationsClientPage.tsx:355 src/app/admin/settings/square-sync/SquareSyncSettingsClient.tsx:52 src/components/admin/AdvancedIntegrations.tsx:59 src/components/admin/CommunicationsLoading.tsx:247 src/components/admin/HeadgateEditForm.tsx:26-28 src/components/admin/IntegrationsInner.tsx:278 src/components/admin/OrderEditForm.tsx:64 src/components/admin/OrderEditForm.tsx:70-71 src/components/admin/PaymentSettingsForm.tsx:35 src/components/admin/ProductEditForm.tsx:39-44 src/components/admin/StopEditForm.tsx:41-47 src/components/admin/StopProductAssignment.tsx:49 src/components/admin/WaterLogEntryEditForm.tsx:32-33 src/components/admin/WaterUserEditForm.tsx:25-28 src/components/admin/products/StockAdjustButton.tsx:33 ⚠ Bugs: useSearchParams without Suspense ×5 Learn more: https://react.doctor/docs/rules/react-doctor/nextjs-no-use-search-params-without-suspense uses useSearchParams() outside , so this page falls back to client-side rendering. → Wrap the component using `useSearchParams` in `` so the rest of the page can stay statically rendered. src/app/admin/v2/orders/page.tsx:54 src/app/admin/v2/orders/page.tsx:69 src/app/admin/v2/products/page.tsx:140 src/app/admin/v2/products/page.tsx:156 src/app/admin/v2/products/page.tsx:174 ⚠ Bugs: Unknown DOM property ×6 Learn more: https://react.doctor/docs/rules/react-doctor/no-unknown-property React ignores this prop because it doesn't recognize the name. → Use the prop name React expects, like `className`, `htmlFor`, or `tabIndex`, so the attribute is applied correctly. src/app/brands/page.tsx:225 src/components/landing/FeaturesAndStats.tsx:240 src/components/landing/HeroSection.tsx:988 src/components/landing/TestimonialsAndCTA.tsx:628 src/components/legal/CookieConsentBanner.tsx:66 src/components/storefront/TuxedoVideoHero.tsx:441 ⚠ Bugs: Client-side redirect for navigation ×9 Learn more: https://react.doctor/docs/rules/react-doctor/nextjs-no-client-side-redirect router.push() in useEffect flashes the wrong page before redirecting. → Avoid redirects inside useEffect. Use an event handler, middleware, or server-side redirect (App Router: redirect() from next/navigation; Pages Router: getServerSideProps redirect) src/app/logout/page.tsx:14 src/app/logout/page.tsx:18 src/app/wholesale/employee/page.tsx:54 src/app/wholesale/employee/page.tsx:69 src/app/wholesale/portal/page.tsx:307 src/app/wholesale/portal/page.tsx:316 src/app/wholesale/portal/page.tsx:322 src/app/wholesale/portal/page.tsx:328 src/components/route-trace/QRScanModal.tsx:158 ⚠ Bugs: Effects chained together ×2 Learn more: https://react.doctor/docs/rules/react-doctor/no-effect-chain Your screen redraws several times from a single action because one useEffect changes "selected", which sets off this one. → Compute as much as possible during render (e.g. `const isGameOver = round > 5`) and write all related state inside the event handler that originally fires the chain. Each effect link adds an extra render and makes the code rigid as requirements evolve src/components/admin/CommandPalette.tsx:224 src/context/CartContext.tsx:139 ⚠ Maintainability: Component rendered by inline function call ×3 Learn more: https://react.doctor/docs/rules/react-doctor/no-render-in-render Your users lose state because "renderMappings()" builds UI from an inline call that React remounts, so pull it into its own component instead. → Make it a named component so React preserves its identity and does not remount its state. src/components/admin/ContactImportForm.tsx:613 src/components/admin/ContactImportForm.tsx:620 src/components/admin/ContactImportForm.tsx:627 ⚠ Bugs: Hand-rolled external store subscription ×2 Learn more: https://react.doctor/docs/rules/react-doctor/prefer-use-sync-external-store Your users can see stale or torn values because useState "online" syncs an outside store through a useEffect. → Replace the `useState(getSnapshot())` + `useEffect(() => store.subscribe(() => setSnapshot(getSnapshot())))` pair with `useSyncExternalStore(store.subscribe, getSnapshot)`. The hook gets this right during concurrent rendering and on the server; the hand-rolled version doesn't. src/components/admin/OfflineBanner.tsx:7 src/components/admin/orders/OrdersFilterButton.tsx:20 ⚠ Performance: Unstable context provider value ×2 Learn more: https://react.doctor/docs/rules/react-doctor/jsx-no-constructed-context-values Every reader of this context redraws on each render because you build its `value` inline. → Wrap the context value in `useMemo` or move it outside the component so consumers do not redraw every render. src/components/admin/Toast.tsx:61 src/context/CartContext.tsx:313 ⚠ Performance: setState reads a stale value ×2 Learn more: https://react.doctor/docs/rules/react-doctor/rerender-functional-setstate You can lose this update because setCurrentStep(currentStep + ...) reads a stale value. → Use the callback form: `setState(prev => prev + 1)` to always read the latest value src/components/onboarding/OnboardingFlow.tsx:104 src/components/onboarding/OnboardingFlow.tsx:110 ⚠ Performance: array.find() inside a loop ×6 Learn more: https://react.doctor/docs/rules/react-doctor/js-index-maps This gets slow as your list grows because array.find() runs inside a loop, so build a Map once before the loop for instant lookups → Build a `Map` once before the loop instead of calling `array.find(...)` inside it src/app/api/time-tracking/export/route.ts:156 src/app/api/time-tracking/export/route.ts:196 src/app/api/time-tracking/export/route.ts:205 src/components/admin/OrderEditForm.tsx:151 src/components/admin/StopTableClient.tsx:229 src/lib/billing.ts:80 ⚠ Accessibility: Control missing accessible label ×444 Learn more: https://react.doctor/docs/rules/react-doctor/control-has-associated-label Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`. → Give every interactive control a label screen readers can read. src/app/admin/import/ImportCenterClient.tsx:192 src/app/admin/settings/ai/AIClient.tsx:362 src/app/admin/settings/ai/AIClient.tsx:888 src/app/admin/settings/ai/AIClient.tsx:898 src/app/admin/settings/ai/AIClient.tsx:929 src/app/admin/settings/ai/AIClient.tsx:937 src/app/admin/settings/ai/AIClient.tsx:947 src/app/admin/settings/ai/AIClient.tsx:1103 src/app/admin/settings/ai/AIClient.tsx:1107 src/app/admin/settings/ai/AIClient.tsx:1111 src/app/admin/settings/ai/AIClient.tsx:1115 src/app/admin/settings/ai/AIClient.tsx:1233 src/app/admin/settings/ai/AIClient.tsx:1391 src/app/admin/settings/ai/AIClient.tsx:1425 src/app/admin/settings/ai/AIClient.tsx:1436 src/app/admin/settings/ai/AIClient.tsx:1447 src/app/admin/settings/ai/AIClient.tsx:1458 src/app/admin/settings/ai/AIClient.tsx:1470 src/app/admin/settings/ai/AIClient.tsx:1626 src/app/admin/settings/ai/AIClient.tsx:1630 src/app/admin/settings/ai/AIClient.tsx:1649 src/app/admin/settings/ai/AIClient.tsx:1657 src/app/admin/settings/ai/AIClient.tsx:1665 src/app/admin/settings/ai/AIClient.tsx:1773 src/app/admin/settings/billing/BillingClientPage.tsx:407 src/app/admin/settings/integrations/IntegrationsClient.tsx:312 src/app/admin/settings/integrations/IntegrationsClient.tsx:567 src/app/admin/settings/integrations/IntegrationsClient.tsx:616 src/app/admin/settings/integrations/IntegrationsClient.tsx:622 src/app/admin/water-log/headgates/HeadgatesManager.tsx:271-272 src/app/admin/water-log/headgates/HeadgatesManager.tsx:285-286 src/app/admin/water-log/headgates/HeadgatesManager.tsx:369 src/app/admin/water-log/headgates/HeadgatesManager.tsx:507 src/app/blog/page.tsx:244 src/app/change-password/ChangePasswordForm.tsx:87 src/app/change-password/ChangePasswordForm.tsx:103 src/app/change-password/page.tsx:78 src/app/change-password/page.tsx:94 src/app/indian-river-direct/contact/ContactClientPage.tsx:165 src/app/indian-river-direct/contact/ContactClientPage.tsx:176 src/app/indian-river-direct/contact/ContactClientPage.tsx:202 src/app/indian-river-direct/faq/FAQClientPage.tsx:171 src/app/indian-river-direct/faq/FAQClientPage.tsx:179 src/app/indian-river-direct/page.tsx:457 src/app/maintenance/page.tsx:62 src/app/maintenance/page.tsx:67 src/app/reset-password/page.tsx:102 src/app/reset-password/page.tsx:117 src/app/tuxedo/contact/ContactClientPage.tsx:134 src/app/tuxedo/contact/ContactClientPage.tsx:144 src/app/tuxedo/contact/ContactClientPage.tsx:169 src/app/tuxedo/faq/FAQClientPage.tsx:160 src/app/tuxedo/faq/FAQClientPage.tsx:168 src/app/water/admin/login/WaterAdminPinClient.tsx:41 src/app/wholesale/employee/page.tsx:432 src/app/wholesale/employee/page.tsx:442 src/app/wholesale/login/page.tsx:256 src/app/wholesale/login/page.tsx:269 src/app/wholesale/portal/page.tsx:117 src/app/wholesale/portal/page.tsx:184 src/app/wholesale/portal/page.tsx:192 src/app/wholesale/portal/page.tsx:749 src/app/wholesale/portal/page.tsx:818 src/app/wholesale/register/page.tsx:214 src/app/wholesale/register/page.tsx:225 src/app/wholesale/register/page.tsx:236 src/app/wholesale/register/page.tsx:249 src/components/admin/AIProviderPanel.tsx:233 src/components/admin/AIProviderPanel.tsx:254 src/components/admin/AIProviderPanel.tsx:283 src/components/admin/AIProviderPanel.tsx:297 src/components/admin/AbandonedCartDashboard.tsx:114 src/components/admin/AbandonedCartDashboard.tsx:119 src/components/admin/AddLocationModal.tsx:122 src/components/admin/AddLocationModal.tsx:137 src/components/admin/AddLocationModal.tsx:152 src/components/admin/AddLocationModal.tsx:165 src/components/admin/AddLocationModal.tsx:179 src/components/admin/AddLocationModal.tsx:195 src/components/admin/AddLocationModal.tsx:208 src/components/admin/AddLocationModal.tsx:223 src/components/admin/AddLocationModal.tsx:237 src/components/admin/AdminOrdersPanel.tsx:635-636 src/components/admin/AdminOrdersPanel.tsx:698-699 src/components/admin/AdminOrdersPanel.tsx:1005 src/components/admin/AdminOrdersPanel.tsx:1019 src/components/admin/AdminStopsPanel.tsx:150 src/components/admin/AdminStopsPanel.tsx:204 src/components/admin/AdminStopsPanel.tsx:246 src/components/admin/AdminStopsPanel.tsx:320 src/components/admin/AdminStopsPanel.tsx:330 src/components/admin/AdvancedAIPanel.tsx:394 src/components/admin/AdvancedAIPanel.tsx:421 src/components/admin/AdvancedAIPanel.tsx:450 src/components/admin/AdvancedAIPanel.tsx:466 src/components/admin/AdvancedIntegrations.tsx:200 src/components/admin/AdvancedIntegrations.tsx:222 src/components/admin/AdvancedIntegrations.tsx:236 src/components/admin/AdvancedIntegrations.tsx:294 src/components/admin/AdvancedIntegrations.tsx:309 src/components/admin/AdvancedIntegrations.tsx:331 src/components/admin/AdvancedShipping.tsx:124 src/components/admin/AdvancedShipping.tsx:137 src/components/admin/AdvancedShipping.tsx:202 src/components/admin/AdvancedSquareSync.tsx:100 src/components/admin/AdvancedSquareSync.tsx:110 src/components/admin/AdvancedSquareSync.tsx:120 src/components/admin/AdvancedSquareSync.tsx:174 src/components/admin/BrandSettingsForm.tsx:493 src/components/admin/BrandSettingsForm.tsx:499 src/components/admin/BrandSettingsForm.tsx:512 src/components/admin/BrandSettingsForm.tsx:518 src/components/admin/BrandSettingsForm.tsx:531 src/components/admin/BrandSettingsForm.tsx:537 src/components/admin/BrandSettingsForm.tsx:550 src/components/admin/BrandSettingsForm.tsx:556 src/components/admin/BrandSettingsForm.tsx:707 src/components/admin/BrandSettingsForm.tsx:775 src/components/admin/BrandSettingsForm.tsx:914 src/components/admin/CampaignListPanel.tsx:152 src/components/admin/CampaignListPanel.tsx:285 src/components/admin/CampaignListPanel.tsx:483 src/components/admin/CampaignListPanel.tsx:564 src/components/admin/CampaignListPanel.tsx:574 src/components/admin/CampaignListPanel.tsx:583 src/components/admin/CampaignListPanel.tsx:617 src/components/admin/CampaignListPanel.tsx:627 src/components/admin/CampaignListPanel.tsx:663 src/components/admin/ContactListPanel.tsx:102-118 src/components/admin/ContactListPanel.tsx:243 src/components/admin/ContactListPanel.tsx:282 src/components/admin/ContactListPanel.tsx:307 src/components/admin/CreateUserModal.tsx:294 src/components/admin/DriverPickupPanel.tsx:177 src/components/admin/EditLocationModal.tsx:141 src/components/admin/EditLocationModal.tsx:155 src/components/admin/EditLocationModal.tsx:169 src/components/admin/EditLocationModal.tsx:181 src/components/admin/EditLocationModal.tsx:194 src/components/admin/EditLocationModal.tsx:209 src/components/admin/EditLocationModal.tsx:221 src/components/admin/EditLocationModal.tsx:235 src/components/admin/EditLocationModal.tsx:248 src/components/admin/ElegantModal.tsx:68 src/components/admin/HarvestReach/CampaignComposerPage.tsx:504 src/components/admin/HarvestReach/CampaignComposerPage.tsx:516 src/components/admin/HarvestReach/CampaignComposerPage.tsx:570 src/components/admin/HarvestReach/CampaignComposerPage.tsx:776 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:331 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:340 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:370 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:386 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:400 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:426 src/components/admin/HarvestReach/SegmentBuilderPanel.tsx:441 src/components/admin/HarvestReach/SegmentEditModal.tsx:78 src/components/admin/HarvestReach/SegmentEditModal.tsx:92 src/components/admin/IntegrationsInner.tsx:228 src/components/admin/LocationsTab.tsx:224 src/components/admin/LocationsTab.tsx:333 src/components/admin/MessageCustomersSection.tsx:196 src/components/admin/MessageCustomersSection.tsx:210 src/components/admin/MessageLogPanel.tsx:132-148 src/components/admin/MessageLogPanel.tsx:322 src/components/admin/NewProductForm.tsx:277 src/components/admin/NewStopForm.tsx:131 src/components/admin/NewStopForm.tsx:156 src/components/admin/NewStopForm.tsx:182 src/components/admin/NewStopForm.tsx:208 src/components/admin/NewStopForm.tsx:232 src/components/admin/NewStopForm.tsx:296 src/components/admin/NewStopForm.tsx:307 src/components/admin/NewStopForm.tsx:321 src/components/admin/OrderEditForm.tsx:268 src/components/admin/OrderEditForm.tsx:287 src/components/admin/OrderEditForm.tsx:316 src/components/admin/OrderEditForm.tsx:336 src/components/admin/OrderEditForm.tsx:358 src/components/admin/OrderEditForm.tsx:399 src/components/admin/OrderPaymentSection.tsx:206 src/components/admin/OrderPaymentSection.tsx:280 src/components/admin/OrderPaymentSection.tsx:304 src/components/admin/ProductEditForm.tsx:290 src/components/admin/ProductFilterBar.tsx:25 src/components/admin/ProductFormModal.tsx:415 src/components/admin/ProductTableBody.tsx:72 src/components/admin/ProductTableBody.tsx:175 src/components/admin/ProductTableClient.tsx:55 src/components/admin/ProductTableClient.tsx:99 src/components/admin/ProductTableClient.tsx:220 src/components/admin/ProductsClient.tsx:435 src/components/admin/ProductsClient.tsx:684 src/components/admin/ReportsDashboard.tsx:345 src/components/admin/ReportsDashboard.tsx:352 src/components/admin/ReportsDashboard.tsx:672 src/components/admin/ScheduleImportModal.tsx:190 src/components/admin/ScheduleImportModal.tsx:249 src/components/admin/ScheduleImportModal.tsx:254-256 src/components/admin/ScheduleImportModal.tsx:262-263 src/components/admin/ScheduleImportModal.tsx:269-270 src/components/admin/ScheduleImportModal.tsx:276-277 src/components/admin/ScheduleImportModal.tsx:283-284 src/components/admin/ScheduleImportModal.tsx:290-291 src/components/admin/SettingsSections.tsx:325 src/components/admin/SettingsSections.tsx:334 src/components/admin/SettingsSections.tsx:343 src/components/admin/SettingsSections.tsx:399 src/components/admin/SettingsSections.tsx:410 src/components/admin/SettingsSections.tsx:422 src/components/admin/SettingsSections.tsx:433 src/components/admin/SettingsSections.tsx:607 src/components/admin/SettingsSections.tsx:624 src/components/admin/SettingsSections.tsx:679 src/components/admin/SettingsSections.tsx:689 src/components/admin/SettingsSections.tsx:695 src/components/admin/SettingsSections.tsx:711 src/components/admin/ShippingFulfillmentPanel.tsx:509 src/components/admin/StopEditForm.tsx:124 src/components/admin/StopEditForm.tsx:149 src/components/admin/StopEditForm.tsx:176 src/components/admin/StopEditForm.tsx:198 src/components/admin/StopEditForm.tsx:210 src/components/admin/StopEditForm.tsx:222 src/components/admin/StopEditForm.tsx:233 src/components/admin/StopEditForm.tsx:248 src/components/admin/StopMessagingForm.tsx:171 src/components/admin/StopProductAssignment.tsx:235 src/components/admin/StopTableClient.tsx:548 src/components/admin/StopTableClient.tsx:1055 src/components/admin/TaxDashboard.tsx:267 src/components/admin/TaxDashboard.tsx:274 src/components/admin/TemplateEditForm.tsx:171 src/components/admin/TemplateEditForm.tsx:267 src/components/admin/TemplateEditForm.tsx:449 src/components/admin/TemplateEditForm.tsx:491 src/components/admin/TemplateEditForm.tsx:552 src/components/admin/TemplateEditForm.tsx:562 src/components/admin/TemplateEditForm.tsx:575 src/components/admin/TimeTrackingAdminPanel.tsx:420 src/components/admin/TimeTrackingAdminPanel.tsx:429 src/components/admin/TimeTrackingAdminPanel.tsx:553 src/components/admin/TimeTrackingAdminPanel.tsx:603 src/components/admin/TimeTrackingAdminPanel.tsx:655 src/components/admin/TimeTrackingAdminPanel.tsx:664 src/components/admin/TimeTrackingAdminPanel.tsx:802 src/components/admin/TimeTrackingAdminPanel.tsx:887 src/components/admin/TimeTrackingAdminPanel.tsx:891 src/components/admin/TimeTrackingSettingsClient.tsx:376-379 src/components/admin/TimeTrackingSettingsClient.tsx:534 src/components/admin/TimeTrackingSettingsClient.tsx:543 src/components/admin/TimeTrackingSettingsClient.tsx:552 src/components/admin/TimeTrackingSettingsClient.tsx:581 src/components/admin/TimeTrackingSettingsClient.tsx:591 src/components/admin/TimeTrackingSettingsClient.tsx:601 src/components/admin/TimeTrackingSettingsClient.tsx:615 src/components/admin/TimeTrackingSettingsClient.tsx:634 src/components/admin/TimeTrackingSettingsClient.tsx:683 src/components/admin/TimeTrackingSettingsClient.tsx:742 src/components/admin/TimeTrackingSettingsClient.tsx:748 src/components/admin/TimeTrackingSettingsClient.tsx:764 src/components/admin/UsersPage.tsx:370 src/components/admin/UsersPage.tsx:388 src/components/admin/UsersPage.tsx:520 src/components/admin/UsersPage.tsx:543 src/components/admin/UsersPage.tsx:558 src/components/admin/UsersPage.tsx:573 src/components/admin/UsersPage.tsx:586 src/components/admin/UsersPage.tsx:646 src/components/admin/WaterLogAdminPanel.tsx:848 src/components/admin/WaterLogAdminPanel.tsx:1261 src/components/admin/WaterUserEditForm.tsx:91 src/components/admin/WelcomeSequenceDashboard.tsx:94 src/components/admin/WelcomeSequenceDashboard.tsx:99 src/components/admin/design-system/AdminActionMenu.tsx:40 src/components/admin/design-system/AdminFilterBar.tsx:52 src/components/admin/design-system/AdminFilterBar.tsx:90 src/components/admin/design-system/AdminFilterBar.tsx:102 src/components/admin/design-system/AdminModal.tsx:52 src/components/admin/design-system/AdminPagination.tsx:20 src/components/admin/design-system/AdminPagination.tsx:68 src/components/admin/design-system/AdminPagination.tsx:101 src/components/admin/design-system/AdminPagination.tsx:110 src/components/admin/design-system/AdminToggle.tsx:83 src/components/admin/shared/DataTable.tsx:100 src/components/admin/shared/DataTable.tsx:109 src/components/admin/shared/FilterBar.tsx:47 src/components/admin/stops/StopsLocations.tsx:186 src/components/legal/CookieConsentBanner.tsx:111 src/components/legal/CookieConsentBanner.tsx:129 src/components/notifications/ToastNotification.tsx:110 src/components/route-trace/AdminLookupPage.tsx:94 src/components/route-trace/FsmaReportModal.tsx:281 src/components/route-trace/FsmaReportModal.tsx:300 src/components/route-trace/FsmaReportModal.tsx:311 src/components/route-trace/FsmaReportModal.tsx:384 src/components/route-trace/LotCreateForm.tsx:98 src/components/route-trace/LotCreateForm.tsx:109 src/components/route-trace/LotCreateForm.tsx:121 src/components/route-trace/LotCreateForm.tsx:136 src/components/route-trace/LotCreateForm.tsx:148 src/components/route-trace/LotCreateForm.tsx:158 src/components/route-trace/LotCreateForm.tsx:176 src/components/route-trace/LotCreateForm.tsx:189 src/components/route-trace/LotCreateForm.tsx:214 src/components/route-trace/LotCreateForm.tsx:232 src/components/route-trace/LotCreateForm.tsx:242 src/components/route-trace/LotCreateForm.tsx:254 src/components/route-trace/LotCreateForm.tsx:265 src/components/route-trace/LotCreateForm.tsx:287 src/components/route-trace/LotCreateModal.tsx:121 src/components/route-trace/LotCreateModal.tsx:150 src/components/route-trace/LotCreateModal.tsx:166 src/components/route-trace/LotCreateModal.tsx:183 src/components/route-trace/LotCreateModal.tsx:203 src/components/route-trace/LotCreateModal.tsx:220 src/components/route-trace/LotCreateModal.tsx:235 src/components/route-trace/LotCreateModal.tsx:258 src/components/route-trace/LotCreateModal.tsx:276 src/components/route-trace/LotCreateModal.tsx:307 src/components/route-trace/LotCreateModal.tsx:330 src/components/route-trace/LotCreateModal.tsx:345 src/components/route-trace/LotCreateModal.tsx:362 src/components/route-trace/LotCreateModal.tsx:378 src/components/route-trace/LotCreateModal.tsx:408 src/components/route-trace/LotDetailPanel.tsx:773 src/components/route-trace/LotDetailPanel.tsx:783 src/components/route-trace/LotDetailPanel.tsx:824 src/components/route-trace/LotDetailPanel.tsx:835 src/components/route-trace/LotDetailPanel.tsx:878 src/components/route-trace/LotDetailPanel.tsx:889 src/components/route-trace/LotDetailPanel.tsx:900 src/components/route-trace/LotListTable.tsx:215 src/components/route-trace/LotListTable.tsx:300-301 src/components/route-trace/LotListTable.tsx:314 src/components/route-trace/LotListTable.tsx:321-322 src/components/route-trace/QRScanModal.tsx:241 src/components/route-trace/QRScanModal.tsx:277 src/components/route-trace/QuickNewLotModal.tsx:85 src/components/route-trace/QuickNewLotModal.tsx:100 src/components/route-trace/QuickNewLotModal.tsx:112 src/components/route-trace/QuickNewLotModal.tsx:126 src/components/route-trace/QuickNewLotModal.tsx:138 src/components/route-trace/QuickNewLotModal.tsx:152 src/components/route-trace/RouteTraceDashboard.tsx:630 src/components/route-trace/RouteTraceSettings.tsx:69 src/components/route-trace/RouteTraceSettings.tsx:95 src/components/route-trace/RouteTraceSettings.tsx:121 src/components/route-trace/StickerPreviewModal.tsx:93 src/components/storefront/HeroSection.tsx:91 src/components/storefront/IndianRiverPaginatedStops.tsx:115 src/components/storefront/PaginatedStops.tsx:116 src/components/storefront/TuxedoVideoHero.tsx:173 src/components/storefront/ZipCodeSearch.tsx:51 src/components/time-tracking/TimeTrackingFieldClient.tsx:772 src/components/time-tracking/TimeTrackingFieldClient.tsx:787 src/components/time-tracking/TimeTrackingFieldClient.tsx:920 src/components/water/WaterAdminClient.tsx:495 src/components/water/WaterAdminClient.tsx:596 src/components/water/WaterAdminClient.tsx:678 src/components/water/WaterAdminClient.tsx:684 src/components/water/WaterAdminClient.tsx:733-738 src/components/water/WaterFieldClient.tsx:455 src/components/water/WaterFieldClient.tsx:606 src/components/water/WaterFieldClient.tsx:696 src/components/wholesale/DepositModal.tsx:59 src/components/wholesale/OrderDetailsModal.tsx:40 src/components/wholesale/admin/AddRecipientForm.tsx:29 src/components/wholesale/admin/AddRecipientForm.tsx:36 src/components/wholesale/admin/CustomerPricingPanel.tsx:55 src/components/wholesale/admin/CustomerPricingPanel.tsx:73 src/components/wholesale/admin/CustomerPricingPanel.tsx:87 src/components/wholesale/admin/CustomersTab.tsx:247 src/components/wholesale/admin/CustomersTab.tsx:371-372 src/components/wholesale/admin/CustomersTab.tsx:383 src/components/wholesale/admin/CustomersTab.tsx:403-404 src/components/wholesale/admin/CustomersTab.tsx:425 src/components/wholesale/admin/CustomersTab.tsx:433 src/components/wholesale/admin/OrdersTab.tsx:224 src/components/wholesale/admin/OrdersTab.tsx:229 src/components/wholesale/admin/OrdersTab.tsx:295-296 src/components/wholesale/admin/OrdersTab.tsx:314-315 src/components/wholesale/admin/OrdersTab.tsx:362 src/components/wholesale/admin/OrdersTab.tsx:374 src/components/wholesale/admin/OrdersTab.tsx:468 src/components/wholesale/admin/OrdersTab.tsx:502 src/components/wholesale/admin/PriceSheetModal.tsx:40 src/components/wholesale/admin/ProductsTab.tsx:155 src/components/wholesale/admin/ProductsTab.tsx:160 src/components/wholesale/admin/ProductsTab.tsx:167 src/components/wholesale/admin/ProductsTab.tsx:192 src/components/wholesale/admin/ProductsTab.tsx:224 src/components/wholesale/admin/SettingsTab.tsx:125 src/components/wholesale/admin/SettingsTab.tsx:138 src/components/wholesale/admin/SettingsTab.tsx:163 src/components/wholesale/admin/SettingsTab.tsx:174 src/components/wholesale/admin/SettingsTab.tsx:179 src/components/wholesale/admin/SettingsTab.tsx:184 src/components/wholesale/admin/SettingsTab.tsx:189 src/components/wholesale/admin/SettingsTab.tsx:194 src/components/wholesale/admin/SettingsTab.tsx:199 src/components/wholesale/admin/SettingsTab.tsx:204 src/components/wholesale/admin/SettingsTab.tsx:209 src/components/wholesale/admin/SettingsTab.tsx:214 src/components/wholesale/admin/SettingsTab.tsx:239 src/components/wholesale/admin/SettingsTab.tsx:249 src/components/wholesale/admin/SettingsTab.tsx:254 src/components/wholesale/admin/WebhookSettingsSection.tsx:108 src/components/wholesale/admin/WebhookSettingsSection.tsx:118 src/components/wholesale/admin/WebhookSettingsSection.tsx:131 ⚠ Bugs: Data fetching inside an effect ×3 Learn more: https://react.doctor/docs/rules/react-doctor/no-fetch-in-effect fetch() inside useEffect can race, double-fire, or leak. Use a data-fetching layer or Server Component instead. → Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`. src/app/logout/page.tsx:9 src/components/admin/AIProviderPanel.tsx:110 src/components/admin/AdvancedAIPanel.tsx:206 ⚠ Accessibility: outline:none removes focus ring ×2 Learn more: https://react.doctor/docs/rules/react-doctor/no-outline-none Your keyboard users can't tell where they are because outline: none hides the focus ring, so style :focus-visible instead, or add a box-shadow focus ring. → Style `:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px }` so the focus ring shows for keyboard users but not mouse clicks. src/components/admin/CommandPalette.tsx:344 src/components/admin/stops/StopsDatePicker.tsx:61 ⚠ Performance: sort() to find min or max ×2 Learn more: https://react.doctor/docs/rules/react-doctor/js-min-max-loop This is slow because array.sort()[0] sorts the whole list just to grab the smallest or largest, so use Math.min(...array) instead → Use `Math.min(...array)` or `Math.max(...array)` instead of sorting the whole list just to read the first or last item src/actions/ai-import.ts:266 src/components/admin/HarvestReach/AnalyticsDashboard.tsx:223 ⚠ Bugs: Plain anchor reloads internal Next.js links ×36 Learn more: https://react.doctor/docs/rules/react-doctor/nextjs-no-a-element Plain reloads the whole page for internal links, so Next.js loses client-side navigation and prefetching. → `import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position src/app/admin/communications/abandoned-carts/page.tsx:27-29 src/app/admin/communications/settings/page.tsx:24 src/app/admin/communications/welcome-sequence/page.tsx:26-28 src/app/admin/settings/ai/AIClient.tsx:1830-1832 src/app/admin/settings/billing/BillingClientPage.tsx:501 src/app/admin/settings/integrations/IntegrationsClientPage.tsx:413 src/app/brands/page.tsx:217-219 src/app/changelog/page.tsx:195 src/app/changelog/page.tsx:201 src/app/login/LoginClient.tsx:203 src/app/protected-example/page.tsx:36 src/app/protected-example/page.tsx:96 src/app/tuxedo/page.tsx:692 src/app/tuxedo/page.tsx:725 src/app/tuxedo/stops/TuxedoStopsList.tsx:75 src/app/waitlist/page.tsx:124-125 src/components/admin/CampaignListPanel.tsx:699 src/components/admin/DashboardClient.tsx:181 src/components/admin/DashboardHeader.tsx:36 src/components/admin/IntegrationsInner.tsx:311 src/components/admin/PaymentSettingsForm.tsx:319 src/components/admin/PaymentSettingsForm.tsx:382 src/components/admin/WaterLogAdminPanel.tsx:320 src/components/landing/LandingPageWrapper.tsx:85 src/components/landing/LandingPageWrapper.tsx:95 src/components/landing/LandingPageWrapper.tsx:175 src/components/landing/LandingPageWrapper.tsx:185 src/components/legal/CookieConsentBanner.tsx:167-169 src/components/notifications/InAppNotificationCenter.tsx:200 ⚠ Bugs: Plain img ships unoptimized images ×6 Learn more: https://react.doctor/docs/rules/react-doctor/nextjs-no-img-element Plain ships unoptimized, oversized images. → Use `next/image` so users get optimized formats, responsive srcsets, and lazy loading instead of oversized image downloads. src/app/admin/v2/products/page.tsx:192 src/app/admin/water-log/headgates/HeadgatesManager.tsx:328 src/app/admin/water-log/headgates/HeadgatesManager.tsx:521 src/app/indian-river-direct/about/page.tsx:116 src/app/tuxedo/about/AboutClient.tsx:112 src/components/route-trace/StickerPreviewModal.tsx:249 ⚠ Bugs: Effect re-subscribes on a changing callback ×7 Learn more: https://react.doctor/docs/rules/react-doctor/prefer-use-effect-event Your effect re-subscribes whenever "closeMobileMenu" changes, even though it's only used inside `addEventListener`. → Wrap the callback with `useEffectEvent(callback)` (React 19+) and call it inside the sub-handler. An Effect Event always sees the latest props and state but isn't a dependency, so the effect won't re-subscribe every time the parent redraws. See https://react.dev/reference/react/useEffectEvent src/components/admin/AdminSidebar.tsx:219 src/components/admin/AnalyticsDashboard.tsx:405 src/components/admin/HarvestReach/MatchingCustomersPanel.tsx:72 src/components/admin/ProductFormModal.tsx:164 src/components/admin/UpgradePlanModal.tsx:119 src/components/route-trace/QRScanModal.tsx:179 src/components/storefront/QuickCartSheet.tsx:64 ⚠ Bugs: Derived value copied into state ×34 Learn more: https://react.doctor/docs/rules/react-doctor/no-derived-state Storing "dirty" in state when you can derive it from other values costs an extra render. → Work out the value while rendering (or with useMemo if it's expensive) instead of copying it into useState through a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state src/app/admin/settings/integrations/IntegrationsClientPage.tsx:127 src/components/admin/AddStopModal.tsx:52-59 src/components/admin/CommandPalette.tsx:226 src/components/admin/EditLocationModal.tsx:47-55 src/components/admin/EditStopModal.tsx:65-73 src/components/admin/PaymentSettingsForm.tsx:106 src/components/admin/ReportsDashboard.tsx:313 src/components/admin/SettingsSections.tsx:167 src/components/admin/ShippingSettingsForm.tsx:74 src/components/admin/TimeTrackingAdminPanel.tsx:200 src/components/admin/TimeTrackingSettingsClient.tsx:167 ⚠ Bugs: Multiple setState calls in one effect ×11 Learn more: https://react.doctor/docs/rules/react-doctor/no-cascading-set-state 4 setState calls in one useEffect redraw your screen each time they run together. → Combine related updates in `useReducer` so one effect does not redraw the screen once per `setState` call. src/app/cart/CartClient.tsx:46 src/app/wholesale/employee/page.tsx:44 src/components/admin/AddStopModal.tsx:49 src/components/admin/BrandSettingsForm.tsx:78 src/components/admin/EditLocationModal.tsx:44 src/components/admin/EditStopModal.tsx:55 src/components/admin/HarvestReach/MatchingCustomersPanel.tsx:53 src/components/admin/MessageCustomersSection.tsx:33 src/components/admin/ShippingSettingsForm.tsx:79 src/components/admin/StopDetailModal.tsx:37 src/components/route-trace/LotCreateModal.tsx:37 ⚠ Bugs: preventDefault on a form or link ×3 Learn more: https://react.doctor/docs/rules/react-doctor/no-prevent-default Your users can't submit this
without JavaScript because onSubmit calls preventDefault(), so use a server action like `` to make it work either way. → Use `` where your framework supports it (it works without JS), or use a `