Skip to content

Commit f8692b4

Browse files
authored
Merge branch 'main' into claude/shopify-csv-sync-TuV8j
2 parents cd45d02 + b5fe48b commit f8692b4

7 files changed

Lines changed: 4442 additions & 16 deletions

File tree

data/shopify-import-3.csv

Lines changed: 4313 additions & 0 deletions
Large diffs are not rendered by default.

scripts/sync-shopify-catalog.ts

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const csvArg = args.find(
6868
(a) => !a.startsWith("--") && (limitIdx === -1 || a !== args[limitIdx + 1])
6969
);
7070
const CSV_PATH =
71-
csvArg || process.env.CSV_PATH || path.resolve("data/shopify-import-2.csv");
71+
csvArg || process.env.CSV_PATH || path.resolve("data/shopify-import-3.csv");
7272

7373
// Throttle between products (ms)
7474
const THROTTLE_MS = 500;
@@ -259,7 +259,7 @@ function groupProducts(rows: Record<string, string>[]): ProductGroup[] {
259259
title: row["Title"] || "",
260260
bodyHtml: row["Body (HTML)"] || "",
261261
vendor: row["Vendor"] || "Asper Beauty",
262-
productType: type,
262+
productType: normalizeProductType(type),
263263
tags,
264264
status: (row["Status"] || "active").toLowerCase(),
265265
seoTitle: row["SEO Title"] || "",
@@ -348,7 +348,7 @@ const PRODUCT_CREATE = `
348348
`;
349349

350350
const PRODUCT_UPDATE = `
351-
mutation ProductUpdate($input: ProductInput!) {
351+
mutation ProductUpdate($input: ProductInput!, $media: [CreateMediaInput!]) {
352352
productUpdate(input: $input) {
353353
product {
354354
id
@@ -398,6 +398,103 @@ const GET_PUBLICATIONS = `
398398
}
399399
`;
400400

401+
// ---------------------------------------------------------------------------
402+
// Inventory queries & mutations
403+
// ---------------------------------------------------------------------------
404+
405+
const LOCATIONS_QUERY = `
406+
query GetLocations {
407+
locations(first: 1) {
408+
edges { node { id name } }
409+
}
410+
}
411+
`;
412+
413+
const VARIANT_INVENTORY_ITEMS = `
414+
query VariantInventoryItems($productId: ID!) {
415+
product(id: $productId) {
416+
variants(first: 100) {
417+
edges {
418+
node {
419+
id
420+
sku
421+
inventoryItem { id }
422+
}
423+
}
424+
}
425+
}
426+
}
427+
`;
428+
429+
const INVENTORY_SET_QUANTITIES = `
430+
mutation InventorySetQuantities($input: InventorySetQuantitiesInput!) {
431+
inventorySetQuantities(input: $input) {
432+
inventoryAdjustmentGroup { reason }
433+
userErrors { field message }
434+
}
435+
}
436+
`;
437+
438+
let cachedLocationId: string | null = null;
439+
440+
async function getPrimaryLocationId(): Promise<string> {
441+
if (cachedLocationId) return cachedLocationId;
442+
const data = await adminGraphQL(LOCATIONS_QUERY);
443+
const loc = data?.locations?.edges?.[0]?.node;
444+
if (!loc) throw new Error("No Shopify locations found. Create a location first.");
445+
cachedLocationId = loc.id;
446+
console.log(` 📍 Primary location: ${loc.name} (${loc.id})`);
447+
return loc.id;
448+
}
449+
450+
async function setInventoryQuantities(
451+
productId: string,
452+
variants: { sku: string; inventoryQty: number }[],
453+
tag: string
454+
) {
455+
const locationId = await getPrimaryLocationId();
456+
457+
// Fetch inventoryItemIds for the product's variants
458+
const data = await adminGraphQL(VARIANT_INVENTORY_ITEMS, { productId });
459+
const variantEdges = data?.product?.variants?.edges || [];
460+
461+
const quantities: { inventoryItemId: string; locationId: string; quantity: number }[] = [];
462+
463+
for (const edge of variantEdges) {
464+
const node = edge.node;
465+
const inventoryItemId = node.inventoryItem?.id;
466+
if (!inventoryItemId) continue;
467+
468+
// Match by SKU or by position
469+
const csvMatch = variants.find((v) => v.sku && v.sku === node.sku);
470+
const qty = csvMatch?.inventoryQty ?? variants[variantEdges.indexOf(edge)]?.inventoryQty;
471+
472+
if (qty !== undefined && qty >= 0) {
473+
quantities.push({ inventoryItemId, locationId, quantity: qty });
474+
}
475+
}
476+
477+
if (quantities.length === 0) return;
478+
479+
try {
480+
const result = await adminGraphQL(INVENTORY_SET_QUANTITIES, {
481+
input: {
482+
name: "available",
483+
reason: "correction",
484+
quantities,
485+
},
486+
});
487+
const errors = result?.inventorySetQuantities?.userErrors;
488+
if (errors?.length) {
489+
console.warn(` ⚠️ ${tag} inventory warnings:`, errors);
490+
} else {
491+
console.log(` 📦 ${tag}: set inventory for ${quantities.length} variant(s)`);
492+
}
493+
} catch (invErr: any) {
494+
console.warn(` ⚠️ ${tag} inventory update failed: ${invErr.message}`);
495+
}
496+
}
497+
401498
// ---------------------------------------------------------------------------
402499
// Sync logic
403500
// ---------------------------------------------------------------------------
@@ -591,6 +688,14 @@ async function syncProduct(product: ProductGroup, index: number, total: number)
591688
}
592689
}
593690
}
691+
// 3. Set inventory quantities at the primary location
692+
if (variantEdges?.length > 0 && product.variants.some((v) => v.inventoryQty > 0)) {
693+
await setInventoryQuantities(
694+
productId,
695+
product.variants.map((v) => ({ sku: v.sku, inventoryQty: v.inventoryQty })),
696+
tag
697+
);
698+
}
594699

595700
// 3. Publish to storefront (auto-publish new active products, or if --publish flag)
596701
if (PUBLISH || (!existing && product.status === "active")) {

src/components/brand/SocialLinks.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ const XIcon = () => (
102102
export const socialLinks = [
103103
{ key: "whatsapp", href: "https://wa.me/962790656666", label: "WhatsApp", Icon: WhatsAppIcon },
104104
{ key: "instagram", href: "https://www.instagram.com/asper.beauty.shop/", label: "Instagram", Icon: InstagramIcon },
105-
{ key: "facebook", href: "https://www.facebook.com/AsperBeautyShop", label: "Facebook", Icon: FacebookIcon },
105+
{ key: "facebook", href: "https://www.facebook.com/robu.sweileh", label: "Facebook", Icon: FacebookIcon },
106106
{ key: "tiktok", href: "https://tiktok.com/@asper.beauty.shop", label: "TikTok", Icon: TikTokIcon },
107107
{ key: "youtube", href: "https://youtube.com/@asperbeautyshop", label: "YouTube", Icon: YouTubeIcon },
108108
{ key: "snapchat", href: "https://snapchat.com/add/asperbeautyshop", label: "Snapchat", Icon: SnapchatIcon },

src/components/home/SocialGallery.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const socialPosts = [
1818
platform: "facebook" as const,
1919
caption: "Pharmacist-approved sunscreen picks for summer ☀️",
2020
hashtag: "#SanctuaryOfScience",
21-
link: "https://www.facebook.com/AsperBeautyShop",
21+
link: "https://www.facebook.com/robu.sweileh",
2222
},
2323
{
2424
id: 3,
@@ -32,7 +32,7 @@ const socialPosts = [
3232
platform: "facebook" as const,
3333
caption: "Pregnancy-safe skincare essentials 🤰",
3434
hashtag: "#SafeBeauty",
35-
link: "https://www.facebook.com/AsperBeautyShop",
35+
link: "https://www.facebook.com/robu.sweileh",
3636
},
3737
{
3838
id: 5,
@@ -46,7 +46,7 @@ const socialPosts = [
4646
platform: "facebook" as const,
4747
caption: "Customer spotlight: Sara's acne journey ✨",
4848
hashtag: "#AsperStories",
49-
link: "https://www.facebook.com/AsperBeautyShop",
49+
link: "https://www.facebook.com/robu.sweileh",
5050
},
5151
];
5252

@@ -152,7 +152,7 @@ export default function SocialGallery() {
152152
</a>
153153
<span className="text-border">|</span>
154154
<a
155-
href="https://www.facebook.com/AsperBeautyShop"
155+
href="https://www.facebook.com/robu.sweileh"
156156
target="_blank"
157157
rel="noopener noreferrer"
158158
className="group inline-flex items-center gap-2 text-sm font-body text-muted-foreground hover:text-accent transition-colors"

src/lib/categoryMapping.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ export const CATEGORY_GROUPS: CategoryGroup[] = [
6666
icon: "🧴",
6767
subcategories: [
6868
{ label: "Cleanser", types: ["Cleanser", "Face Wash", "Micellar Water", "Micellar"] },
69-
{ label: "Serum", types: ["Serum", "Face Serum"] },
69+
{ label: "Serum & Essence", types: ["Serum", "Face Serum", "Essence"] },
7070
{ label: "Sunscreen", types: ["Sunscreen", "Sun Screen", "SPF", "Sun Care"] },
71-
{ label: "Moisturizer", types: ["Moisturizer", "Moisturiser", "Face Cream", "Day Cream", "Night Cream"] },
72-
{ label: "Toner", types: ["Toner", "Face Tonic"] },
71+
{ label: "Moisturizer", types: ["Moisturizer", "Moisturiser", "Face Cream", "Day Cream", "Night Cream", "Skin Care"] },
72+
{ label: "Toner & Mist", types: ["Toner", "Face Tonic", "Face Mist"] },
7373
{ label: "Mask", types: ["Face Mask", "Sheet Mask", "Clay Mask", "Peel-Off Mask"] },
7474
{ label: "Eye Care", types: ["Eye Cream", "Eye Serum", "Under Eye"] },
75-
{ label: "Exfoliator", types: ["Exfoliator", "Scrub", "Face Scrub", "Peeling"] },
75+
{ label: "Exfoliator & Peeling", types: ["Exfoliator", "Scrub", "Face Scrub", "Peeling"] },
7676
],
7777
},
7878
{
@@ -84,6 +84,7 @@ export const CATEGORY_GROUPS: CategoryGroup[] = [
8484
{ label: "Hair Treatment", types: ["Hair Treatment", "Hair Mask", "Hair Oil", "Hair Serum"] },
8585
{ label: "Styling", types: ["Hair Styling", "Hair Spray", "Hair Gel", "Hair Wax"] },
8686
{ label: "Hair Color", types: ["Hair Color", "Hair Dye"] },
87+
{ label: "Hair Care", types: ["Hair Care"] },
8788
],
8889
},
8990
{
@@ -93,14 +94,15 @@ export const CATEGORY_GROUPS: CategoryGroup[] = [
9394
{ label: "Body Lotion", types: ["Body Lotion", "Body Cream", "Body Butter"] },
9495
{ label: "Body Wash", types: ["Body Wash", "Shower Gel", "Bath & Body"] },
9596
{ label: "Hand & Foot", types: ["Hand Cream", "Foot Cream", "Hand & Nail"] },
96-
{ label: "Deodorant", types: ["Deodorant", "Antiperspirant"] },
97+
{ label: "Deodorant", types: ["Deodorant", "Antiperspirant", "Deodorant Spray", "Deodorant & Antiperspirant Roll"] },
98+
{ label: "Intimate Care", types: ["Intimate Wash", "Intimate Care"] },
9799
],
98100
},
99101
{
100102
label: "Fragrance",
101103
icon: "🌹",
102104
subcategories: [
103-
{ label: "Perfume", types: ["Perfume", "Eau de Parfum", "EDP"] },
105+
{ label: "Perfume", types: ["Perfume", "Eau de Parfum", "EDP", "Fragrance"] },
104106
{ label: "Eau de Toilette", types: ["Eau de Toilette", "EDT"] },
105107
{ label: "Body Mist", types: ["Body Mist", "Body Spray"] },
106108
{ label: "Gift Sets", types: ["Gift Set", "Fragrance Set"] },
@@ -113,6 +115,12 @@ export const CATEGORY_GROUPS: CategoryGroup[] = [
113115
{ label: "Baby Skin", types: ["Baby Lotion", "Baby Cream", "Baby Oil", "Baby Wash"] },
114116
{ label: "Maternity", types: ["Maternity", "Stretch Mark", "Nursing"] },
115117
{ label: "Baby Hair", types: ["Baby Shampoo"] },
118+
{ label: "Feeding", types: ["Nipple", "Bottle", "Cutlery", "Baby Set"] },
119+
{ label: "Soothers & Teething", types: ["Soothers", "Teether"] },
120+
{ label: "Baby Toys", types: ["Toys", "Playmat", "Projecter"] },
121+
{ label: "Baby Essentials", types: ["Thermometer", "Sterilizer", "Swaddles", "Baby Towel", "Accessories"] },
122+
{ label: "Baby Oral Care", types: ["Toothpaste", "Toothbrush"] },
123+
{ label: "Baby Clothing", types: ["Body Care"] },
116124
],
117125
},
118126
];

src/pages/Index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ const Index = () => {
382382
<a href="https://wa.me/962790656666" target="_blank" rel="noopener noreferrer" className="text-sm text-primary-foreground/60 hover:text-accent transition-colors font-body">
383383
WhatsApp: +962 79 065 6666
384384
</a>
385-
<a href="https://instagram.com/asper.beauty.shop" target="_blank" rel="noopener noreferrer" className="text-sm text-primary-foreground/60 hover:text-accent transition-colors font-body">
385+
<a href="https://www.instagram.com/asper.beauty.shop/" target="_blank" rel="noopener noreferrer" className="text-sm text-primary-foreground/60 hover:text-accent transition-colors font-body">
386386
@asper.beauty.shop
387387
</a>
388388
<IncognitoToggle className="mt-1 border-primary-foreground/20 text-primary-foreground/60 hover:text-primary-foreground hover:border-primary-foreground/40" />

src/pages/Products.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const Products = () => {
104104
Product Catalog
105105
</h1>
106106
<p className="mt-2 text-muted-foreground font-body">
107-
Browse our curated collection of 3,000+ beauty & wellness products
107+
Browse our curated collection of 4,000+ beauty & wellness products
108108
</p>
109109

110110
<form onSubmit={handleSearch} className="mt-6 flex gap-2 max-w-lg">

0 commit comments

Comments
 (0)