diff --git a/editor/components/commerce/collection-detail.tsx b/editor/components/commerce/collection-detail.tsx index 754faee..9137efe 100644 --- a/editor/components/commerce/collection-detail.tsx +++ b/editor/components/commerce/collection-detail.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { useCollectionProducts } from '@/editor/hooks/use-shopify-collections'; import ProductCard from './product-card'; +import { Skeleton } from '@/components/ui/skeleton'; const CollectionDetail: React.FC<{ handle?: string }> = ({ handle: handleProp }) => { const handle = handleProp ?? ''; @@ -14,25 +15,20 @@ const CollectionDetail: React.FC<{ handle?: string }> = ({ handle: handleProp }) ? handle.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) : 'Collection'; - if (loading) { + if (loading || !handle) { return (
-

- {formattedTitle} -

+
+ +
- {/* Loading Skeleton */}
{Array.from({ length: 8 }).map((_, index) => ( -
-
-
-
-
-
-
-
+
+ + +
))}
diff --git a/editor/components/commerce/collection.tsx b/editor/components/commerce/collection.tsx index a2ec4df..b06ee4c 100644 --- a/editor/components/commerce/collection.tsx +++ b/editor/components/commerce/collection.tsx @@ -2,6 +2,7 @@ import type { ShopifyCollection } from "@reacteditor/field-shopify"; import { useCollectionProducts } from "@/editor/hooks/use-shopify-collections"; import { ProductCard } from "./product-card"; import { Typography } from "@/editor/theme/Typography"; +import { Skeleton } from "@/components/ui/skeleton"; export type CollectionProps = { collection: ShopifyCollection | null; @@ -17,10 +18,19 @@ export function CollectionView({ if (!selected) { return ( -
+
-
- Pick a collection to render this page. +
+ + + {showDescription === "yes" ? ( + + ) : null} +
+
+ {Array.from({ length: 8 }).map((_, i) => ( + + ))}
@@ -50,10 +60,7 @@ export function CollectionView({
{loading ? Array.from({ length: 8 }).map((_, i) => ( -
+ )) : products.map((p: any) => )}
diff --git a/editor/components/commerce/featured-product.tsx b/editor/components/commerce/featured-product.tsx index 0d90a02..1aee5f5 100644 --- a/editor/components/commerce/featured-product.tsx +++ b/editor/components/commerce/featured-product.tsx @@ -3,6 +3,8 @@ import type { ShopifyProduct } from "@reacteditor/field-shopify"; import { useProduct } from "@/editor/hooks/use-shopify-products"; import { useShopifyCart } from "@/editor/hooks/use-shopify-cart"; import { Typography } from "@/editor/theme/Typography"; +import { Skeleton } from "@/components/ui/skeleton"; +import { cn } from "@/lib/utils"; export type FeaturedProductProps = { product: ShopifyProduct | null; @@ -23,21 +25,31 @@ export function FeaturedProductView({ const product: any = full ?? selected; const cart = useShopifyCart(); - if (!selected && loading) { - return ( -
-
-
-
-
- ); - } if (!product) { return ( -
-
-
- Pick a product to feature here. +
+
+
+ +
+
+ + + +
+ + + +
+
+ + +
diff --git a/editor/components/commerce/product-detail/index.tsx b/editor/components/commerce/product-detail/index.tsx index fcc020c..688724a 100644 --- a/editor/components/commerce/product-detail/index.tsx +++ b/editor/components/commerce/product-detail/index.tsx @@ -9,6 +9,7 @@ import ProductDetailInfo from './product-detail-info'; import ProductRecommendations from './product-recommendations'; import { Button } from '@/editor/components/ui/button'; import { Alert, AlertTitle, AlertDescription } from '@/editor/components/ui/alert'; +import { Skeleton } from '@/components/ui/skeleton'; import { Breadcrumb, BreadcrumbList, @@ -111,53 +112,51 @@ const ProductDetail: React.FC = ({ handle: handleProp }) => } }; - if (loading) { + if (loading || !handle || !product) { + if (error && handle && !loading) { + return ( +
+
+

Product not found

+

+ {error} +

+ +
+
+ ); + } + return (
- {/* Image Gallery Skeleton */}
-
+
{Array.from({ length: 4 }).map((_, i) => ( -
+ ))}
- {/* Product Info Skeleton */} -
-
-
-
-
-
+
+ + + + +
); } - if (error || !product) { - return ( -
-
-

Product not found

-

- {error || "The requested product could not be found."} -

- -
-
- ); - } - return (
diff --git a/editor/components/commerce/product-details.tsx b/editor/components/commerce/product-details.tsx index 69dfd67..3b901c3 100644 --- a/editor/components/commerce/product-details.tsx +++ b/editor/components/commerce/product-details.tsx @@ -5,6 +5,7 @@ import { useProduct } from "@/editor/hooks/use-shopify-products"; import { useShopifyCart } from "@/editor/hooks/use-shopify-cart"; import { Typography } from "@/editor/theme/Typography"; import { cn } from "@/editor/lib/utils"; +import { Skeleton } from "@/components/ui/skeleton"; export type ProductDetailsProps = { product: ShopifyProduct | null; @@ -26,42 +27,39 @@ export function ProductDetailsView({ product: selected }: ProductDetailsProps) { } }, [product]); - if (!handle) { + if (!handle || loading || !product) { return ( -
-
-
- Pick a product to render this page. +
+
+
+ +
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
-
-
- ); - } - - if (loading) { - return ( -
-
-
-
-
-
-
-
-
-
- ); - } - - if (!product) { - return ( -
-
-
-

Product not found

-

- The requested product could not be found. -

+
+ + +
+ +
+ + + +
+
+
+ + +
+
+ + + + +
diff --git a/editor/components/commerce/recommended-products.tsx b/editor/components/commerce/recommended-products.tsx index 82758f5..71d4396 100644 --- a/editor/components/commerce/recommended-products.tsx +++ b/editor/components/commerce/recommended-products.tsx @@ -5,6 +5,7 @@ import { } from "@/editor/hooks/use-shopify-products"; import { ProductCard } from "./product-card"; import { Typography } from "@/editor/theme/Typography"; +import { Skeleton } from "@/components/ui/skeleton"; export type RecommendedProductsProps = { product: ShopifyProduct | null; @@ -25,10 +26,16 @@ export function RecommendedProductsView({ if (!selected) { return ( -
+
-
- Pick a product to load recommendations. +
+ {tagline ? : null} + +
+
+ {Array.from({ length: limit }).map((_, i) => ( + + ))}
@@ -50,10 +57,7 @@ export function RecommendedProductsView({
{items.length === 0 ? Array.from({ length: limit }).map((_, i) => ( -
+ )) : items.map((p: any) => )}
diff --git a/editor/components/navigation/navigation.editor.tsx b/editor/components/navigation/navigation.editor.tsx index 057e9b7..7b1c28a 100644 --- a/editor/components/navigation/navigation.editor.tsx +++ b/editor/components/navigation/navigation.editor.tsx @@ -16,10 +16,11 @@ export const navigationEditor: ComponentConfig = { { label: "About", href: "/about" }, ], showSearch: "yes", - showAccount: "yes", showCart: "yes", sticky: "yes", tone: "default", + bannerText: "", + bannerTone: "accent", }, fields: { brand: { label: "Brand", type: "text", contentEditable: true }, @@ -33,16 +34,22 @@ export const navigationEditor: ComponentConfig = { href: { label: "Link", type: "text" }, }, }, - showSearch: { - label: "Search icon", - type: "radio", + bannerText: { + label: "Banner text", + type: "text", + contentEditable: true, + }, + bannerTone: { + label: "Banner tone", + type: "select", options: [ - { label: "Show", value: "yes" }, - { label: "Hide", value: "no" }, + { label: "Default", value: "default" }, + { label: "Accent", value: "accent" }, + { label: "Inverse (dark)", value: "inverse" }, ], }, - showAccount: { - label: "Account icon", + showSearch: { + label: "Search icon", type: "radio", options: [ { label: "Show", value: "yes" }, diff --git a/editor/components/navigation/navigation.tsx b/editor/components/navigation/navigation.tsx index 4683b99..3594206 100644 --- a/editor/components/navigation/navigation.tsx +++ b/editor/components/navigation/navigation.tsx @@ -1,4 +1,4 @@ -import { Menu as MenuIcon, ShoppingBag, Search, User } from "lucide-react"; +import { Menu as MenuIcon, ShoppingBag, Search } from "lucide-react"; import { useState } from "react"; import { Link } from "react-router"; import { useShopifyCart } from "@/editor/hooks/use-shopify-cart"; @@ -9,20 +9,22 @@ export type NavigationProps = { brand: string; links: Array<{ label: string; href: string }>; showSearch: "yes" | "no"; - showAccount: "yes" | "no"; showCart: "yes" | "no"; sticky: "yes" | "no"; tone: "default" | "muted" | "inverse"; + bannerText: string; + bannerTone: "default" | "accent" | "inverse"; }; export function Navigation({ brand, links, showSearch, - showAccount, showCart, sticky, tone, + bannerText, + bannerTone, }: NavigationProps) { const [mobileOpen, setMobileOpen] = useState(false); const [cartOpen, setCartOpen] = useState(false); @@ -35,12 +37,33 @@ export function Navigation({ inverse: "bg-foreground text-background", }; + const bannerToneClass: Record = { + default: "bg-muted text-foreground", + accent: "bg-primary text-primary-foreground", + inverse: "bg-foreground text-background", + }; + + const hasBanner = typeof bannerText === "string" && bannerText.trim().length > 0; + return ( <> +
+ {hasBanner && ( +
+
+ {bannerText} +
+
+ )}
@@ -74,15 +97,6 @@ export function Navigation({ )} - {showAccount === "yes" && ( - - - - )} {showCart === "yes" && (
+
{/* Mobile menu */} diff --git a/editor/components/testimonials/testimonials.tsx b/editor/components/testimonials/testimonials.tsx index 3ee7aff..f2e428a 100644 --- a/editor/components/testimonials/testimonials.tsx +++ b/editor/components/testimonials/testimonials.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { Quote, ArrowLeft, ArrowRight } from "lucide-react"; +import { ArrowLeft, ArrowRight } from "lucide-react"; import { Typography } from "@/editor/theme/Typography"; export type TestimonialsProps = { @@ -30,16 +30,11 @@ export function Testimonials({ tagline, heading, items }: TestimonialsProps) { {item ? (
-
- "{item.quote}" + {item.quote}
{item.avatar ? ( diff --git a/editor/config/initial-data.ts b/editor/config/initial-data.ts index aa8f4d6..d4ba0fd 100644 --- a/editor/config/initial-data.ts +++ b/editor/config/initial-data.ts @@ -26,10 +26,11 @@ export const initialData: Record = { ], cta: { label: "", href: "" }, showSearch: "yes", - showAccount: "yes", showCart: "yes", sticky: "yes", tone: "default", + bannerText: "", + bannerTone: "accent", }, }, {