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:
@@ -37,10 +37,17 @@ export default function CheckoutClient() {
|
||||
const [hostedError, setHostedError] = useState<string | null>(null);
|
||||
|
||||
// Stable idempotency key per checkout session — survives retries
|
||||
const idempotencyKeyRef = useRef<string | null>(null);
|
||||
if (typeof window !== "undefined" && !idempotencyKeyRef.current) {
|
||||
idempotencyKeyRef.current = crypto.randomUUID();
|
||||
}
|
||||
const [idempotencyKey, setIdempotencyKey] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize idempotency key in useEffect, not during render
|
||||
const init = async () => {
|
||||
if (typeof window !== "undefined" && !idempotencyKey) {
|
||||
setIdempotencyKey(crypto.randomUUID());
|
||||
}
|
||||
};
|
||||
init();
|
||||
}, [idempotencyKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!cartBrandId) return;
|
||||
@@ -112,7 +119,7 @@ export default function CheckoutClient() {
|
||||
shippingAddress: hasShipItems
|
||||
? { state: shippingState, postal_code: shippingPostal, city: shippingCity }
|
||||
: undefined,
|
||||
idempotencyKey: idempotencyKeyRef.current,
|
||||
idempotencyKey: idempotencyKey,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -408,7 +415,7 @@ export default function CheckoutClient() {
|
||||
shippingState={shippingState}
|
||||
shippingPostal={shippingPostal}
|
||||
shippingCity={shippingCity}
|
||||
checkoutSessionKey={idempotencyKeyRef.current ?? undefined}
|
||||
checkoutSessionKey={idempotencyKey || undefined}
|
||||
onHostedCheckout={() => void handleHostedCheckout()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -74,14 +74,14 @@ function SuccessContent() {
|
||||
// embedded Stripe Elements (?payment_intent=...). Both paths store the
|
||||
// same payload in `pending_checkout` before payment is initiated.
|
||||
useEffect(() => {
|
||||
if (!sessionId && !paymentIntentId) return;
|
||||
if (paymentIntentId && redirectStatus && redirectStatus !== "succeeded") {
|
||||
setError("Payment was not completed. Please try again.");
|
||||
setCreating(false);
|
||||
return;
|
||||
}
|
||||
const init = async () => {
|
||||
if (!sessionId && !paymentIntentId) return;
|
||||
if (paymentIntentId && redirectStatus && redirectStatus !== "succeeded") {
|
||||
setError("Payment was not completed. Please try again.");
|
||||
setCreating(false);
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const pendingStr = sessionStorage.getItem("pending_checkout");
|
||||
if (!pendingStr) {
|
||||
setError("No pending order found. Please start checkout again.");
|
||||
@@ -133,7 +133,8 @@ function SuccessContent() {
|
||||
setError("Failed to create order. Please contact support.");
|
||||
setCreating(false);
|
||||
}
|
||||
})();
|
||||
};
|
||||
init();
|
||||
}, [sessionId, paymentIntentId, redirectStatus]);
|
||||
|
||||
if (!order || !orderIdParam) {
|
||||
|
||||
Reference in New Issue
Block a user