Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/[locale]/products/[id]/customization-experience.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ function CustomizationExperienceInner({
ProductCustomizationConfig.fromJson(customizationConfig);
const resolvedCustomizationConfig =
customizationConfig ?? ProductCustomizationConfig.default().toJson();
const isAllowsPhoto = customizationModel.allowPhotoDesign !== false;
const isAllowsPhoto = customizationModel.allowsPhoto();
const customizableBaseImages = productImages.filter(
(image) => image.purpose === ProductImagePurpose.CUSTOMIZABLE_BASE,
);
Expand Down
8 changes: 7 additions & 1 deletion app/[locale]/seller/products/product-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,16 @@ function buildTranslationMap(

function buildPayload(locale: SupportedLocale, form: FormState) {
const current = form.translations[locale];
const hasCustomizableBase = form.images.customizableBase.length > 0;
const effectiveMode =
hasCustomizableBase && form.customizationConfig?.mode === 'description'
? 'text_photo'
: form.customizationConfig?.mode;

const customizationConfig = form.customizationConfig
? {
...form.customizationConfig,
allowPhotoDesign: form.images.customizableBase.length > 0,
mode: effectiveMode,
}
: undefined;
const currentDesignChangeDescription =
Expand Down
31 changes: 19 additions & 12 deletions modules/cart/application/migrate-guest-cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,17 +435,21 @@ async function resolveGuestCustomizationIds(
const cached = createdCustomizationCache.get(cacheKey);
if (cached) return [cached];

const created = await customizationCreator.create({
productId: g.productId,
text: g.customizationText ?? null,
color: g.customizationColor ?? null,
size: g.customizationSize ?? null,
imageUrl: g.customizationImageUrl ?? null,
designPosition: g.customizationDesignPosition ?? null,
});

createdCustomizationCache.set(cacheKey, created.id);
return [created.id];
try {
const created = await customizationCreator.create({
productId: g.productId,
text: g.customizationText ?? null,
color: g.customizationColor ?? null,
size: g.customizationSize ?? null,
imageUrl: g.customizationImageUrl ?? null,
designPosition: g.customizationDesignPosition ?? null,
});

createdCustomizationCache.set(cacheKey, created.id);
return [created.id];
} catch {
return null;
}
}

function guestCustomizationKey(g: GuestCartItem): string {
Expand All @@ -471,7 +475,10 @@ function isCustomizationAllowed(
(g.customizationColor !== undefined && g.customizationColor !== null) ||
(g.customizationSize !== undefined && g.customizationSize !== null);
const hasPhoto =
g.customizationImageUrl !== undefined && g.customizationImageUrl !== null;
(g.customizationImageUrl !== undefined &&
g.customizationImageUrl !== null) ||
(g.customizationDesignPosition !== undefined &&
g.customizationDesignPosition !== null);

if (hasPhoto && !capability.allowsPhoto()) return false;
if (hasStyle && !capability.allowsStyleOptions()) return false;
Expand Down
50 changes: 46 additions & 4 deletions modules/cart/presentation/components/add-to-cart-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,22 @@ function findCartItemInfo(
productId: string,
normalizedCustomization: CustomizationDraftPayload,
): { found: { cartItemId: string; quantity: number } | null } {
const hasDraftContent =
hasText(normalizedCustomization.text) ||
hasText(normalizedCustomization.color) ||
hasText(normalizedCustomization.size) ||
hasText(normalizedCustomization.imageUrl) ||
Boolean(normalizedCustomization.designPosition);

const found = items.find(
(item) =>
item.productId === productId &&
isAuthCustomizationMatching(
item.customizations ?? [],
normalizedCustomization,
),
(hasDraftContent
? isAuthCustomizationMatching(
item.customizations ?? [],
normalizedCustomization,
)
: true),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
);
return {
found: found ? { cartItemId: found.id, quantity: found.quantity } : null,
Expand Down Expand Up @@ -168,6 +177,7 @@ export function AddToCartButton({

const [state, setState] = useState<ButtonState>('idle');
const [cartItemInfo, setCartItemInfo] = useState<CartItemInfo | null>(null);
const [productInCart, setProductInCart] = useState(false);
const [showCustomizationChoice, setShowCustomizationChoice] = useState(false);
const [savingDesign, setSavingDesign] = useState(false);

Expand Down Expand Up @@ -203,6 +213,13 @@ export function AddToCartButton({
normalizedCustomization,
);
setCartItemInfo(result.found);
setProductInCart(
!editCartItemId &&
result.found === null &&
(data.items ?? []).some(
(item: { productId: string }) => item.productId === productId,
),
);
} catch {
/* fallback to "Add to Cart" */
}
Expand Down Expand Up @@ -263,6 +280,12 @@ export function AddToCartButton({
normalizedCustomization,
);
setCartItemInfo(result.found);
setProductInCart(
result.found === null &&
(data.items ?? []).some(
(item: { productId: string }) => item.productId === productId,
),
);
} catch {
/* ignore */
}
Expand Down Expand Up @@ -674,6 +697,25 @@ export function AddToCartButton({
);
}

// Product in cart with a different customization variant: show "Add another"
// button without quantity controls (this variant is not yet in the cart).
if (productInCart && isCustomizationHasContent && isAuthenticated) {
return (
<div className={styles.quantityRow}>
<button
type="button"
className={styles.saveButton}
onClick={handleAddAnother}
disabled={disabled || savingDesign || state === 'adding'}
>
{state === 'adding'
? labels.adding
: labels.addAnotherPersonalization}
</button>
</div>
);
}

// Default: "Add to Cart".
return (
<>
Expand Down
7 changes: 5 additions & 2 deletions modules/cart/presentation/components/cart-merge-detector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,16 @@ export function CartMergeDetector({
strategy: 'merge',
}),
});
if (!res.ok) return;
if (!res.ok) {
hasCheckedRef.current = false;
return;
}
// Clear guest cart after successful migration
guestCart.clearCart();
dispatchCartUpdated();
router.refresh();
} catch {
// Ignore - let user retry manually
hasCheckedRef.current = false;
}
}
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export interface ProductCustomizationConfigJson {
previewTemplateUrl: string | null;
textOffset: PreviewOffset | null;
imageOffset: PreviewOffset | null;
allowPhotoDesign?: boolean;
categoryId?: string | null;
}

Expand Down Expand Up @@ -72,7 +71,6 @@ const productCustomizationConfigSchema = z
previewTemplateUrl: z.string().min(1).nullable().optional(),
textOffset: previewOffsetSchema.nullable().optional(),
imageOffset: previewOffsetSchema.nullable().optional(),
allowPhotoDesign: z.boolean().optional(),
categoryId: z.string().nullable().optional(),
})
.strip();
Expand All @@ -85,7 +83,6 @@ export class ProductCustomizationConfig {
previewTemplateUrl: null,
textOffset: null,
imageOffset: null,
allowPhotoDesign: true,
categoryId: null,
});
}
Expand All @@ -96,15 +93,26 @@ export class ProductCustomizationConfig {
return this.default();
}

const data = parsed.data;
const data = parsed.data as Record<string, unknown>;
const raw = value as Record<string, unknown> | null;

// Backward compat: stored JSON may have allowPhotoDesign: true with
// mode: 'description' from before the flag was removed.
const mode: CustomizationMode =
(data.mode as CustomizationMode) ?? 'description';
const effectiveMode: CustomizationMode =
mode === 'description' &&
(raw?.allowPhotoDesign as boolean | undefined) === true
? 'text_photo'
: mode;

return new ProductCustomizationConfig({
mode: data.mode ?? 'description',
previewEnabled: data.previewEnabled ?? false,
previewTemplateUrl: data.previewTemplateUrl ?? null,
textOffset: data.textOffset ?? null,
imageOffset: data.imageOffset ?? null,
allowPhotoDesign: data.allowPhotoDesign ?? true,
categoryId: data.categoryId ?? null,
mode: effectiveMode,
previewEnabled: (data.previewEnabled as boolean) ?? false,
previewTemplateUrl: (data.previewTemplateUrl as string | null) ?? null,
textOffset: (data.textOffset as PreviewOffset | null) ?? null,
imageOffset: (data.imageOffset as PreviewOffset | null) ?? null,
categoryId: (data.categoryId as string | null) ?? null,
});
}

Expand All @@ -113,7 +121,6 @@ export class ProductCustomizationConfig {
readonly previewTemplateUrl: string | null;
readonly textOffset: PreviewOffset | null;
readonly imageOffset: PreviewOffset | null;
readonly allowPhotoDesign: boolean;
readonly categoryId: string | null;

private constructor(data: ProductCustomizationConfigJson) {
Expand All @@ -122,7 +129,6 @@ export class ProductCustomizationConfig {
this.previewTemplateUrl = data.previewTemplateUrl;
this.textOffset = data.textOffset;
this.imageOffset = data.imageOffset;
this.allowPhotoDesign = data.allowPhotoDesign ?? true;
this.categoryId = data.categoryId ?? null;
}

Expand Down Expand Up @@ -166,7 +172,6 @@ export class ProductCustomizationConfig {
previewTemplateUrl: this.previewTemplateUrl,
textOffset: this.textOffset,
imageOffset: this.imageOffset,
allowPhotoDesign: this.allowPhotoDesign,
categoryId: this.categoryId,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export const productCustomizationConfigSchema = z
previewTemplateUrl: z.string().min(1).nullable(),
textOffset: previewOffsetSchema.nullable(),
imageOffset: previewOffsetSchema.nullable(),
allowPhotoDesign: z.boolean().optional(),
categoryId: z.string().nullable().optional(),
})
.strip();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ describe('CustomizationExperience', () => {
purpose: ProductImagePurpose.CUSTOMIZABLE_BASE,
},
]}
customizationConfig={ProductCustomizationConfig.default().toJson()}
customizationConfig={ProductCustomizationConfig.fromJson({
mode: 'text_photo',
}).toJson()}
labels={labels}
initialDraft={{ color: 'Red' }}
/>,
Expand Down Expand Up @@ -261,7 +263,9 @@ describe('CustomizationExperience', () => {
purpose: ProductImagePurpose.CUSTOMIZABLE_BASE,
},
]}
customizationConfig={ProductCustomizationConfig.default().toJson()}
customizationConfig={ProductCustomizationConfig.fromJson({
mode: 'text_photo',
}).toJson()}
labels={labels}
initialDraft={{ color: 'Red' }}
/>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ describe('ProductCustomizationConfig', () => {
expect(config.previewTemplateUrl).toBeNull();
expect(config.textOffset).toBeNull();
expect(config.imageOffset).toBeNull();
expect(config.allowPhotoDesign).toBe(true);
expect(config.isDefault()).toBe(true);
expect(config.isPreviewCapable()).toBe(false);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ describe('PrismaProductRepository', () => {
previewTemplateUrl: 'https://cdn.example.com/mock.png',
textOffset: { x: 12, y: 20 },
imageOffset: { x: 18, y: 30 },
allowPhotoDesign: true,
categoryId: null,
}),
translations: {
Expand Down Expand Up @@ -244,7 +243,6 @@ describe('PrismaProductRepository', () => {
previewTemplateUrl: 'https://cdn.example.com/mock.png',
textOffset: { x: 12, y: 20 },
imageOffset: { x: 18, y: 30 },
allowPhotoDesign: true,
categoryId: null,
}),
updatedAt: product.updatedAt,
Expand Down
Loading