-
- 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 && (
+
+ )}
@@ -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",
},
},
{