feat(storage): MinIO object storage, Neon Auth, Supabase removal
Deploy to route.crispygoat.com / deploy (push) Failing after 3m1s

- Add MinIO/S3-compatible storage client (src/lib/storage.ts) with uploadObject,
  deleteObject, presigned URL helpers, and BUCKETS constant
- Wire product images, brand logos, and water log photos to MinIO via the
  new storage client
- Migrate forgot-password to Neon Auth (remove Supabase /auth/v1/recover call)
- Migrate send-scheduled cron to direct Postgres + Resend (remove Supabase Edge
  Function proxy)
- Add logoUrl to email types (OrderReceipt, Welcome, PasswordReset) and pass
  brand_settings.logo_url from all call sites
- Update email templates to use dynamic logoUrl instead of hardcoded Supabase
  bucket URLs
- Remove hardcoded Supabase URLs from TuxedoVideoHero, TuxedoAboutPage,
  TimeTrackingFieldClient; use brand_settings props + local public/ fallback
- Download brand logos (3) and tuxedo-hero.mp4 (36MB) from Supabase bucket to
  public/ for local development
- Add MinIO env vars to .env.example (endpoint, access key, secret, buckets)
- Fix TimeTrackingFieldClient to destructure logoUrl and brandAccent props
- Fix admin/users.ts logoUrl type (null → undefined for optional string)
- Remove stale sb- cookie from wholesale-auth
- Migrate tuxedo/about page to remove supabase import and use pool query for
  wholesale_settings lookup
This commit is contained in:
openclaw
2026-06-09 11:32:17 -06:00
parent bb464bda65
commit 916ad39176
349 changed files with 6706 additions and 3343 deletions
+139
View File
@@ -0,0 +1,139 @@
import { test, expect } from "@playwright/test";
const BASE = process.env.PLAYWRIGHT_URL ?? "http://localhost:3000";
test.describe("Console Error Checks", () => {
const pagesToTest = [
{ path: "/", name: "Homepage" },
{ path: "/tuxedo", name: "Tuxedo Storefront" },
{ path: "/indian-river-direct", name: "Indian River Direct" },
{ path: "/admin", name: "Admin (unauthenticated)" },
{ path: "/login", name: "Login" },
{ path: "/cart", name: "Cart" },
{ path: "/pricing", name: "Pricing" },
{ path: "/water", name: "Water Log" },
];
for (const { path, name } of pagesToTest) {
test(`${name} (${path}) - no console errors`, async ({ page }) => {
const errors: string[] = [];
page.on("console", (msg) => {
if (msg.type() === "error") {
const text = msg.text();
// Ignore known benign errors
if (
text.includes("favicon") ||
text.includes("Warning:") ||
text.includes("Hydration") ||
text.includes("download the React DevTools") ||
text.includes("404")
) {
return;
}
errors.push(text);
}
});
await page.goto(`${BASE}${path}`, { waitUntil: "domcontentloaded" });
// Wait a bit for any async errors
await page.waitForTimeout(2000);
// Filter out any warnings about smooth scroll (non-critical)
const criticalErrors = errors.filter(e =>
!e.includes("scroll-behavior") &&
!e.includes("setState") &&
!e.includes("Can't perform a React state update")
);
if (criticalErrors.length > 0) {
console.log("Console errors found:", criticalErrors);
}
expect(criticalErrors).toHaveLength(0);
});
}
test("Tax Dashboard - no setState during render errors", async ({ page }) => {
const errors: string[] = [];
page.on("console", (msg) => {
if (msg.type() === "error") {
const text = msg.text();
if (
text.includes("Cannot update a component") ||
text.includes("setState") ||
text.includes("Can't perform a React state update")
) {
errors.push(text);
}
}
});
// Navigate to taxes page (will redirect to login if unauthenticated)
await page.goto(`${BASE}/admin/taxes`, { waitUntil: "domcontentloaded" });
await page.waitForTimeout(2000);
expect(errors).toHaveLength(0);
});
test("Dashboard - no router warnings", async ({ page }) => {
const errors: string[] = [];
page.on("console", (msg) => {
if (msg.type() === "error") {
const text = msg.text();
if (
text.includes("Cannot update a component") ||
text.includes("Router")
) {
errors.push(text);
}
}
});
await page.goto(`${BASE}/admin`, { waitUntil: "domcontentloaded" });
await page.waitForTimeout(2000);
expect(errors).toHaveLength(0);
});
});
test.describe("Navigation Tests", () => {
test("Admin navigation - no errors when clicking links", async ({ page }) => {
const errors: string[] = [];
page.on("console", (msg) => {
if (msg.type() === "error") {
errors.push(msg.text());
}
});
// Login first
await page.goto(`${BASE}/login`, { waitUntil: "domcontentloaded" });
await page.waitForTimeout(1000);
// Check if we're on login page or already authenticated
const onLoginPage = page.url().includes("/login");
if (onLoginPage) {
// Try to see if we can proceed (may fail due to no auth)
await page.waitForTimeout(500);
}
// Navigate to various pages
const paths = ["/", "/tuxedo", "/pricing", "/contact"];
for (const path of paths) {
await page.goto(`${BASE}${path}`, { waitUntil: "domcontentloaded" });
await page.waitForTimeout(500);
}
// Filter out non-critical errors
const criticalErrors = errors.filter(e =>
!e.includes("favicon") &&
!e.includes("Warning:") &&
!e.includes("404") &&
!e.includes("net::ERR")
);
expect(criticalErrors).toHaveLength(0);
});
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB