From b7379e379b9a84c1d6115efa6b0b5c7e75328c91 Mon Sep 17 00:00:00 2001 From: Andymick Date: Fri, 17 Jul 2026 22:53:36 +0100 Subject: [PATCH] Add login prompt when non-authenticated users add items to cart - Create LoginPromptModal component for authentication encouragement - Check customer authentication before adding items to cart - Show modal for non-logged-in users with options to create account, log in, or continue as guest - Allow guests to add items (stored in IndexedDB) while prompting to sign up - Implemented in both StandardProductView (ready-made products) and DesignCanvas (personalized items) - Add server action checkCustomerAuth() for authentication checks Addresses user request: prompt guests to create account or log in when adding items to cart. Co-Authored-By: Claude Haiku 4.5 --- src/app/actions.ts | 6 ++++ src/components/DesignCanvas.tsx | 12 ++++++- src/components/LoginPromptModal.tsx | 47 ++++++++++++++++++++++++++ src/components/StandardProductView.tsx | 12 ++++++- 4 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 src/components/LoginPromptModal.tsx diff --git a/src/app/actions.ts b/src/app/actions.ts index 2924ac3..90f6ae0 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -5,6 +5,7 @@ import { prisma } from '@/lib/prisma'; import { upsertMailingListEntry } from '@/lib/mailingList'; import { verifyTurnstileToken } from '@/lib/turnstile'; import { getClientIp } from '@/lib/rateLimit'; +import { getCurrentCustomer } from '@/lib/auth'; const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; @@ -30,3 +31,8 @@ export async function subscribeToNewsletter(email: string, turnstileToken: strin return { ok: true, message: "Thanks — you're on the list." }; } + +export async function checkCustomerAuth() { + const customer = await getCurrentCustomer(); + return { isAuthenticated: !!customer }; +} diff --git a/src/components/DesignCanvas.tsx b/src/components/DesignCanvas.tsx index 760b0c8..c5855e4 100644 --- a/src/components/DesignCanvas.tsx +++ b/src/components/DesignCanvas.tsx @@ -6,8 +6,10 @@ import type Konva from 'konva'; import { v4 as uuid } from 'uuid'; import ProductMockup from './ProductMockup'; import Price from './Price'; +import LoginPromptModal from './LoginPromptModal'; import { useCart } from '@/lib/cartStore'; import { getEffectivePrice } from '@/lib/pricing'; +import { checkCustomerAuth } from '@/app/actions'; import type { DesignElement, PrintArea, ProductDTO } from '@/lib/types'; const DISPLAY = 560; // css px size the mockup + stage are rendered at @@ -310,6 +312,7 @@ export default function DesignCanvas({ product }: { product: ProductDTO }) { const [selectedId, setSelectedId] = useState(null); const [quantity, setQuantity] = useState(1); const [justAdded, setJustAdded] = useState(false); + const [showLoginPrompt, setShowLoginPrompt] = useState(false); const [nodeReadyTick, setNodeReadyTick] = useState(0); const stageFrontRef = useRef(null); @@ -462,7 +465,12 @@ export default function DesignCanvas({ product }: { product: ProductDTO }) { ).price : product.effectivePrice; - const handleAddToBag = () => { + const handleAddToBag = async () => { + const { isAuthenticated } = await checkCustomerAuth(); + if (!isAuthenticated) { + setShowLoginPrompt(true); + } + if (!canPersonalize) { const flatPhoto = product.colorPhotos[color] ?? product.imageUrl ?? ''; addItem({ @@ -861,6 +869,8 @@ export default function DesignCanvas({ product }: { product: ProductDTO }) { {justAdded &&

Added to your bag.

} + + setShowLoginPrompt(false)} /> ); } diff --git a/src/components/LoginPromptModal.tsx b/src/components/LoginPromptModal.tsx new file mode 100644 index 0000000..2ebdb61 --- /dev/null +++ b/src/components/LoginPromptModal.tsx @@ -0,0 +1,47 @@ +'use client'; + +import Link from 'next/link'; + +interface LoginPromptModalProps { + isOpen: boolean; + onClose: () => void; +} + +export default function LoginPromptModal({ isOpen, onClose }: LoginPromptModalProps) { + if (!isOpen) return null; + + return ( +
+
+

Create an account to shop

+

+ Add items to your bag and save your designs. You'll need an account to checkout. +

+ +
+ + Create account + + + Log in + +
+ + +
+
+ ); +} diff --git a/src/components/StandardProductView.tsx b/src/components/StandardProductView.tsx index 3e37def..db28829 100644 --- a/src/components/StandardProductView.tsx +++ b/src/components/StandardProductView.tsx @@ -4,8 +4,10 @@ import { useState } from 'react'; import { v4 as uuid } from 'uuid'; import ProductMockup from './ProductMockup'; import Price from './Price'; +import LoginPromptModal from './LoginPromptModal'; import { useCart } from '@/lib/cartStore'; import { getColorName } from '@/lib/colorNames'; +import { checkCustomerAuth } from '@/app/actions'; import type { ProductDTO } from '@/lib/types'; export default function StandardProductView({ product }: { product: ProductDTO }) { @@ -14,6 +16,7 @@ export default function StandardProductView({ product }: { product: ProductDTO } const [view, setView] = useState<'front' | 'back'>('front'); const [quantity, setQuantity] = useState(1); const [justAdded, setJustAdded] = useState(false); + const [showLoginPrompt, setShowLoginPrompt] = useState(false); const addItem = useCart((s) => s.addItem); const hasBack = Boolean(product.imageUrlBack); @@ -44,7 +47,12 @@ export default function StandardProductView({ product }: { product: ProductDTO } return {product.name}; } - const handleAddToBag = () => { + const handleAddToBag = async () => { + const { isAuthenticated } = await checkCustomerAuth(); + if (!isAuthenticated) { + setShowLoginPrompt(true); + } + addItem({ cartItemId: uuid(), productId: product.id, @@ -182,6 +190,8 @@ export default function StandardProductView({ product }: { product: ProductDTO } {justAdded &&

Added to your bag.

} + + setShowLoginPrompt(false)} /> ); }