Update react editor
This commit is contained in:
15
app/[[...slug]]/page.tsx
Normal file
15
app/[[...slug]]/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import { Render } from "@reacteditor/core";
|
||||||
|
import { appConfig } from "@/editor.config";
|
||||||
|
import resolveRoute from "@/lib/resolve-route";
|
||||||
|
import schema from "@/app.schema.json";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const params = useParams();
|
||||||
|
const slug = Array.isArray(params?.slug) ? (params.slug as string[]) : [];
|
||||||
|
const { key } = resolveRoute(slug);
|
||||||
|
const pageData = (schema as any)[key];
|
||||||
|
return <Render config={appConfig as any} data={pageData} />;
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useMemo } from "react";
|
|
||||||
import { useParams } from "next/navigation";
|
|
||||||
import { Editor } from "@reacteditor/core";
|
|
||||||
import createTailwindCdnPlugin from "@reacteditor/plugin-tailwind-cdn";
|
|
||||||
import createShopifyPlugin from "@reacteditor/plugin-shopify";
|
|
||||||
import { collectionsConfig } from "@/editor.config";
|
|
||||||
import schema from "@/app.schema.json";
|
|
||||||
|
|
||||||
const currentRoute = "/collections/:handle";
|
|
||||||
|
|
||||||
export default function CollectionEditorPage() {
|
|
||||||
const params = useParams();
|
|
||||||
const handle = typeof params?.handle === "string" ? params.handle : "";
|
|
||||||
const data = (schema as any)["/collections/:handle"];
|
|
||||||
const plugins = useMemo(
|
|
||||||
() => [
|
|
||||||
createTailwindCdnPlugin(),
|
|
||||||
createShopifyPlugin({
|
|
||||||
storeDomain:
|
|
||||||
process.env.NEXT_PUBLIC_SHOPIFY_DOMAIN ?? "mock.shop",
|
|
||||||
publicAccessToken:
|
|
||||||
process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePublish = async (nextData: any, route?: any) => {
|
|
||||||
const resolved = route ?? { key: currentRoute };
|
|
||||||
if (typeof window !== "undefined" && window.parent !== window) {
|
|
||||||
window.parent.postMessage(
|
|
||||||
{ type: "PUBLISH", data: { data: nextData, route: resolved } },
|
|
||||||
"*",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-screen w-screen">
|
|
||||||
<Editor
|
|
||||||
config={collectionsConfig as any}
|
|
||||||
data={data}
|
|
||||||
currentRoute={currentRoute}
|
|
||||||
route={{ key: currentRoute, path: `/collections/${handle}`, params: { handle } }}
|
|
||||||
plugins={plugins}
|
|
||||||
iframe={{ enabled: true }}
|
|
||||||
ui={{ leftSideBarVisible: false }}
|
|
||||||
onPublish={handlePublish}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Render } from "@reacteditor/core";
|
|
||||||
import { collectionsConfig } from "@/editor.config";
|
|
||||||
import schema from "@/app.schema.json";
|
|
||||||
|
|
||||||
export default function CollectionPage() {
|
|
||||||
const pageData = (schema as any)["/collections/:handle"];
|
|
||||||
return <Render config={collectionsConfig as any} data={pageData} />;
|
|
||||||
}
|
|
||||||
@@ -4,22 +4,22 @@ import { useMemo } from "react";
|
|||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Editor } from "@reacteditor/core";
|
import { Editor } from "@reacteditor/core";
|
||||||
import createTailwindCdnPlugin from "@reacteditor/plugin-tailwind-cdn";
|
import createTailwindCdnPlugin from "@reacteditor/plugin-tailwind-cdn";
|
||||||
import createShopifyPlugin from "@reacteditor/plugin-shopify";
|
import { createShopifyPlugin } from "@reacteditor/plugin-shopify";
|
||||||
import { productConfig } from "@/editor.config";
|
import { appConfig } from "@/editor.config";
|
||||||
|
import resolveRoute from "@/lib/resolve-route";
|
||||||
import schema from "@/app.schema.json";
|
import schema from "@/app.schema.json";
|
||||||
|
|
||||||
const currentRoute = "/products/:handle";
|
export default function EditorPage() {
|
||||||
|
|
||||||
export default function ProductEditorPage() {
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const handle = typeof params?.handle === "string" ? params.handle : "";
|
const slug = Array.isArray(params?.slug) ? (params.slug as string[]) : [];
|
||||||
const data = (schema as any)["/products/:handle"];
|
const { key, path, params: routeParams } = resolveRoute(slug);
|
||||||
|
const data = (schema as any)[key];
|
||||||
|
|
||||||
const plugins = useMemo(
|
const plugins = useMemo(
|
||||||
() => [
|
() => [
|
||||||
createTailwindCdnPlugin(),
|
createTailwindCdnPlugin(),
|
||||||
createShopifyPlugin({
|
createShopifyPlugin({
|
||||||
storeDomain:
|
storeDomain: process.env.NEXT_PUBLIC_SHOPIFY_DOMAIN ?? "mock.shop",
|
||||||
process.env.NEXT_PUBLIC_SHOPIFY_DOMAIN ?? "mock.shop",
|
|
||||||
publicAccessToken:
|
publicAccessToken:
|
||||||
process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN,
|
process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN,
|
||||||
}),
|
}),
|
||||||
@@ -28,7 +28,7 @@ export default function ProductEditorPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handlePublish = async (nextData: any, route?: any) => {
|
const handlePublish = async (nextData: any, route?: any) => {
|
||||||
const resolved = route ?? { key: currentRoute };
|
const resolved = route ?? { key };
|
||||||
if (typeof window !== "undefined" && window.parent !== window) {
|
if (typeof window !== "undefined" && window.parent !== window) {
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
{ type: "PUBLISH", data: { data: nextData, route: resolved } },
|
{ type: "PUBLISH", data: { data: nextData, route: resolved } },
|
||||||
@@ -41,12 +41,10 @@ export default function ProductEditorPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="h-screen w-screen">
|
<div className="h-screen w-screen">
|
||||||
<Editor
|
<Editor
|
||||||
config={productConfig as any}
|
config={appConfig as any}
|
||||||
data={data}
|
data={data}
|
||||||
currentRoute={currentRoute}
|
route={{ key, path, params: routeParams }}
|
||||||
route={{ key: currentRoute, path: `/products/${handle}`, params: { handle } }}
|
|
||||||
plugins={plugins}
|
plugins={plugins}
|
||||||
iframe={{ enabled: true }}
|
|
||||||
ui={{ leftSideBarVisible: false }}
|
ui={{ leftSideBarVisible: false }}
|
||||||
onPublish={handlePublish}
|
onPublish={handlePublish}
|
||||||
/>
|
/>
|
||||||
10
app/page.tsx
10
app/page.tsx
@@ -1,10 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Render } from "@reacteditor/core";
|
|
||||||
import { homeConfig } from "@/editor.config";
|
|
||||||
import schema from "@/app.schema.json";
|
|
||||||
|
|
||||||
export default function HomePage() {
|
|
||||||
const pageData = (schema as any)["/"];
|
|
||||||
return <Render config={homeConfig as any} data={pageData} />;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Render } from "@reacteditor/core";
|
|
||||||
import { productConfig } from "@/editor.config";
|
|
||||||
import schema from "@/app.schema.json";
|
|
||||||
|
|
||||||
export default function ProductPage() {
|
|
||||||
const pageData = (schema as any)["/products/:handle"];
|
|
||||||
return <Render config={productConfig as any} data={pageData} />;
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useMemo } from "react";
|
|
||||||
import { Editor } from "@reacteditor/core";
|
|
||||||
import createTailwindCdnPlugin from "@reacteditor/plugin-tailwind-cdn";
|
|
||||||
import createShopifyPlugin from "@reacteditor/plugin-shopify";
|
|
||||||
import { searchConfig } from "@/editor.config";
|
|
||||||
import schema from "@/app.schema.json";
|
|
||||||
|
|
||||||
const currentRoute = "/search";
|
|
||||||
|
|
||||||
export default function SearchEditorPage() {
|
|
||||||
const data = (schema as any)["/search"];
|
|
||||||
const plugins = useMemo(
|
|
||||||
() => [
|
|
||||||
createTailwindCdnPlugin(),
|
|
||||||
createShopifyPlugin({
|
|
||||||
storeDomain:
|
|
||||||
process.env.NEXT_PUBLIC_SHOPIFY_DOMAIN ?? "mock.shop",
|
|
||||||
publicAccessToken:
|
|
||||||
process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePublish = async (nextData: any, route?: any) => {
|
|
||||||
const resolved = route ?? { key: currentRoute };
|
|
||||||
if (typeof window !== "undefined" && window.parent !== window) {
|
|
||||||
window.parent.postMessage(
|
|
||||||
{ type: "PUBLISH", data: { data: nextData, route: resolved } },
|
|
||||||
"*",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-screen w-screen">
|
|
||||||
<Editor
|
|
||||||
config={searchConfig as any}
|
|
||||||
data={data}
|
|
||||||
currentRoute={currentRoute}
|
|
||||||
route={{ key: currentRoute, path: currentRoute, params: {} }}
|
|
||||||
plugins={plugins}
|
|
||||||
iframe={{ enabled: true }}
|
|
||||||
ui={{ leftSideBarVisible: false }}
|
|
||||||
onPublish={handlePublish}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Render } from "@reacteditor/core";
|
|
||||||
import { searchConfig } from "@/editor.config";
|
|
||||||
import schema from "@/app.schema.json";
|
|
||||||
|
|
||||||
export default function SearchPage() {
|
|
||||||
const pageData = (schema as any)["/search"];
|
|
||||||
return <Render config={searchConfig as any} data={pageData} />;
|
|
||||||
}
|
|
||||||
@@ -181,7 +181,7 @@ const CartDrawer: React.FC = () => {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="text-muted-foreground hover:text-foreground"
|
className="h-auto w-auto p-2 text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<X size={18} />
|
<X size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { useParams } from 'next/navigation';
|
import { useRouteHandle } from '@/lib/resolve-route';
|
||||||
import { ChevronDown, SlidersHorizontal } from 'lucide-react';
|
import { ChevronDown, SlidersHorizontal } from 'lucide-react';
|
||||||
import type { ShopifyCollection } from '@reacteditor/field-shopify';
|
import type { ShopifyCollection } from '@reacteditor/field-shopify';
|
||||||
import {
|
import {
|
||||||
@@ -358,10 +358,8 @@ function buildProductFilters(active: ActiveFilters): ProductFilter[] {
|
|||||||
|
|
||||||
export function CollectionView(props: CollectionProps) {
|
export function CollectionView(props: CollectionProps) {
|
||||||
const { collection: selected, showDescription, showCoverImage, customCoverImage, columns, limit, defaultSort } = props;
|
const { collection: selected, showDescription, showCoverImage, customCoverImage, columns, limit, defaultSort } = props;
|
||||||
const params = useParams();
|
const routeHandle = useRouteHandle();
|
||||||
const paramHandle =
|
const handle = selected?.handle ?? routeHandle ?? '';
|
||||||
typeof params?.handle === 'string' ? params.handle : undefined;
|
|
||||||
const handle = selected?.handle ?? paramHandle ?? '';
|
|
||||||
|
|
||||||
const [sort, setSort] = useState<CollectionSortKey>(defaultSort);
|
const [sort, setSort] = useState<CollectionSortKey>(defaultSort);
|
||||||
const [reverse, setReverse] = useState(false);
|
const [reverse, setReverse] = useState(false);
|
||||||
@@ -425,7 +423,7 @@ export function CollectionView(props: CollectionProps) {
|
|||||||
const description = collection?.description ?? (selected as any)?.description;
|
const description = collection?.description ?? (selected as any)?.description;
|
||||||
const collectionImage = customCoverImage || collection?.image?.url;
|
const collectionImage = customCoverImage || collection?.image?.url;
|
||||||
|
|
||||||
if (!selected && !paramHandle) {
|
if (!selected && !routeHandle) {
|
||||||
return (
|
return (
|
||||||
<section className="bg-background pb-24 pt-12 md:pt-20">
|
<section className="bg-background pb-24 pt-12 md:pt-20">
|
||||||
<Container>
|
<Container>
|
||||||
@@ -463,7 +461,7 @@ export function CollectionView(props: CollectionProps) {
|
|||||||
Collection
|
Collection
|
||||||
</p>
|
</p>
|
||||||
<Typography variant="h1">
|
<Typography variant="h1">
|
||||||
{collection?.title ?? (selected as any)?.title ?? paramHandle}
|
{collection?.title ?? (selected as any)?.title ?? routeHandle}
|
||||||
</Typography>
|
</Typography>
|
||||||
{showDescription === 'yes' && description ? (
|
{showDescription === 'yes' && description ? (
|
||||||
<Typography variant="subtitle1" className="mt-4 max-w-2xl">
|
<Typography variant="subtitle1" className="mt-4 max-w-2xl">
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useProduct, type Product } from '@/hooks/use-shopify-products';
|
import { useProduct, type Product } from '@/hooks/use-shopify-products';
|
||||||
|
import { useRouteHandle } from '@/lib/resolve-route';
|
||||||
import { useShopifyCart } from '@/hooks/use-shopify-cart';
|
import { useShopifyCart } from '@/hooks/use-shopify-cart';
|
||||||
import ProductDetailGallery from './product-detail-gallery';
|
import ProductDetailGallery from './product-detail-gallery';
|
||||||
import ProductDetailInfo from './product-detail-info';
|
import ProductDetailInfo from './product-detail-info';
|
||||||
import ProductRecommendations from './product-recommendations';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
@@ -44,7 +44,8 @@ interface ProductDetailProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ProductDetail: React.FC<ProductDetailProps> = ({ handle: handleProp }) => {
|
const ProductDetail: React.FC<ProductDetailProps> = ({ handle: handleProp }) => {
|
||||||
const handle = handleProp || '';
|
const routeHandle = useRouteHandle();
|
||||||
|
const handle = handleProp || routeHandle || '';
|
||||||
const { addItem, openCart } = useShopifyCart();
|
const { addItem, openCart } = useShopifyCart();
|
||||||
|
|
||||||
const { product, loading, error } = useProduct(handle);
|
const { product, loading, error } = useProduct(handle);
|
||||||
@@ -197,8 +198,6 @@ const ProductDetail: React.FC<ProductDetailProps> = ({ handle: handleProp }) =>
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ProductRecommendations productId={product.id} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useRouteHandle } from "@/lib/resolve-route";
|
||||||
import type { ShopifyProduct } from "@reacteditor/field-shopify";
|
import type { ShopifyProduct } from "@reacteditor/field-shopify";
|
||||||
import { useProduct } from "@/hooks/use-shopify-products";
|
import { useProduct } from "@/hooks/use-shopify-products";
|
||||||
import { useShopifyCart } from "@/hooks/use-shopify-cart";
|
import { useShopifyCart } from "@/hooks/use-shopify-cart";
|
||||||
@@ -14,10 +14,8 @@ export type ProductDetailsProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function ProductDetailsView({ product: selected }: ProductDetailsProps) {
|
export function ProductDetailsView({ product: selected }: ProductDetailsProps) {
|
||||||
const params = useParams();
|
const routeHandle = useRouteHandle();
|
||||||
const paramHandle =
|
const handle = selected?.handle ?? routeHandle ?? null;
|
||||||
typeof params?.handle === "string" ? params.handle : undefined;
|
|
||||||
const handle = selected?.handle ?? paramHandle ?? null;
|
|
||||||
const { product, loading } = useProduct(handle);
|
const { product, loading } = useProduct(handle);
|
||||||
const cart = useShopifyCart();
|
const cart = useShopifyCart();
|
||||||
const [activeImage, setActiveImage] = useState(0);
|
const [activeImage, setActiveImage] = useState(0);
|
||||||
|
|||||||
25
components/commerce/product-recommendations.editor.tsx
Normal file
25
components/commerce/product-recommendations.editor.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentConfig } from "@reacteditor/core";
|
||||||
|
import { LayoutGrid } from "lucide-react";
|
||||||
|
import {
|
||||||
|
ProductRecommendationsView,
|
||||||
|
type ProductRecommendationsProps,
|
||||||
|
} from "@/components/commerce/product-recommendations";
|
||||||
|
|
||||||
|
const productRecommendationsEditor: ComponentConfig<ProductRecommendationsProps> = {
|
||||||
|
label: "Product recommendations",
|
||||||
|
icon: <LayoutGrid size={16} />,
|
||||||
|
category: "commerce",
|
||||||
|
defaultProps: {
|
||||||
|
product: null,
|
||||||
|
heading: "You Might Also Like",
|
||||||
|
limit: 4,
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
product: { label: "Source product", type: "shopifyProduct" } as any,
|
||||||
|
heading: { label: "Heading", type: "text", contentEditable: true },
|
||||||
|
limit: { label: "Limit", type: "number", min: 2, max: 8 },
|
||||||
|
},
|
||||||
|
render: (props) => <ProductRecommendationsView {...props} />,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default productRecommendationsEditor;
|
||||||
@@ -1,15 +1,31 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useProductRecommendations } from '@/hooks/use-shopify-products';
|
import type { ShopifyProduct } from '@reacteditor/field-shopify';
|
||||||
import ProductCard from '../product-card';
|
import {
|
||||||
|
useProduct,
|
||||||
|
useProductRecommendations,
|
||||||
|
} from '@/hooks/use-shopify-products';
|
||||||
|
import { useRouteHandle } from '@/lib/resolve-route';
|
||||||
|
import { ProductCard } from './product-card';
|
||||||
|
|
||||||
interface ProductRecommendationsProps {
|
export type ProductRecommendationsProps = {
|
||||||
productId: string;
|
product: ShopifyProduct | null;
|
||||||
}
|
heading: string;
|
||||||
|
limit: number;
|
||||||
|
};
|
||||||
|
|
||||||
const ProductRecommendations: React.FC<ProductRecommendationsProps> = ({ productId }) => {
|
export function ProductRecommendationsView({
|
||||||
const { recommendations, loading, error } = useProductRecommendations(productId);
|
product: selected,
|
||||||
|
heading,
|
||||||
|
limit,
|
||||||
|
}: ProductRecommendationsProps) {
|
||||||
|
const routeHandle = useRouteHandle();
|
||||||
|
const handle = selected?.handle ?? routeHandle ?? null;
|
||||||
|
const { product } = useProduct(handle);
|
||||||
|
const { recommendations, loading, error } = useProductRecommendations(
|
||||||
|
product?.id ?? null,
|
||||||
|
);
|
||||||
|
|
||||||
// Don't show section if we're not loading and have no recommendations
|
// Don't show section if we're not loading and have no recommendations
|
||||||
if (!loading && (!recommendations || recommendations.length === 0)) {
|
if (!loading && (!recommendations || recommendations.length === 0)) {
|
||||||
@@ -20,12 +36,12 @@ const ProductRecommendations: React.FC<ProductRecommendationsProps> = ({ product
|
|||||||
<div className="bg-muted py-16">
|
<div className="bg-muted py-16">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<h2 className="text-4xl font-bold text-center mb-12 text-foreground font-heading">
|
<h2 className="text-4xl font-bold text-center mb-12 text-foreground font-heading">
|
||||||
You Might Also Like
|
{heading}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
|
||||||
{Array.from({ length: 4 }).map((_, index) => (
|
{Array.from({ length: limit }).map((_, index) => (
|
||||||
<div key={index} className="bg-card rounded-lg shadow-md overflow-hidden animate-pulse">
|
<div key={index} className="bg-card rounded-lg shadow-md overflow-hidden animate-pulse">
|
||||||
<div className="aspect-square bg-muted"></div>
|
<div className="aspect-square bg-muted"></div>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
@@ -43,7 +59,7 @@ const ProductRecommendations: React.FC<ProductRecommendationsProps> = ({ product
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
|
||||||
{recommendations.slice(0, 4).map((recommendedProduct) => (
|
{recommendations.slice(0, limit).map((recommendedProduct) => (
|
||||||
<ProductCard
|
<ProductCard
|
||||||
key={recommendedProduct.id}
|
key={recommendedProduct.id}
|
||||||
product={recommendedProduct}
|
product={recommendedProduct}
|
||||||
@@ -54,6 +70,6 @@ const ProductRecommendations: React.FC<ProductRecommendationsProps> = ({ product
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ProductRecommendations;
|
export default ProductRecommendationsView;
|
||||||
@@ -11,6 +11,7 @@ import collectionGridEditor from "@/components/commerce/collection-grid.editor";
|
|||||||
import collectionEditor from "@/components/commerce/collection.editor";
|
import collectionEditor from "@/components/commerce/collection.editor";
|
||||||
import productDetailsEditor from "@/components/commerce/product-details.editor";
|
import productDetailsEditor from "@/components/commerce/product-details.editor";
|
||||||
import recommendedProductsEditor from "@/components/commerce/recommended-products.editor";
|
import recommendedProductsEditor from "@/components/commerce/recommended-products.editor";
|
||||||
|
import productRecommendationsEditor from "@/components/commerce/product-recommendations.editor";
|
||||||
import searchProductsEditor from "@/components/commerce/search-products.editor";
|
import searchProductsEditor from "@/components/commerce/search-products.editor";
|
||||||
|
|
||||||
import featuresEditor from "@/components/features/features.editor";
|
import featuresEditor from "@/components/features/features.editor";
|
||||||
@@ -32,22 +33,23 @@ const categories = {
|
|||||||
footer: { title: "Footer" },
|
footer: { title: "Footer" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const sharedComponents = {
|
export const appConfig: UserConfig = {
|
||||||
navigation: navigationEditor,
|
|
||||||
footer: footerEditor,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const homeConfig: UserConfig = {
|
|
||||||
root: Root,
|
root: Root,
|
||||||
categories,
|
categories,
|
||||||
components: {
|
components: {
|
||||||
...sharedComponents,
|
navigation: navigationEditor,
|
||||||
|
footer: footerEditor,
|
||||||
hero: heroEditor,
|
hero: heroEditor,
|
||||||
banner: bannerEditor,
|
banner: bannerEditor,
|
||||||
"featured-product": featuredProductEditor,
|
"featured-product": featuredProductEditor,
|
||||||
"products-grid": productsGridEditor,
|
"products-grid": productsGridEditor,
|
||||||
"products-carousel": productsCarouselEditor,
|
"products-carousel": productsCarouselEditor,
|
||||||
"collection-grid": collectionGridEditor,
|
"collection-grid": collectionGridEditor,
|
||||||
|
collection: collectionEditor,
|
||||||
|
"product-details": productDetailsEditor,
|
||||||
|
"recommended-products": recommendedProductsEditor,
|
||||||
|
"product-recommendations": productRecommendationsEditor,
|
||||||
|
"search-products": searchProductsEditor,
|
||||||
features: featuresEditor,
|
features: featuresEditor,
|
||||||
testimonials: testimonialsEditor,
|
testimonials: testimonialsEditor,
|
||||||
"image-gallery": imageGalleryEditor,
|
"image-gallery": imageGalleryEditor,
|
||||||
@@ -57,32 +59,3 @@ export const homeConfig: UserConfig = {
|
|||||||
faq: faqEditor,
|
faq: faqEditor,
|
||||||
} as any,
|
} as any,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const productConfig: UserConfig = {
|
|
||||||
root: Root,
|
|
||||||
categories,
|
|
||||||
components: {
|
|
||||||
...sharedComponents,
|
|
||||||
"product-details": productDetailsEditor,
|
|
||||||
"recommended-products": recommendedProductsEditor,
|
|
||||||
"products-carousel": productsCarouselEditor,
|
|
||||||
} as any,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const collectionsConfig: UserConfig = {
|
|
||||||
root: Root,
|
|
||||||
categories,
|
|
||||||
components: {
|
|
||||||
...sharedComponents,
|
|
||||||
collection: collectionEditor,
|
|
||||||
} as any,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const searchConfig: UserConfig = {
|
|
||||||
root: Root,
|
|
||||||
categories,
|
|
||||||
components: {
|
|
||||||
...sharedComponents,
|
|
||||||
"search-products": searchProductsEditor,
|
|
||||||
} as any,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
const TEMPLATE_PATTERNS: { key: string; prefix: string; param: string }[] = [
|
|
||||||
{ key: "/products/*", prefix: "/products/", param: "handle" },
|
|
||||||
{ key: "/collections/*", prefix: "/collections/", param: "handle" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export type ResolvedEditorPath = {
|
|
||||||
isEdit: boolean;
|
|
||||||
path: string;
|
|
||||||
templateKey: string | null;
|
|
||||||
params: Record<string, string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveEditorPath = (editorPath: string[] = []): ResolvedEditorPath => {
|
|
||||||
const isEdit =
|
|
||||||
editorPath.length > 0 && editorPath[editorPath.length - 1] === "edit";
|
|
||||||
|
|
||||||
const segments = isEdit ? editorPath.slice(0, -1) : editorPath;
|
|
||||||
const path = segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
||||||
|
|
||||||
for (const { key, prefix, param } of TEMPLATE_PATTERNS) {
|
|
||||||
if (path.startsWith(prefix) && path.length > prefix.length) {
|
|
||||||
return {
|
|
||||||
isEdit,
|
|
||||||
path,
|
|
||||||
templateKey: key,
|
|
||||||
params: { [param]: path.slice(prefix.length) },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { isEdit, path, templateKey: null, params: {} };
|
|
||||||
};
|
|
||||||
|
|
||||||
export default resolveEditorPath;
|
|
||||||
37
lib/resolve-route.ts
Normal file
37
lib/resolve-route.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { useParams } from "next/navigation";
|
||||||
|
|
||||||
|
const TEMPLATE_PATTERNS: { key: string; prefix: string; param: string }[] = [
|
||||||
|
{ key: "/products/:handle", prefix: "/products/", param: "handle" },
|
||||||
|
{ key: "/collections/:handle", prefix: "/collections/", param: "handle" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export type ResolvedRoute = {
|
||||||
|
key: string;
|
||||||
|
path: string;
|
||||||
|
params: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveRoute = (slug: string[] = []): ResolvedRoute => {
|
||||||
|
const path = slug.length === 0 ? "/" : `/${slug.join("/")}`;
|
||||||
|
|
||||||
|
for (const { key, prefix, param } of TEMPLATE_PATTERNS) {
|
||||||
|
if (path.startsWith(prefix) && path.length > prefix.length) {
|
||||||
|
return { key, path, params: { [param]: path.slice(prefix.length) } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { key: path, path, params: {} };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the current route's `handle` param from the catch-all slug segments.
|
||||||
|
* Use in client components rendered under `app/[[...slug]]` (and its editor
|
||||||
|
* counterpart) where the handle is no longer exposed as a direct route param.
|
||||||
|
*/
|
||||||
|
export const useRouteHandle = (): string | undefined => {
|
||||||
|
const params = useParams();
|
||||||
|
const slug = Array.isArray(params?.slug) ? (params.slug as string[]) : [];
|
||||||
|
return resolveRoute(slug).params.handle;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default resolveRoute;
|
||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user