add skeleton resolvers
This commit is contained in:
@@ -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 (
|
||||
<div className="pt-4 pb-16">
|
||||
<div className="container mx-auto px-4">
|
||||
<h2 className="text-5xl font-bold text-center mb-16 text-gray-900 font-heading">
|
||||
{formattedTitle}
|
||||
</h2>
|
||||
<div className="mb-16 flex justify-center">
|
||||
<Skeleton className="h-12 w-64" />
|
||||
</div>
|
||||
|
||||
{/* Loading Skeleton */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
|
||||
{Array.from({ length: 8 }).map((_, index) => (
|
||||
<div key={index} className="bg-white rounded-lg shadow-md overflow-hidden animate-pulse">
|
||||
<div className="aspect-square bg-gray-200"></div>
|
||||
<div className="p-6">
|
||||
<div className="h-6 bg-gray-200 rounded mb-2"></div>
|
||||
<div className="h-4 bg-gray-200 rounded mb-4"></div>
|
||||
<div className="h-8 bg-gray-200 rounded mb-4"></div>
|
||||
<div className="h-12 bg-gray-200 rounded"></div>
|
||||
</div>
|
||||
<div key={index} className="flex flex-col gap-3">
|
||||
<Skeleton className="aspect-square w-full" />
|
||||
<Skeleton className="h-5 w-3/4" />
|
||||
<Skeleton className="h-4 w-1/3" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<section className="py-20">
|
||||
<section className="bg-background pb-24 pt-12 md:pt-20">
|
||||
<div className="container mx-auto max-w-7xl px-6">
|
||||
<div className="mx-auto max-w-md rounded-md border border-dashed border-border p-8 text-center text-sm text-muted-foreground">
|
||||
Pick a collection to render this page.
|
||||
<header className="mx-auto mb-14 flex max-w-2xl flex-col items-center gap-3 text-center">
|
||||
<Skeleton className="h-3 w-24" />
|
||||
<Skeleton className="h-10 w-3/4" />
|
||||
{showDescription === "yes" ? (
|
||||
<Skeleton className="mt-1 h-5 w-2/3" />
|
||||
) : null}
|
||||
</header>
|
||||
<div className="grid grid-cols-2 gap-x-6 gap-y-12 md:grid-cols-3 lg:grid-cols-4">
|
||||
{Array.from({ length: 8 }).map((_, i) => (
|
||||
<Skeleton key={i} className="aspect-[4/5] w-full" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -50,10 +60,7 @@ export function CollectionView({
|
||||
<div className="grid grid-cols-2 gap-x-6 gap-y-12 md:grid-cols-3 lg:grid-cols-4">
|
||||
{loading
|
||||
? Array.from({ length: 8 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="aspect-[4/5] w-full animate-pulse rounded-md bg-muted"
|
||||
/>
|
||||
<Skeleton key={i} className="aspect-[4/5] w-full" />
|
||||
))
|
||||
: products.map((p: any) => <ProductCard key={p.id} product={p} />)}
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<section className="py-20 md:py-28">
|
||||
<div className="container mx-auto max-w-7xl px-6">
|
||||
<div className="aspect-[2/1] w-full animate-pulse rounded-lg bg-muted" />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
if (!product) {
|
||||
return (
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto max-w-7xl px-6">
|
||||
<div className="mx-auto max-w-md rounded-md border border-dashed border-border p-8 text-center text-sm text-muted-foreground">
|
||||
Pick a product to feature here.
|
||||
<section
|
||||
className={cn(
|
||||
"py-20 md:py-28",
|
||||
tone === "muted" ? "bg-muted/40" : "bg-background",
|
||||
)}
|
||||
>
|
||||
<div className="container mx-auto grid max-w-7xl grid-cols-1 items-center gap-10 px-6 md:grid-cols-2 md:gap-16">
|
||||
<div className={cn(align === "right" && "md:order-2")}>
|
||||
<Skeleton className="aspect-[4/5] w-full" />
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start gap-5">
|
||||
<Skeleton className="h-3 w-24" />
|
||||
<Skeleton className="h-10 w-3/4" />
|
||||
<Skeleton className="h-6 w-32" />
|
||||
<div className="w-full max-w-md space-y-2">
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-5/6" />
|
||||
<Skeleton className="h-4 w-4/6" />
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap gap-3">
|
||||
<Skeleton className="h-11 w-32 rounded-full" />
|
||||
<Skeleton className="h-11 w-32 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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<ProductDetailProps> = ({ handle: handleProp }) =>
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
if (loading || !handle || !product) {
|
||||
if (error && handle && !loading) {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="mx-auto flex max-w-md flex-col items-start gap-3 rounded-lg border border-border bg-foreground/[0.02] p-6">
|
||||
<p className="text-sm font-medium">Product not found</p>
|
||||
<p className="font-mono text-xs leading-relaxed text-muted-foreground line-clamp-3">
|
||||
{error}
|
||||
</p>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => window.history.back()}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
{/* Image Gallery Skeleton */}
|
||||
<div>
|
||||
<div className="aspect-square bg-gray-200 rounded-lg animate-pulse mb-4"></div>
|
||||
<Skeleton className="aspect-square w-full mb-4" />
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<div key={i} className="aspect-square bg-gray-200 rounded animate-pulse"></div>
|
||||
<Skeleton key={i} className="aspect-square w-full" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Product Info Skeleton */}
|
||||
<div>
|
||||
<div className="h-8 bg-gray-200 rounded mb-4 animate-pulse"></div>
|
||||
<div className="h-6 bg-gray-200 rounded mb-6 w-1/3 animate-pulse"></div>
|
||||
<div className="h-24 bg-gray-200 rounded mb-6 animate-pulse"></div>
|
||||
<div className="h-12 bg-gray-200 rounded mb-4 animate-pulse"></div>
|
||||
<div className="h-12 bg-gray-200 rounded animate-pulse"></div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Skeleton className="h-8 w-3/4" />
|
||||
<Skeleton className="h-6 w-1/3" />
|
||||
<Skeleton className="h-24 w-full" />
|
||||
<Skeleton className="h-12 w-full" />
|
||||
<Skeleton className="h-12 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !product) {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="mx-auto flex max-w-md flex-col items-start gap-3 rounded-lg border border-border bg-foreground/[0.02] p-6">
|
||||
<p className="text-sm font-medium">Product not found</p>
|
||||
<p className="font-mono text-xs leading-relaxed text-muted-foreground line-clamp-3">
|
||||
{error || "The requested product could not be found."}
|
||||
</p>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => window.history.back()}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
|
||||
@@ -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 (
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto max-w-7xl px-6">
|
||||
<div className="mx-auto max-w-md rounded-md border border-dashed border-border p-8 text-center text-sm text-muted-foreground">
|
||||
Pick a product to render this page.
|
||||
<section className="bg-background py-12 md:py-20">
|
||||
<div className="container mx-auto grid max-w-7xl grid-cols-1 gap-10 px-6 md:grid-cols-2 md:gap-16">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Skeleton className="aspect-[4/5] w-full" />
|
||||
<div className="flex gap-3">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<Skeleton key={i} className="aspect-square w-20 flex-shrink-0" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto grid max-w-7xl grid-cols-1 gap-12 px-6 md:grid-cols-2">
|
||||
<div className="aspect-[4/5] w-full animate-pulse rounded-md bg-muted" />
|
||||
<div className="space-y-4">
|
||||
<div className="h-8 w-3/4 animate-pulse rounded bg-muted" />
|
||||
<div className="h-5 w-1/4 animate-pulse rounded bg-muted" />
|
||||
<div className="h-32 w-full animate-pulse rounded bg-muted" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto max-w-7xl px-6">
|
||||
<div className="mx-auto max-w-md rounded-md border border-border p-6 text-sm">
|
||||
<p className="font-medium">Product not found</p>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
The requested product could not be found.
|
||||
</p>
|
||||
<div className="flex flex-col gap-6">
|
||||
<Skeleton className="h-10 w-3/4" />
|
||||
<Skeleton className="h-6 w-1/4" />
|
||||
<div className="flex flex-col gap-3">
|
||||
<Skeleton className="h-3 w-16" />
|
||||
<div className="flex gap-2">
|
||||
<Skeleton className="h-10 w-16 rounded-full" />
|
||||
<Skeleton className="h-10 w-16 rounded-full" />
|
||||
<Skeleton className="h-10 w-16 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 pt-2">
|
||||
<Skeleton className="h-11 w-32 rounded-full" />
|
||||
<Skeleton className="h-11 flex-1 rounded-full" />
|
||||
</div>
|
||||
<div className="space-y-2 border-t border-border pt-6">
|
||||
<Skeleton className="h-3 w-20" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-5/6" />
|
||||
<Skeleton className="h-4 w-4/6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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 (
|
||||
<section className="py-20">
|
||||
<section className="bg-background py-20 md:py-28">
|
||||
<div className="container mx-auto max-w-7xl px-6">
|
||||
<div className="mx-auto max-w-md rounded-md border border-dashed border-border p-8 text-center text-sm text-muted-foreground">
|
||||
Pick a product to load recommendations.
|
||||
<div className="mb-12 flex max-w-xl flex-col gap-3">
|
||||
{tagline ? <Skeleton className="h-3 w-24" /> : null}
|
||||
<Skeleton className="h-8 w-2/3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-x-6 gap-y-12 md:grid-cols-4">
|
||||
{Array.from({ length: limit }).map((_, i) => (
|
||||
<Skeleton key={i} className="aspect-[4/5] w-full" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -50,10 +57,7 @@ export function RecommendedProductsView({
|
||||
<div className="grid grid-cols-2 gap-x-6 gap-y-12 md:grid-cols-4">
|
||||
{items.length === 0
|
||||
? Array.from({ length: limit }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="aspect-[4/5] w-full animate-pulse rounded-md bg-muted"
|
||||
/>
|
||||
<Skeleton key={i} className="aspect-[4/5] w-full" />
|
||||
))
|
||||
: items.map((p: any) => <ProductCard key={p.id} product={p} />)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user