update resolve route
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { useRouteHandle } from '@/lib/resolve-route';
|
import { useRouteSegment } from '@/hooks/use-route-segment';
|
||||||
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,7 +358,7 @@ 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 routeHandle = useRouteHandle();
|
const routeHandle = useRouteSegment();
|
||||||
const handle = selected?.handle ?? routeHandle ?? '';
|
const handle = selected?.handle ?? routeHandle ?? '';
|
||||||
|
|
||||||
const [sort, setSort] = useState<CollectionSortKey>(defaultSort);
|
const [sort, setSort] = useState<CollectionSortKey>(defaultSort);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
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 { useRouteSegment } from '@/hooks/use-route-segment';
|
||||||
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';
|
||||||
@@ -44,7 +44,7 @@ interface ProductDetailProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ProductDetail: React.FC<ProductDetailProps> = ({ handle: handleProp }) => {
|
const ProductDetail: React.FC<ProductDetailProps> = ({ handle: handleProp }) => {
|
||||||
const routeHandle = useRouteHandle();
|
const routeHandle = useRouteSegment();
|
||||||
const handle = handleProp || routeHandle || '';
|
const handle = handleProp || routeHandle || '';
|
||||||
const { addItem, openCart } = useShopifyCart();
|
const { addItem, openCart } = useShopifyCart();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useRouteHandle } from "@/lib/resolve-route";
|
import { useRouteSegment } from "@/hooks/use-route-segment";
|
||||||
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,7 +14,7 @@ export type ProductDetailsProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function ProductDetailsView({ product: selected }: ProductDetailsProps) {
|
export function ProductDetailsView({ product: selected }: ProductDetailsProps) {
|
||||||
const routeHandle = useRouteHandle();
|
const routeHandle = useRouteSegment();
|
||||||
const handle = selected?.handle ?? routeHandle ?? null;
|
const handle = selected?.handle ?? routeHandle ?? null;
|
||||||
const { product, loading } = useProduct(handle);
|
const { product, loading } = useProduct(handle);
|
||||||
const cart = useShopifyCart();
|
const cart = useShopifyCart();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
useProduct,
|
useProduct,
|
||||||
useProductRecommendations,
|
useProductRecommendations,
|
||||||
} from '@/hooks/use-shopify-products';
|
} from '@/hooks/use-shopify-products';
|
||||||
import { useRouteHandle } from '@/lib/resolve-route';
|
import { useRouteSegment } from '@/hooks/use-route-segment';
|
||||||
import { ProductCard } from './product-card';
|
import { ProductCard } from './product-card';
|
||||||
|
|
||||||
export type ProductRecommendationsProps = {
|
export type ProductRecommendationsProps = {
|
||||||
@@ -20,7 +20,7 @@ export function ProductRecommendationsView({
|
|||||||
heading,
|
heading,
|
||||||
limit,
|
limit,
|
||||||
}: ProductRecommendationsProps) {
|
}: ProductRecommendationsProps) {
|
||||||
const routeHandle = useRouteHandle();
|
const routeHandle = useRouteSegment();
|
||||||
const handle = selected?.handle ?? routeHandle ?? null;
|
const handle = selected?.handle ?? routeHandle ?? null;
|
||||||
const { product } = useProduct(handle);
|
const { product } = useProduct(handle);
|
||||||
const { recommendations, loading, error } = useProductRecommendations(
|
const { recommendations, loading, error } = useProductRecommendations(
|
||||||
|
|||||||
17
hooks/use-route-segment.ts
Normal file
17
hooks/use-route-segment.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last segment of the current catch-all slug route, e.g. the
|
||||||
|
* `cool-shirt` in `/products/cool-shirt`. Components use this to derive the
|
||||||
|
* resource they should load from the Next.js route segments directly.
|
||||||
|
*/
|
||||||
|
export function useRouteSegment(): string | undefined {
|
||||||
|
const params = useParams();
|
||||||
|
const segments = Array.isArray(params?.slug) ? (params.slug as string[]) : [];
|
||||||
|
// Editor routes are served under `/editor/*`; ignore that prefix so the
|
||||||
|
// resolved segment matches the public route.
|
||||||
|
const routeSegments = segments[0] === "editor" ? segments.slice(1) : segments;
|
||||||
|
return routeSegments[routeSegments.length - 1];
|
||||||
|
}
|
||||||
@@ -1,9 +1,4 @@
|
|||||||
import { useParams } from "next/navigation";
|
import schema from "@/app.schema.json";
|
||||||
|
|
||||||
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 = {
|
export type ResolvedRoute = {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -11,27 +6,59 @@ export type ResolvedRoute = {
|
|||||||
params: Record<string, string>;
|
params: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolveRoute = (segments: string[] = []): ResolvedRoute => {
|
const ROUTE_KEYS = Object.keys(schema as Record<string, unknown>);
|
||||||
const path = segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
||||||
|
|
||||||
for (const { key, prefix, param } of TEMPLATE_PATTERNS) {
|
/**
|
||||||
if (path.startsWith(prefix) && path.length > prefix.length) {
|
* Matches a concrete path's segments against a single express-style pattern
|
||||||
return { key, path, params: { [param]: path.slice(prefix.length) } };
|
* (e.g. `/products/:handle`). Returns the captured params, or null on mismatch.
|
||||||
|
*/
|
||||||
|
const matchPattern = (
|
||||||
|
pattern: string,
|
||||||
|
segments: string[],
|
||||||
|
): Record<string, string> | null => {
|
||||||
|
const patternSegments = pattern === "/" ? [] : pattern.slice(1).split("/");
|
||||||
|
if (patternSegments.length !== segments.length) return null;
|
||||||
|
|
||||||
|
const params: Record<string, string> = {};
|
||||||
|
for (let i = 0; i < patternSegments.length; i++) {
|
||||||
|
const part = patternSegments[i];
|
||||||
|
if (part.startsWith(":")) {
|
||||||
|
params[part.slice(1)] = segments[i];
|
||||||
|
} else if (part !== segments[i]) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return params;
|
||||||
return { key: path, path, params: {} };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the current route's `handle` param from the catch-all slug segments.
|
* Resolves catch-all slug segments to a route key defined in the schema,
|
||||||
* Use in client components rendered under `app/[[...slug]]` (and its editor
|
* supporting any express-style pattern (`/products/:handle`, `/blog/:slug`,
|
||||||
* counterpart) where the handle is no longer exposed as a direct route param.
|
* etc.). Static segments are preferred over dynamic ones when both match.
|
||||||
*/
|
*/
|
||||||
export const useRouteHandle = (): string | undefined => {
|
const resolveRoute = (segments: string[] = []): ResolvedRoute => {
|
||||||
const params = useParams();
|
// Editor routes live under `/editor/*`; the `editor` prefix is not part of
|
||||||
const segments = Array.isArray(params?.slug) ? (params.slug as string[]) : [];
|
// the schema route keys, so strip it before matching.
|
||||||
return resolveRoute(segments).params.handle;
|
const routeSegments =
|
||||||
|
segments[0] === "editor" ? segments.slice(1) : segments;
|
||||||
|
const path =
|
||||||
|
routeSegments.length === 0 ? "/" : `/${routeSegments.join("/")}`;
|
||||||
|
|
||||||
|
let best: ResolvedRoute | null = null;
|
||||||
|
let bestDynamicCount = Infinity;
|
||||||
|
|
||||||
|
for (const key of ROUTE_KEYS) {
|
||||||
|
const params = matchPattern(key, routeSegments);
|
||||||
|
if (!params) continue;
|
||||||
|
|
||||||
|
const dynamicCount = Object.keys(params).length;
|
||||||
|
if (dynamicCount < bestDynamicCount) {
|
||||||
|
best = { key, path, params };
|
||||||
|
bestDynamicCount = dynamicCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best ?? { key: path, path, params: {} };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default resolveRoute;
|
export default resolveRoute;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user