Add: Weight field to product admin pages
Added weight (in kilograms) field to both create and edit product pages. Admins can now set the weight for each product, which is converted to grams and stored in the database for Royal Mail shipping calculations.
This commit is contained in:
@@ -86,7 +86,8 @@
|
|||||||
"Bash(git commit -m 'Feature: Add product selection when starting design from photo request *)",
|
"Bash(git commit -m 'Feature: Add product selection when starting design from photo request *)",
|
||||||
"Bash(git commit -m 'Refactor: Move start design server action to separate file *)",
|
"Bash(git commit -m 'Refactor: Move start design server action to separate file *)",
|
||||||
"Bash(git commit -m 'Add: Prominent '\\\\''Shop plain items'\\\\'' section to homepage *)",
|
"Bash(git commit -m 'Add: Prominent '\\\\''Shop plain items'\\\\'' section to homepage *)",
|
||||||
"Bash(git commit -m 'Feature: Royal Mail shipping integration with weight-based postage *)"
|
"Bash(git commit -m 'Feature: Royal Mail shipping integration with weight-based postage *)",
|
||||||
|
"Bash(git commit -m 'Add: Weight field to product admin pages *)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,6 +239,25 @@ export default async function EditProductPage({ params }: { params: { id: string
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="weightKg" className="tag-label mb-2 block">
|
||||||
|
Weight (kg, optional)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="weightKg"
|
||||||
|
name="weightKg"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min={0}
|
||||||
|
defaultValue={row.weightGrams != null ? (row.weightGrams / 1000).toFixed(2) : ''}
|
||||||
|
className="w-full border border-line bg-paper px-3 py-2 text-sm"
|
||||||
|
placeholder="e.g. 0.25"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-muted">
|
||||||
|
Used for Royal Mail shipping cost calculation at checkout. Enter weight in kilograms.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="tag-label mb-2 block">Front photo</label>
|
<label className="tag-label mb-2 block">Front photo</label>
|
||||||
{product.imageUrl && (
|
{product.imageUrl && (
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ export async function createProduct(formData: FormData) {
|
|||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// Weight in kilograms converted to grams
|
||||||
|
const weightKgInput = String(formData.get('weightKg') ?? '').trim();
|
||||||
|
const weightGrams = weightKgInput ? Math.round(Number(weightKgInput) * 1000) : null;
|
||||||
|
|
||||||
const imageUrl = imageFile && imageFile.size > 0 ? await fileToDataUrl(imageFile) : null;
|
const imageUrl = imageFile && imageFile.size > 0 ? await fileToDataUrl(imageFile) : null;
|
||||||
const imageUrlBack = imageBackFile && imageBackFile.size > 0 ? await fileToDataUrl(imageBackFile) : null;
|
const imageUrlBack = imageBackFile && imageBackFile.size > 0 ? await fileToDataUrl(imageBackFile) : null;
|
||||||
const colorPhotos = await extractColorPhotos(formData, colors, 'front');
|
const colorPhotos = await extractColorPhotos(formData, colors, 'front');
|
||||||
@@ -132,6 +136,7 @@ export async function createProduct(formData: FormData) {
|
|||||||
description: description || `${name} — personalize it your way.`,
|
description: description || `${name} — personalize it your way.`,
|
||||||
basePrice: Math.round(priceDollars * 100),
|
basePrice: Math.round(priceDollars * 100),
|
||||||
personalisedPrice: personalisedPriceDollars ? Math.round(Number(personalisedPriceDollars) * 100) : null,
|
personalisedPrice: personalisedPriceDollars ? Math.round(Number(personalisedPriceDollars) * 100) : null,
|
||||||
|
weightGrams,
|
||||||
...saleFields,
|
...saleFields,
|
||||||
colors: JSON.stringify(colors),
|
colors: JSON.stringify(colors),
|
||||||
colorPhotos: Object.keys(colorPhotos).length > 0 ? JSON.stringify(colorPhotos) : null,
|
colorPhotos: Object.keys(colorPhotos).length > 0 ? JSON.stringify(colorPhotos) : null,
|
||||||
@@ -173,6 +178,10 @@ export async function updateProduct(formData: FormData) {
|
|||||||
const referenceWidthCm = referenceWidthCmInput ? Number(referenceWidthCmInput) : null;
|
const referenceWidthCm = referenceWidthCmInput ? Number(referenceWidthCmInput) : null;
|
||||||
const referenceHeightCm = referenceHeightCmInput ? Number(referenceHeightCmInput) : null;
|
const referenceHeightCm = referenceHeightCmInput ? Number(referenceHeightCmInput) : null;
|
||||||
|
|
||||||
|
// Weight in kilograms converted to grams
|
||||||
|
const weightKgInput = String(formData.get('weightKg') ?? '').trim();
|
||||||
|
const weightGrams = weightKgInput ? Math.round(Number(weightKgInput) * 1000) : null;
|
||||||
|
|
||||||
if (!id) throw new Error('Missing product id.');
|
if (!id) throw new Error('Missing product id.');
|
||||||
if (!name || !category || !priceDollars) {
|
if (!name || !category || !priceDollars) {
|
||||||
throw new Error('Name, category, and price are required.');
|
throw new Error('Name, category, and price are required.');
|
||||||
@@ -234,6 +243,7 @@ export async function updateProduct(formData: FormData) {
|
|||||||
showOnProducts: boolean;
|
showOnProducts: boolean;
|
||||||
referenceWidthCm: number | null;
|
referenceWidthCm: number | null;
|
||||||
referenceHeightCm: number | null;
|
referenceHeightCm: number | null;
|
||||||
|
weightGrams: number | null;
|
||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
imageUrlBack?: string;
|
imageUrlBack?: string;
|
||||||
printArea?: string;
|
printArea?: string;
|
||||||
@@ -255,6 +265,7 @@ export async function updateProduct(formData: FormData) {
|
|||||||
showOnProducts,
|
showOnProducts,
|
||||||
referenceWidthCm,
|
referenceWidthCm,
|
||||||
referenceHeightCm,
|
referenceHeightCm,
|
||||||
|
weightGrams,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only touch the front photo/print area if a new photo was actually uploaded —
|
// Only touch the front photo/print area if a new photo was actually uploaded —
|
||||||
|
|||||||
@@ -214,6 +214,24 @@ export default async function NewProductPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="weightKg" className="tag-label mb-2 block">
|
||||||
|
Weight (kg, optional)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="weightKg"
|
||||||
|
name="weightKg"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min={0}
|
||||||
|
className="w-full border border-line bg-paper px-3 py-2 text-sm"
|
||||||
|
placeholder="e.g. 0.25"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-muted">
|
||||||
|
Used for Royal Mail shipping cost calculation at checkout. Enter weight in kilograms.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="tag-label mb-2 block">Front photo (optional)</label>
|
<label className="tag-label mb-2 block">Front photo (optional)</label>
|
||||||
<PhotoPrintAreaField />
|
<PhotoPrintAreaField />
|
||||||
|
|||||||
Reference in New Issue
Block a user