feat(storage): MinIO object storage, Neon Auth, Supabase removal
Deploy to route.crispygoat.com / deploy (push) Failing after 3m1s
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:
@@ -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 |
Reference in New Issue
Block a user