Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
226 lines
No EOL
11 KiB
Text
226 lines
No EOL
11 KiB
Text
class UrlNode {
|
|
insert(urlPath) {
|
|
this._insert(urlPath.split('/').filter(Boolean), [], false);
|
|
}
|
|
smoosh() {
|
|
return this._smoosh();
|
|
}
|
|
_smoosh(prefix = '/') {
|
|
const childrenPaths = [
|
|
...this.children.keys()
|
|
].sort();
|
|
if (this.slugName !== null) {
|
|
childrenPaths.splice(childrenPaths.indexOf('[]'), 1);
|
|
}
|
|
if (this.restSlugName !== null) {
|
|
childrenPaths.splice(childrenPaths.indexOf('[...]'), 1);
|
|
}
|
|
if (this.optionalRestSlugName !== null) {
|
|
childrenPaths.splice(childrenPaths.indexOf('[[...]]'), 1);
|
|
}
|
|
const routes = childrenPaths.map((c)=>this.children.get(c)._smoosh(`${prefix}${c}/`)).reduce((prev, curr)=>[
|
|
...prev,
|
|
...curr
|
|
], []);
|
|
if (this.slugName !== null) {
|
|
routes.push(...this.children.get('[]')._smoosh(`${prefix}[${this.slugName}]/`));
|
|
}
|
|
if (!this.placeholder) {
|
|
const r = prefix === '/' ? '/' : prefix.slice(0, -1);
|
|
if (this.optionalRestSlugName != null) {
|
|
throw Object.defineProperty(new Error(`You cannot define a route with the same specificity as a optional catch-all route ("${r}" and "${r}[[...${this.optionalRestSlugName}]]").`), "__NEXT_ERROR_CODE", {
|
|
value: "E458",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
routes.unshift(r);
|
|
}
|
|
if (this.restSlugName !== null) {
|
|
routes.push(...this.children.get('[...]')._smoosh(`${prefix}[...${this.restSlugName}]/`));
|
|
}
|
|
if (this.optionalRestSlugName !== null) {
|
|
routes.push(...this.children.get('[[...]]')._smoosh(`${prefix}[[...${this.optionalRestSlugName}]]/`));
|
|
}
|
|
return routes;
|
|
}
|
|
_insert(urlPaths, slugNames, isCatchAll) {
|
|
if (urlPaths.length === 0) {
|
|
this.placeholder = false;
|
|
return;
|
|
}
|
|
if (isCatchAll) {
|
|
throw Object.defineProperty(new Error(`Catch-all must be the last part of the URL.`), "__NEXT_ERROR_CODE", {
|
|
value: "E392",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
// The next segment in the urlPaths list
|
|
let nextSegment = urlPaths[0];
|
|
// Check if the segment matches `[something]`
|
|
if (nextSegment.startsWith('[') && nextSegment.endsWith(']')) {
|
|
// Strip `[` and `]`, leaving only `something`
|
|
let segmentName = nextSegment.slice(1, -1);
|
|
let isOptional = false;
|
|
if (segmentName.startsWith('[') && segmentName.endsWith(']')) {
|
|
// Strip optional `[` and `]`, leaving only `something`
|
|
segmentName = segmentName.slice(1, -1);
|
|
isOptional = true;
|
|
}
|
|
if (segmentName.startsWith('…')) {
|
|
throw Object.defineProperty(new Error(`Detected a three-dot character ('…') at ('${segmentName}'). Did you mean ('...')?`), "__NEXT_ERROR_CODE", {
|
|
value: "E147",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
if (segmentName.startsWith('...')) {
|
|
// Strip `...`, leaving only `something`
|
|
segmentName = segmentName.substring(3);
|
|
isCatchAll = true;
|
|
}
|
|
if (segmentName.startsWith('[') || segmentName.endsWith(']')) {
|
|
throw Object.defineProperty(new Error(`Segment names may not start or end with extra brackets ('${segmentName}').`), "__NEXT_ERROR_CODE", {
|
|
value: "E421",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
if (segmentName.startsWith('.')) {
|
|
throw Object.defineProperty(new Error(`Segment names may not start with erroneous periods ('${segmentName}').`), "__NEXT_ERROR_CODE", {
|
|
value: "E288",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
function handleSlug(previousSlug, nextSlug) {
|
|
if (previousSlug !== null) {
|
|
// If the specific segment already has a slug but the slug is not `something`
|
|
// This prevents collisions like:
|
|
// pages/[post]/index.js
|
|
// pages/[id]/index.js
|
|
// Because currently multiple dynamic params on the same segment level are not supported
|
|
if (previousSlug !== nextSlug) {
|
|
// TODO: This error seems to be confusing for users, needs an error link, the description can be based on above comment.
|
|
throw Object.defineProperty(new Error(`You cannot use different slug names for the same dynamic path ('${previousSlug}' !== '${nextSlug}').`), "__NEXT_ERROR_CODE", {
|
|
value: "E337",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
}
|
|
slugNames.forEach((slug)=>{
|
|
if (slug === nextSlug) {
|
|
throw Object.defineProperty(new Error(`You cannot have the same slug name "${nextSlug}" repeat within a single dynamic path`), "__NEXT_ERROR_CODE", {
|
|
value: "E247",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
if (slug.replace(/\W/g, '') === nextSegment.replace(/\W/g, '')) {
|
|
throw Object.defineProperty(new Error(`You cannot have the slug names "${slug}" and "${nextSlug}" differ only by non-word symbols within a single dynamic path`), "__NEXT_ERROR_CODE", {
|
|
value: "E499",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
});
|
|
slugNames.push(nextSlug);
|
|
}
|
|
if (isCatchAll) {
|
|
if (isOptional) {
|
|
if (this.restSlugName != null) {
|
|
throw Object.defineProperty(new Error(`You cannot use both an required and optional catch-all route at the same level ("[...${this.restSlugName}]" and "${urlPaths[0]}" ).`), "__NEXT_ERROR_CODE", {
|
|
value: "E299",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
handleSlug(this.optionalRestSlugName, segmentName);
|
|
// slugName is kept as it can only be one particular slugName
|
|
this.optionalRestSlugName = segmentName;
|
|
// nextSegment is overwritten to [[...]] so that it can later be sorted specifically
|
|
nextSegment = '[[...]]';
|
|
} else {
|
|
if (this.optionalRestSlugName != null) {
|
|
throw Object.defineProperty(new Error(`You cannot use both an optional and required catch-all route at the same level ("[[...${this.optionalRestSlugName}]]" and "${urlPaths[0]}").`), "__NEXT_ERROR_CODE", {
|
|
value: "E300",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
handleSlug(this.restSlugName, segmentName);
|
|
// slugName is kept as it can only be one particular slugName
|
|
this.restSlugName = segmentName;
|
|
// nextSegment is overwritten to [...] so that it can later be sorted specifically
|
|
nextSegment = '[...]';
|
|
}
|
|
} else {
|
|
if (isOptional) {
|
|
throw Object.defineProperty(new Error(`Optional route parameters are not yet supported ("${urlPaths[0]}").`), "__NEXT_ERROR_CODE", {
|
|
value: "E435",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
handleSlug(this.slugName, segmentName);
|
|
// slugName is kept as it can only be one particular slugName
|
|
this.slugName = segmentName;
|
|
// nextSegment is overwritten to [] so that it can later be sorted specifically
|
|
nextSegment = '[]';
|
|
}
|
|
}
|
|
// If this UrlNode doesn't have the nextSegment yet we create a new child UrlNode
|
|
if (!this.children.has(nextSegment)) {
|
|
this.children.set(nextSegment, new UrlNode());
|
|
}
|
|
this.children.get(nextSegment)._insert(urlPaths.slice(1), slugNames, isCatchAll);
|
|
}
|
|
constructor(){
|
|
this.placeholder = true;
|
|
this.children = new Map();
|
|
this.slugName = null;
|
|
this.restSlugName = null;
|
|
this.optionalRestSlugName = null;
|
|
}
|
|
}
|
|
/**
|
|
* @deprecated Use `sortSortableRoutes` or `sortPages` instead.
|
|
*/ export function getSortedRoutes(normalizedPages) {
|
|
// First the UrlNode is created, and every UrlNode can have only 1 dynamic segment
|
|
// Eg you can't have pages/[post]/abc.js and pages/[hello]/something-else.js
|
|
// Only 1 dynamic segment per nesting level
|
|
// So in the case that is test/integration/dynamic-routing it'll be this:
|
|
// pages/[post]/comments.js
|
|
// pages/blog/[post]/comment/[id].js
|
|
// Both are fine because `pages/[post]` and `pages/blog` are on the same level
|
|
// So in this case `UrlNode` created here has `this.slugName === 'post'`
|
|
// And since your PR passed through `slugName` as an array basically it'd including it in too many possibilities
|
|
// Instead what has to be passed through is the upwards path's dynamic names
|
|
const root = new UrlNode();
|
|
// Here the `root` gets injected multiple paths, and insert will break them up into sublevels
|
|
normalizedPages.forEach((pagePath)=>root.insert(pagePath));
|
|
// Smoosh will then sort those sublevels up to the point where you get the correct route definition priority
|
|
return root.smoosh();
|
|
}
|
|
/**
|
|
* @deprecated Use `sortSortableRouteObjects` or `sortPageObjects` instead.
|
|
*/ export function getSortedRouteObjects(objects, getter) {
|
|
// We're assuming here that all the pathnames are unique, that way we can
|
|
// sort the list and use the index as the key.
|
|
const indexes = {};
|
|
const pathnames = [];
|
|
for(let i = 0; i < objects.length; i++){
|
|
const pathname = getter(objects[i]);
|
|
indexes[pathname] = i;
|
|
pathnames[i] = pathname;
|
|
}
|
|
// Sort the pathnames.
|
|
const sorted = getSortedRoutes(pathnames);
|
|
// Map the sorted pathnames back to the original objects using the new sorted
|
|
// index.
|
|
return sorted.map((pathname)=>objects[indexes[pathname]]);
|
|
}
|
|
|
|
//# sourceMappingURL=sorted-routes.js.map |