diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8f9df64 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Next.js: debug server-side", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev" + }, + {"name": "Launch Chrome against localhost", + "type": "pwa-chrome", + "request": "launch", + "url": "http://localhost:3000", + "webRoot": "${workspaceFolder}" + } + + ] +} \ No newline at end of file diff --git a/dynamic-route-healthcheck.sh b/dynamic-route-healthcheck.sh new file mode 100755 index 0000000..ee68765 --- /dev/null +++ b/dynamic-route-healthcheck.sh @@ -0,0 +1,107 @@ +#!/bin/zsh + +echo "π Running Next.js Dynamic Route Health Check..." +echo "" + +DYNAMIC_PAGES=$(find src/app -type f -path "*/\[*\]/page.js") + +RED=$(tput setaf 1) +YELLOW=$(tput setaf 3) +GREEN=$(tput setaf 2) +RESET=$(tput sgr0) + +check_pass() { + echo "${GREEN}β PASS${RESET} $1" +} + +check_warn() { + echo "${YELLOW}β WARN${RESET} $1" +} + +check_fail() { + echo "${RED}β FAIL${RESET} $1" +} + +for FILE in $DYNAMIC_PAGES; do + echo "==========================================" + echo "π Checking: $FILE" + + FOLDER=$(basename $(dirname "$FILE")) + PARAM_NAME=${FOLDER//[\[\]]/} + + CONTENT=$(cat "$FILE") + + echo "β‘ Route param is: ${PARAM_NAME}" + + # 1. Check for export default async + if echo "$CONTENT" | grep -q "export default async function"; then + check_pass "Has async default export" + else + check_fail "Missing 'export default async function'" + fi + + # 2. Check for await params + if echo "$CONTENT" | grep -q "await params"; then + check_pass "'await params' found" + else + check_fail "Missing 'await params' β required in Next.js 15+" + fi + + # 3. Check if extracted param name is correct + if echo "$CONTENT" | grep -q "const { $PARAM_NAME } = await params"; then + check_pass "Correct param extraction: { $PARAM_NAME }" + else + check_warn "Param '$PARAM_NAME' may not be extracted with 'await params'" + fi + + # 4. Check for leftover bad usage: params.paramName + if echo "$CONTENT" | grep -q "params\.$PARAM_NAME"; then + check_fail "Found old syntax 'params.$PARAM_NAME' β must be removed!" + else + check_pass "No old 'params.$PARAM_NAME' references" + fi + + # 5. Check that param is used somewhere + if echo "$CONTENT" | grep -q "$PARAM_NAME"; then + check_pass "Param is referenced in file" + else + check_warn "Param '$PARAM_NAME' isn't used β this may be incorrect" + fi + + # 6. Validate imports + IMPORT_ERRORS=0 + + while IFS= read -r line; do + if [[ "$line" == import* ]]; then + PATH=$(echo "$line" | sed -n 's/.*from "\(.*\)".*/\1/p') + if [[ "$PATH" == @/* ]]; then + # Attempt resolution + RESOLVED="src${PATH#@}" + RESOLVED="${RESOLVED%.js}.js" + + if [ ! -f "$RESOLVED" ] && [ ! -f "${RESOLVED%.js}.jsx" ] && [ ! -f "${RESOLVED%.js}.ts" ] && [ ! -f "${RESOLVED%.js}.tsx" ]; then + check_warn "Import may be broken: $line" + IMPORT_ERRORS=1 + fi + fi + fi + done <<< "$(grep "^import" "$FILE")" + + if [[ $IMPORT_ERRORS -eq 0 ]]; then + check_pass "All imports appear valid" + fi + + # 7. Check param folder structure + DIR=$(dirname "$FILE") + + if [ ! -d "$DIR" ]; then + check_fail "Missing directory: $DIR (route broken)" + else + check_pass "Route directory exists" + fi + + echo "" +done + +echo "==========================================" +echo "π Dynamic Route Health Check Complete!" diff --git a/fix-api-dynamic-routes.sh b/fix-api-dynamic-routes.sh new file mode 100755 index 0000000..c71f8c6 --- /dev/null +++ b/fix-api-dynamic-routes.sh @@ -0,0 +1,73 @@ +#!/bin/zsh + +echo "π Starting API Dynamic Route Autofix (Next.js 15+)..." +echo "" + +# Find all dynamic API routes like src/app/api/**/[something]/route.js +DYNAMIC_API=$(find src/app/api -type f -path "*/\[*\]/route.js") + +RED=$(tput setaf 1) +YELLOW=$(tput setaf 3) +GREEN=$(tput setaf 2) +RESET=$(tput sgr0) + +for FILE in $DYNAMIC_API; do + echo "==========================================" + echo "π Fixing API route: $FILE" + + # Extract folder name: [id], [modelId], [partId], etc. + FOLDER=$(basename $(dirname "$FILE")) + PARAM_NAME=${FOLDER//[\[\]]/} # remove brackets + + echo "β‘ Dynamic API param detected: ${GREEN}${PARAM_NAME}${RESET}" + + # Make backup + cp "$FILE" "$FILE.bak" + + CONTENT=$(cat "$FILE") + + ############################################################ + # 1. Fix function signature + ############################################################ + + if echo "$CONTENT" | grep -q "export async function GET(request, { params })"; then + echo " π§ Fixing signature..." + + sed -i '' "s/export async function GET(request, { params })/export async function GET(request, context) {\n const { $PARAM_NAME } = await context.params;\n/" "$FILE" + + elif ! echo "$CONTENT" | grep -q "await context.params"; then + echo " π§ Inserting param extraction at top of GET function..." + + sed -i '' "s/export async function GET([^)]*) {/export async function GET(request, context) {\n const { $PARAM_NAME } = await context.params;/" "$FILE" + else + echo " β Signature already patched" + fi + + ############################################################ + # 2. Fix incorrect usage of params.PARAM_NAME + ############################################################ + + if echo "$CONTENT" | grep -q "params.$PARAM_NAME"; then + echo " π§ Removing old params.$PARAM_NAME usage..." + sed -i '' "s/params\.$PARAM_NAME/$PARAM_NAME/g" "$FILE" + else + echo " β No old params.$PARAM_NAME found" + fi + + ############################################################ + # 3. Validate presence of correct variable usage + ############################################################ + + if grep -q "$PARAM_NAME" "$FILE"; then + echo " β Param $PARAM_NAME is being used correctly" + else + echo " ${YELLOW}β WARN:${RESET} Param '$PARAM_NAME' not found in file. You may need manual review." + fi + + ############################################################ + echo " ${GREEN}β API route successfully patched${RESET}" + echo "" +done + +echo "==========================================" +echo "π API Dynamic Route Autofix Complete!" diff --git a/fix-import-aliases.sh b/fix-import-aliases.sh new file mode 100755 index 0000000..d1a8b02 --- /dev/null +++ b/fix-import-aliases.sh @@ -0,0 +1,36 @@ +#!/bin/zsh + +echo "π§ Fixing incorrect '@/src/...' imports..." + +# Search for JS, JSX, TS, TSX files under src/ only +FILES=$(find src -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \)) + +for FILE in $FILES; do + echo "π Checking $FILE" + + # Fix @/src/lib β @/lib + if grep -q '@/src/lib' "$FILE"; then + echo " β Fixing '@/src/lib' β '@/lib'" + sed -i '' 's#@/src/lib#@/lib#g' "$FILE" + fi + + # Fix @/src/app β @/app + if grep -q '@/src/app' "$FILE"; then + echo " β Fixing '@/src/app' β '@/app'" + sed -i '' 's#@/src/app#@/app#g' "$FILE" + fi + + # Fix @/src/components β @/components + if grep -q '@/src/components' "$FILE"; then + echo " β Fixing '@/src/components' β '@/components'" + sed -i '' 's#@/src/components#@/components#g' "$FILE" + fi + + # Fix @/src/styles β @/styles + if grep -q '@/src/styles' "$FILE"; then + echo " β Fixing '@/src/styles' β '@/styles'" + sed -i '' 's#@/src/styles#@/styles#g' "$FILE" + fi +done + +echo "π Import alias fixes complete!" diff --git a/package-lock.json b/package-lock.json index 7c3d903..ba0b8ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,16 +7,22 @@ "": { "name": "rc-compat", "version": "0.1.0", + "license": "ISC", "dependencies": { + "fs": "^0.0.1-security", "next": "16.0.3", + "path": "^0.12.7", "react": "19.2.0", "react-dom": "19.2.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "autoprefixer": "^10.4.22", "eslint": "^9", "eslint-config-next": "16.0.3", - "tailwindcss": "^4" + "postcss": "^8.5.6", + "sass": "^1.94.2", + "tailwindcss": "^4.1.17" } }, "node_modules/@alloc/quick-lru": { @@ -1143,6 +1149,315 @@ "node": ">=12.4.0" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -2210,6 +2525,43 @@ "node": ">= 0.4" } }, + "node_modules/autoprefixer": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2404,6 +2756,21 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -3349,6 +3716,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3646,6 +4031,12 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "devOptional": true + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3671,6 +4062,11 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -4700,12 +5096,28 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4897,6 +5309,15 @@ "node": ">=6" } }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4975,6 +5396,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4984,6 +5411,14 @@ "node": ">= 0.8.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5049,6 +5484,19 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5214,6 +5662,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sass": { + "version": "1.94.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz", + "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==", + "devOptional": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -5955,6 +6423,14 @@ "punycode": "^2.1.0" } }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dependencies": { + "inherits": "2.0.3" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index ad27f28..a65c4ac 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,24 @@ "lint": "eslint" }, "dependencies": { + "fs": "^0.0.1-security", "next": "16.0.3", + "path": "^0.12.7", "react": "19.2.0", "react-dom": "19.2.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "autoprefixer": "^10.4.22", "eslint": "^9", "eslint-config-next": "16.0.3", - "tailwindcss": "^4" - } + "postcss": "^8.5.6", + "sass": "^1.94.2", + "tailwindcss": "^4.1.17" + }, + "description": "This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).", + "main": "tailwind.config.js", + "keywords": [], + "author": "", + "license": "ISC" } diff --git a/patch-dynamic-routes.sh b/patch-dynamic-routes.sh new file mode 100755 index 0000000..9b63c46 --- /dev/null +++ b/patch-dynamic-routes.sh @@ -0,0 +1,39 @@ +#!/bin/zsh + +echo "π Patching all dynamic Next.js route pages to use 'await params'..." + +# Find all dynamic folders: anything like /[paramName]/ +DYNAMIC_PAGES=$(find src/app -type f -path "*/\[*\]/page.js") + +for FILE in $DYNAMIC_PAGES; do + echo "π Patching: $FILE" + + # Extract folder name, e.g. [partId] + FOLDER=$(basename $(dirname "$FILE")) + + # Remove brackets -> partId + PARAM_NAME=${FOLDER//[\[\]]/} + + # Make backup + cp "$FILE" "$FILE.bak" + + # Replace any direct use of params.PARAM_NAME + # Ensure the file declares: const { paramName } = await params; + # Only insert if not already patched + if ! grep -q "await params" "$FILE"; then + echo " β Inserting param extraction: const { $PARAM_NAME } = await params;" + + # Insert param extraction after function signature + sed -i '' "s/export default async function \(.*\)({ params }) {/export default async function \1({ params }) {\n const { $PARAM_NAME } = await params;/" "$FILE" + else + echo " β Already patched." + fi + + echo " π§ Updating references to params.$PARAM_NAME..." + + # Replace params.PARAM_NAME β PARAM_NAME + sed -i '' "s/params\.$PARAM_NAME/$PARAM_NAME/g" "$FILE" + +done + +echo "π All dynamic route pages patched successfully!" diff --git a/patch-scss-imports.sh b/patch-scss-imports.sh new file mode 100755 index 0000000..b4f6ce5 --- /dev/null +++ b/patch-scss-imports.sh @@ -0,0 +1,42 @@ +#!/bin/zsh + +echo "π¨ Depth-aware SCSS variables import patcher starting..." + +VARIABLES_FILE="src/app/styles/variables.scss" + +if [ ! -f "$VARIABLES_FILE" ]; then + echo "β variables.scss not found at: $VARIABLES_FILE" + exit 1 +fi + +# Normalize absolute path for comparison +VARIABLES_PATH=$(realpath "$VARIABLES_FILE") + +# Find all SCSS files under src/app (excluding the variables file itself) +SCSS_FILES=$(find src/app -type f -name "*.scss" ! -path "*variables.scss") + +for FILE in $SCSS_FILES; do + echo "π Checking: $FILE" + + # Skip if already importing variables + if grep -q 'variables' "$FILE"; then + echo " β Already has variables import" + continue + fi + + # Compute relative path from current SCSS file directory to variables.scss + FILE_DIR=$(dirname "$FILE") + REL_PATH=$(realpath --relative-to="$FILE_DIR" "$VARIABLES_PATH") + + # Convert absolute to Sass import (remove leading ./ if present) + IMPORT_PATH="${REL_PATH#./}" + + echo " β Adding import: @import \"$IMPORT_PATH\";" + + # Prepend import to file + echo "@import \"$IMPORT_PATH\";" | cat - "$FILE" > "$FILE.tmp" + mv "$FILE.tmp" "$FILE" + +done + +echo "π Depth-aware SCSS import patching complete!" diff --git a/patch-tailwind-reference.sh b/patch-tailwind-reference.sh new file mode 100755 index 0000000..4148a8e --- /dev/null +++ b/patch-tailwind-reference.sh @@ -0,0 +1,22 @@ +#!/bin/zsh + +echo "π©Ή Auto-patching SCSS files with @reference \"tailwindcss\"..." + +SCSS_FILES=$(find src -type f -name "*.scss") + +for FILE in $SCSS_FILES; do + echo "Checking: $FILE" + + # Skip if already has @reference + if grep -q '@reference "tailwindcss"' "$FILE"; then + echo " β Already patched" + continue + fi + + echo " β Adding @reference to $FILE" + + # Prepend @reference at top of file + echo '@reference "tailwindcss";' | cat - "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE" +done + +echo "π All SCSS files have been patched for Tailwind v4!" diff --git a/scripts/migrateParts.js b/scripts/migrateParts.js new file mode 100644 index 0000000..74f0947 --- /dev/null +++ b/scripts/migrateParts.js @@ -0,0 +1,12 @@ +import fs from "fs"; +import path from "path"; + +const PARTS_PATH = path.join(process.cwd(), "src/lib/data/parts.js"); + +let content = fs.readFileSync(PARTS_PATH, "utf8"); + +content = content.replace(/fitsModels/g, "compatibleModels"); + +fs.writeFileSync(PARTS_PATH, content); + +console.log("β Migration complete: fitsModels β compatibleModels"); diff --git a/setup-api-layer.sh b/setup-api-layer.sh new file mode 100755 index 0000000..8873d3b --- /dev/null +++ b/setup-api-layer.sh @@ -0,0 +1,110 @@ +#!/bin/zsh + +echo "π‘ Creating API Layer for RC Compatibility App..." + +BASE="src/app/api" + +############################################# +# CREATE DIRECTORY STRUCTURE +############################################# + +mkdir -p "$BASE/models" +mkdir -p "$BASE/models/[id]" +mkdir -p "$BASE/parts" +mkdir -p "$BASE/parts/[id]" +mkdir -p "$BASE/compatibility/model/[id]" +mkdir -p "$BASE/compatibility/part/[id]" + +############################################# +# MODELS β /api/models +############################################# + +cat > "$BASE/models/route.js" << 'EOF' +import { MODELS } from "@/src/lib/data/models"; + +export async function GET() { + return Response.json(MODELS); +} +EOF + +############################################# +# MODEL BY ID β /api/models/[id] +############################################# + +cat > "$BASE/models/[id]/route.js" << 'EOF' +import { MODELS } from "@/src/lib/data/models"; + +export async function GET(request, { params }) { + const model = MODELS.find(m => m.id === params.id); + if (!model) { + return Response.json({ error: "Model not found" }, { status: 404 }); + } + return Response.json(model); +} +EOF + +############################################# +# PARTS β /api/parts +############################################# + +cat > "$BASE/parts/route.js" << 'EOF' +import { PARTS } from "@/src/lib/data/parts"; + +export async function GET() { + return Response.json(PARTS); +} +EOF + +############################################# +# PART BY ID β /api/parts/[id] +############################################# + +cat > "$BASE/parts/[id]/route.js" << 'EOF' +import { PARTS } from "@/src/lib/data/parts"; + +export async function GET(request, { params }) { + const part = PARTS.find(p => p.id === params.id); + if (!part) { + return Response.json({ error: "Part not found" }, { status: 404 }); + } + return Response.json(part); +} +EOF + +############################################# +# MODEL COMPATIBILITY β /api/compatibility/model/[id] +############################################# + +cat > "$BASE/compatibility/model/[id]/route.js" << 'EOF' +import { getCompatibleParts } from "@/src/lib/compatibility/engine"; + +export async function GET(request, { params }) { + const results = getCompatibleParts(params.id); + + if (!results.length) { + return Response.json({ error: "No compatible parts or model not found" }, { status: 404 }); + } + + return Response.json(results); +} +EOF + +############################################# +# PART COMPATIBILITY β /api/compatibility/part/[id] +############################################# + +cat > "$BASE/compatibility/part/[id]/route.js" << 'EOF' +import { getCrossCompatibleModels } from "@/src/lib/compatibility/engine"; + +export async function GET(request, { params }) { + const results = getCrossCompatibleModels(params.id); + + if (!results.length) { + return Response.json({ error: "No compatible models or part not found" }, { status: 404 }); + } + + return Response.json(results); +} +EOF + +echo "π API Layer created successfully!" diff --git a/setup-data-layer.sh b/setup-data-layer.sh new file mode 100755 index 0000000..6a862b0 --- /dev/null +++ b/setup-data-layer.sh @@ -0,0 +1,239 @@ +#!/bin/zsh + +echo "π¦ Creating RC Compatibility Data Layer + Engine..." + +BASE="src/lib" + +############################################# +# CREATE DIRECTORY STRUCTURE +############################################# + +mkdir -p "$BASE/data" +mkdir -p "$BASE/compatibility" +mkdir -p "$BASE/search" + +############################################# +# CREATE DATA FILES +############################################# + +# categories.js +cat > "$BASE/data/categories.js" << 'EOF' +export const CATEGORIES = [ + "drivetrain", + "suspension", + "chassis", + "shocks", + "electronics", + "steering", + "wheels", + "tires", + "body" +]; +EOF + +# models.js +cat > "$BASE/data/models.js" << 'EOF' +export const MODELS = [ + { + id: "tamiya-hotshot", + brand: "Tamiya", + name: "Hotshot", + scale: "1/10", + year: 1985, + categories: ["drivetrain", "suspension", "chassis", "shocks"], + generation: 1, + }, + { + id: "tamiya-supershot", + brand: "Tamiya", + name: "Super Shot", + scale: "1/10", + year: 1986, + categories: ["drivetrain", "suspension", "chassis", "shocks"], + generation: 1, + }, + { + id: "traxxas-rustler-4x4", + brand: "Traxxas", + name: "Rustler 4x4", + scale: "1/10", + year: 2018, + categories: ["drivetrain", "suspension", "electronics", "steering"], + generation: 2, + } +]; +EOF + +# parts.js +cat > "$BASE/data/parts.js" << 'EOF' +export const PARTS = [ + { + id: "TAM-198055", + name: "Hotshot Front Gearbox", + category: "drivetrain", + fitsModels: ["tamiya-hotshot", "tamiya-supershot"], + notes: "Original vintage fit.", + }, + { + id: "TAM-430512", + name: "Super Shot Steering Knuckle", + category: "steering", + fitsModels: ["tamiya-supershot"], + }, + { + id: "TRA-6755X", + name: "Traxxas Steel Driveshaft Upgrade", + category: "drivetrain", + fitsModels: ["traxxas-rustler-4x4"], + upgrade: true, + }, + { + id: "GEN-UNIV-55MM", + name: "Generic 55mm Shock Set", + category: "shocks", + fitsModels: ["tamiya-hotshot", "tamiya-supershot"], + universalFit: true, + } +]; +EOF + +############################################# +# COMPATIBILITY UTILITIES +############################################# + +cat > "$BASE/compatibility/utils.js" << 'EOF' +export function getModel(modelId, models) { + return models.find(m => m.id === modelId) || null; +} + +export function getPart(partId, parts) { + return parts.find(p => p.id === partId) || null; +} +EOF + +############################################# +# COMPATIBILITY SCORING SYSTEM +############################################# + +cat > "$BASE/compatibility/scoring.js" << 'EOF' +export function scoreCompatibility({ directFit, sameBrand, sameCategory, universal, generationMatch }) { + let score = 0; + + if (directFit) score += 100; + if (sameBrand) score += 20; + if (sameCategory) score += 15; + if (generationMatch) score += 10; + if (universal) score += 5; + + return Math.min(score, 100); +} +EOF + +############################################# +# MAIN COMPATIBILITY ENGINE +############################################# + +cat > "$BASE/compatibility/engine.js" << 'EOF' +import { MODELS } from "../data/models"; +import { PARTS } from "../data/parts"; +import { getModel, getPart } from "./utils"; +import { scoreCompatibility } from "./scoring"; + +// Check model-to-part compatibility +export function isCompatible(modelId, partId) { + const model = getModel(modelId, MODELS); + const part = getPart(partId, PARTS); + + if (!model || !part) { + return { compatible: false, reason: "Unknown model or part" }; + } + + const directFit = part.fitsModels.includes(modelId); + const sameBrand = model.brand && part.brand && part.brand === model.brand; + const sameCategory = model.categories.includes(part.category); + const universal = !!part.universalFit; + + const generationMatch = part.generation + ? part.generation === model.generation + : false; + + const score = scoreCompatibility({ + directFit, + sameBrand, + sameCategory, + universal, + generationMatch + }); + + return { + compatible: score > 0, + score, + details: { + directFit, + sameBrand, + sameCategory, + universal, + generationMatch + } + }; +} + +// Get all compatible parts for a model +export function getCompatibleParts(modelId) { + return PARTS + .map(part => ({ + part, + result: isCompatible(modelId, part.id) + })) + .filter(item => item.result.compatible) + .sort((a, b) => b.result.score - a.result.score); +} + +// Get models that fit a part +export function getCrossCompatibleModels(partId) { + return MODELS + .map(model => ({ + model, + result: isCompatible(model.id, partId) + })) + .filter(item => item.result.compatible) + .sort((a, b) => b.result.score - a.result.score); +} + +// Suggest alternative parts in same category +export function suggestAlternatives(partId) { + const part = getPart(partId, PARTS); + if (!part) return []; + + return PARTS.filter(p => + p.category === part.category && p.id !== partId + ); +} +EOF + +############################################# +# SEARCH ENGINE +############################################# + +cat > "$BASE/search/search.js" << 'EOF' +import { MODELS } from "../data/models"; +import { PARTS } from "../data/parts"; + +export function searchModels(query) { + const q = query.toLowerCase(); + return MODELS.filter(m => + m.name.toLowerCase().includes(q) || + m.brand.toLowerCase().includes(q) + ); +} + +export function searchParts(query) { + const q = query.toLowerCase(); + return PARTS.filter(p => + p.name.toLowerCase().includes(q) || + p.id.toLowerCase().includes(q) + ); +} +EOF + +echo "π Data layer + compatibility engine generated successfully!" diff --git a/setup-models-browser.sh b/setup-models-browser.sh new file mode 100755 index 0000000..b452894 --- /dev/null +++ b/setup-models-browser.sh @@ -0,0 +1,104 @@ +#!/bin/zsh + +echo "π Creating Models Browser Page..." + +PAGE="src/app/models" +COMP="src/app/components" +LIB="src/lib/ui" + +mkdir -p "$PAGE" + +############################################# +# Create models/page.js +############################################# + +cat > "$PAGE/page.js" << 'EOF' +"use client"; + +import { useEffect, useState } from "react"; +import ModelCard from "../components/ModelCard"; + +export default function ModelsPage() { + const [models, setModels] = useState([]); + const [filtered, setFiltered] = useState([]); + const [brand, setBrand] = useState("all"); + const [scale, setScale] = useState("all"); + + useEffect(() => { + async function load() { + const res = await fetch("/api/models", { cache: "no-store" }); + const data = await res.json(); + setModels(data); + setFiltered(data); + } + load(); + }, []); + + function filter() { + let result = [...models]; + + if (brand !== "all") { + result = result.filter(m => m.brand === brand); + } + if (scale !== "all") { + result = result.filter(m => m.scale === scale); + } + + setFiltered(result); + } + + const brands = [...new Set(models.map(m => m.brand))]; + const scales = [...new Set(models.map(m => m.scale))]; + + return ( +
{model.brand} β’ {model.scale}
+ID: {part.id}
+Model not found
; + + return ( +{model.brand} β’ {model.scale}
+ +No compatible parts.
+ )} +Part not found
; + + return ( +ID: {part.id}
+ +No compatible models.
+ )} +Model not found
; + + const parts = allParts + ? allParts.filter(p => p.part.category === params.categoryName) + : []; + + return ( +No compatible parts in this category.
+ )} +Model not found
; + + return ( +{model.brand} β’ {model.scale}
+No compatible parts found.
+ )} +Part not found
; + + return ( +Part ID: {part.id}
+ + {part.universalFit && ( + + Universal Fit + + )} + + {part.upgrade && ( + + Upgrade Part + + )} +No compatible models found.
+ )} +Model not found
; + + const parts = allParts.filter( + p => p.part.category === params.categoryName + ); + + return ( +No compatible parts in this category.
+ )} +Search for RC models, parts, and compare compatibility.
+Details and component categories for this RC model.
+List of compatible parts for this category.
+Specifications and compatibility info.
+Compare RC models and parts side-by-side.
+Save your RC models and parts here.
+{model.scale} Scale
+ID: {part.id}
+No parts found in this category.
; + } + + return ( ++ Explore compatible parts for{" "} + {modelId.replace(/-/g, " ")} +
++ Compatibility: {status ? "Compatible" : "Not Compatible"} +
+ ); +} diff --git a/src/app/components/ModelCard.js b/src/app/components/ModelCard.js new file mode 100644 index 0000000..2f4c41f --- /dev/null +++ b/src/app/components/ModelCard.js @@ -0,0 +1,8 @@ +export default function ModelCard({ model }) { + return ( +{model.brand} β’ {model.scale}
+Name: {model.name}
+Scale: {model.scale}
+{part.id}
+ + {/* Small Category Tag */} + + {part.category} + ++ Compatible with {part.fitsModels?.length || 0}{" "} + model(s) +
++ ID: {item.id} +
+ + ); +} diff --git a/src/app/components/SearchSkeletonCard.js b/src/app/components/SearchSkeletonCard.js new file mode 100644 index 0000000..7190a10 --- /dev/null +++ b/src/app/components/SearchSkeletonCard.js @@ -0,0 +1,9 @@ +export default function SearchSkeletonCard() { + return ( +Save your RC models and parts here.
+Model not found
; + + return ( ++ {model.brand} β’ {model.scale} +
+No compatible parts found.
+ )} +- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -
+No matching models or parts found.
+ )}Part not found
; + + return ( +Part ID: {part.id}
+ + {part.universalFit && ( + + Universal Fit + + )} + + {part.upgrade && ( + + Upgrade Part + + )} +{item.model.name}
+No compatible models found.
+ )} +