Skip to content

Commit 776db2a

Browse files
committed
feat(next-web): visual editor support
1 parent 66fec39 commit 776db2a

40 files changed

Lines changed: 418 additions & 440 deletions

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070
"mcp__context7__query-docs",
7171
"Bash(cd /Users/adamhake/chimbo-park-conservancy/apps/next-web && npx vitest run 2>&1 | tail -50)",
7272
"Bash(pnpm --filter @chimborazo/next-web build 2>&1 | tail -80)",
73-
"Bash(cd /Users/adamhake/chimbo-park-conservancy/apps/next-web && pnpm test 2>&1)"
73+
"Bash(cd /Users/adamhake/chimbo-park-conservancy/apps/next-web && pnpm test 2>&1)",
74+
"Bash(ls -la \"/Users/adamhake/chimbo-park-conservancy/node_modules/.pnpm/next-sanity@12.1.1_@emotion+is-prop-valid@1.4.0_@sanity+client@7.16.0_@sanity+types@5.1_44bc6ab29d8209a74397e93bcb6d823d/node_modules/next-sanity/dist/\" | grep -E \"^d|\\\\.d\\\\.ts$|live|draft|visual\")"
7475
],
7576
"deny": [],
7677
"ask": []

apps/next-web/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# next-agents-md
2+
.next-docs/

apps/next-web/CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
3+
<!-- NEXT-AGENTS-MD-START -->[Next.js Docs Index]|root: ./.next-docs|STOP. What you remember about Next.js is WRONG for this project. Always search docs and read before any task.|If docs missing, run this command first: npx @next/codemod agents-md --output CLAUDE.md|01-app:{04-glossary.mdx}|01-app/01-getting-started:{01-installation.mdx,02-project-structure.mdx,03-layouts-and-pages.mdx,04-linking-and-navigating.mdx,05-server-and-client-components.mdx,06-cache-components.mdx,07-fetching-data.mdx,08-updating-data.mdx,09-caching-and-revalidating.mdx,10-error-handling.mdx,11-css.mdx,12-images.mdx,13-fonts.mdx,14-metadata-and-og-images.mdx,15-route-handlers.mdx,16-proxy.mdx,17-deploying.mdx,18-upgrading.mdx}|01-app/02-guides:{analytics.mdx,authentication.mdx,backend-for-frontend.mdx,caching.mdx,ci-build-caching.mdx,content-security-policy.mdx,css-in-js.mdx,custom-server.mdx,data-security.mdx,debugging.mdx,draft-mode.mdx,environment-variables.mdx,forms.mdx,incremental-static-regeneration.mdx,instrumentation.mdx,internationalization.mdx,json-ld.mdx,lazy-loading.mdx,local-development.mdx,mcp.mdx,mdx.mdx,memory-usage.mdx,multi-tenant.mdx,multi-zones.mdx,open-telemetry.mdx,package-bundling.mdx,prefetching.mdx,production-checklist.mdx,progressive-web-apps.mdx,public-static-pages.mdx,redirecting.mdx,sass.mdx,scripts.mdx,self-hosting.mdx,single-page-applications.mdx,static-exports.mdx,tailwind-v3-css.mdx,third-party-libraries.mdx,videos.mdx}|01-app/02-guides/migrating:{app-router-migration.mdx,from-create-react-app.mdx,from-vite.mdx}|01-app/02-guides/testing:{cypress.mdx,jest.mdx,playwright.mdx,vitest.mdx}|01-app/02-guides/upgrading:{codemods.mdx,version-14.mdx,version-15.mdx,version-16.mdx}|01-app/03-api-reference:{07-edge.mdx,08-turbopack.mdx}|01-app/03-api-reference/01-directives:{use-cache-private.mdx,use-cache-remote.mdx,use-cache.mdx,use-client.mdx,use-server.mdx}|01-app/03-api-reference/02-components:{font.mdx,form.mdx,image.mdx,link.mdx,script.mdx}|01-app/03-api-reference/03-file-conventions/01-metadata:{app-icons.mdx,manifest.mdx,opengraph-image.mdx,robots.mdx,sitemap.mdx}|01-app/03-api-reference/03-file-conventions:{default.mdx,dynamic-routes.mdx,error.mdx,forbidden.mdx,instrumentation-client.mdx,instrumentation.mdx,intercepting-routes.mdx,layout.mdx,loading.mdx,mdx-components.mdx,not-found.mdx,page.mdx,parallel-routes.mdx,proxy.mdx,public-folder.mdx,route-groups.mdx,route-segment-config.mdx,route.mdx,src-folder.mdx,template.mdx,unauthorized.mdx}|01-app/03-api-reference/04-functions:{after.mdx,cacheLife.mdx,cacheTag.mdx,connection.mdx,cookies.mdx,draft-mode.mdx,fetch.mdx,forbidden.mdx,generate-image-metadata.mdx,generate-metadata.mdx,generate-sitemaps.mdx,generate-static-params.mdx,generate-viewport.mdx,headers.mdx,image-response.mdx,next-request.mdx,next-response.mdx,not-found.mdx,permanentRedirect.mdx,redirect.mdx,refresh.mdx,revalidatePath.mdx,revalidateTag.mdx,unauthorized.mdx,unstable_cache.mdx,unstable_noStore.mdx,unstable_rethrow.mdx,updateTag.mdx,use-link-status.mdx,use-params.mdx,use-pathname.mdx,use-report-web-vitals.mdx,use-router.mdx,use-search-params.mdx,use-selected-layout-segment.mdx,use-selected-layout-segments.mdx,userAgent.mdx}|01-app/03-api-reference/05-config/01-next-config-js:{adapterPath.mdx,allowedDevOrigins.mdx,appDir.mdx,assetPrefix.mdx,authInterrupts.mdx,basePath.mdx,browserDebugInfoInTerminal.mdx,cacheComponents.mdx,cacheHandlers.mdx,cacheLife.mdx,compress.mdx,crossOrigin.mdx,cssChunking.mdx,devIndicators.mdx,distDir.mdx,env.mdx,expireTime.mdx,exportPathMap.mdx,generateBuildId.mdx,generateEtags.mdx,headers.mdx,htmlLimitedBots.mdx,httpAgentOptions.mdx,images.mdx,incrementalCacheHandlerPath.mdx,inlineCss.mdx,isolatedDevBuild.mdx,logging.mdx,mdxRs.mdx,onDemandEntries.mdx,optimizePackageImports.mdx,output.mdx,pageExtensions.mdx,poweredByHeader.mdx,productionBrowserSourceMaps.mdx,proxyClientMaxBodySize.mdx,reactCompiler.mdx,reactMaxHeadersLength.mdx,reactStrictMode.mdx,redirects.mdx,rewrites.mdx,sassOptions.mdx,serverActions.mdx,serverComponentsHmrCache.mdx,serverExternalPackages.mdx,staleTimes.mdx,staticGeneration.mdx,taint.mdx,trailingSlash.mdx,transpilePackages.mdx,turbopack.mdx,turbopackFileSystemCache.mdx,typedRoutes.mdx,typescript.mdx,urlImports.mdx,useLightningcss.mdx,viewTransition.mdx,webVitalsAttribution.mdx,webpack.mdx}|01-app/03-api-reference/05-config:{02-typescript.mdx,03-eslint.mdx}|01-app/03-api-reference/06-cli:{create-next-app.mdx,next.mdx}|02-pages/01-getting-started:{01-installation.mdx,02-project-structure.mdx,04-images.mdx,05-fonts.mdx,06-css.mdx,11-deploying.mdx}|02-pages/02-guides:{analytics.mdx,authentication.mdx,babel.mdx,ci-build-caching.mdx,content-security-policy.mdx,css-in-js.mdx,custom-server.mdx,debugging.mdx,draft-mode.mdx,environment-variables.mdx,forms.mdx,incremental-static-regeneration.mdx,instrumentation.mdx,internationalization.mdx,lazy-loading.mdx,mdx.mdx,multi-zones.mdx,open-telemetry.mdx,package-bundling.mdx,post-css.mdx,preview-mode.mdx,production-checklist.mdx,redirecting.mdx,sass.mdx,scripts.mdx,self-hosting.mdx,static-exports.mdx,tailwind-v3-css.mdx,third-party-libraries.mdx}|02-pages/02-guides/migrating:{app-router-migration.mdx,from-create-react-app.mdx,from-vite.mdx}|02-pages/02-guides/testing:{cypress.mdx,jest.mdx,playwright.mdx,vitest.mdx}|02-pages/02-guides/upgrading:{codemods.mdx,version-10.mdx,version-11.mdx,version-12.mdx,version-13.mdx,version-14.mdx,version-9.mdx}|02-pages/03-building-your-application/01-routing:{01-pages-and-layouts.mdx,02-dynamic-routes.mdx,03-linking-and-navigating.mdx,05-custom-app.mdx,06-custom-document.mdx,07-api-routes.mdx,08-custom-error.mdx}|02-pages/03-building-your-application/02-rendering:{01-server-side-rendering.mdx,02-static-site-generation.mdx,04-automatic-static-optimization.mdx,05-client-side-rendering.mdx}|02-pages/03-building-your-application/03-data-fetching:{01-get-static-props.mdx,02-get-static-paths.mdx,03-forms-and-mutations.mdx,03-get-server-side-props.mdx,05-client-side.mdx}|02-pages/03-building-your-application/06-configuring:{12-error-handling.mdx}|02-pages/04-api-reference:{06-edge.mdx,08-turbopack.mdx}|02-pages/04-api-reference/01-components:{font.mdx,form.mdx,head.mdx,image-legacy.mdx,image.mdx,link.mdx,script.mdx}|02-pages/04-api-reference/02-file-conventions:{instrumentation.mdx,proxy.mdx,public-folder.mdx,src-folder.mdx}|02-pages/04-api-reference/03-functions:{get-initial-props.mdx,get-server-side-props.mdx,get-static-paths.mdx,get-static-props.mdx,next-request.mdx,next-response.mdx,use-params.mdx,use-report-web-vitals.mdx,use-router.mdx,use-search-params.mdx,userAgent.mdx}|02-pages/04-api-reference/04-config/01-next-config-js:{adapterPath.mdx,allowedDevOrigins.mdx,assetPrefix.mdx,basePath.mdx,bundlePagesRouterDependencies.mdx,compress.mdx,crossOrigin.mdx,devIndicators.mdx,distDir.mdx,env.mdx,exportPathMap.mdx,generateBuildId.mdx,generateEtags.mdx,headers.mdx,httpAgentOptions.mdx,images.mdx,isolatedDevBuild.mdx,onDemandEntries.mdx,optimizePackageImports.mdx,output.mdx,pageExtensions.mdx,poweredByHeader.mdx,productionBrowserSourceMaps.mdx,proxyClientMaxBodySize.mdx,reactStrictMode.mdx,redirects.mdx,rewrites.mdx,serverExternalPackages.mdx,trailingSlash.mdx,transpilePackages.mdx,turbopack.mdx,typescript.mdx,urlImports.mdx,useLightningcss.mdx,webVitalsAttribution.mdx,webpack.mdx}|02-pages/04-api-reference/04-config:{01-typescript.mdx,02-eslint.mdx}|02-pages/04-api-reference/05-cli:{create-next-app.mdx,next.mdx}|03-architecture:{accessibility.mdx,fast-refresh.mdx,nextjs-compiler.mdx,supported-browsers.mdx}|04-community:{01-contribution-guide.mdx,02-rspack.mdx}<!-- NEXT-AGENTS-MD-END -->

apps/next-web/eslint.config.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import js from "@eslint/js";
2+
import tseslint from "typescript-eslint";
3+
import reactHooks from "eslint-plugin-react-hooks";
4+
5+
export default tseslint.config(
6+
js.configs.recommended,
7+
...tseslint.configs.recommended,
8+
{
9+
plugins: {
10+
"react-hooks": reactHooks,
11+
},
12+
rules: {
13+
...reactHooks.configs.recommended.rules,
14+
"react-hooks/set-state-in-effect": "error",
15+
"@typescript-eslint/no-unused-vars": [
16+
"warn",
17+
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
18+
],
19+
"@typescript-eslint/no-explicit-any": "warn",
20+
},
21+
},
22+
{
23+
ignores: [".next/", "node_modules/", "dist/"],
24+
},
25+
);

apps/next-web/next.config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import type { NextConfig } from "next";
22

33
const nextConfig: NextConfig = {
4+
cacheLife: {
5+
sanity: {
6+
stale: 60,
7+
revalidate: 1800,
8+
expire: 7776000, // 90 days — Sanity Live handles on-demand revalidation
9+
},
10+
},
411
images: {
512
remotePatterns: [
613
{

apps/next-web/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"dev": "next dev --turbopack -p 3001",
77
"build": "next build",
88
"start": "next start",
9-
"lint": "next lint",
9+
"lint": "eslint .",
1010
"type-check": "tsc --noEmit",
1111
"test": "vitest run"
1212
},
@@ -43,13 +43,17 @@
4343
"zod": "^4.3.6"
4444
},
4545
"devDependencies": {
46+
"@eslint/js": "catalog:",
4647
"@netlify/plugin-nextjs": "^5.12.0",
4748
"@testing-library/jest-dom": "^6.9.1",
4849
"@testing-library/react": "^16.3.2",
4950
"@types/node": "^25.3.1",
5051
"@types/react": "catalog:",
5152
"@types/react-dom": "^19.2.3",
53+
"eslint": "catalog:",
54+
"eslint-plugin-react-hooks": "^7.0.1",
5255
"typescript": "catalog:",
56+
"typescript-eslint": "catalog:",
5357
"vitest": "^4.0.18"
5458
}
5559
}

apps/next-web/src/app/about/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ export const metadata: Metadata = {
2525
};
2626

2727
export default async function AboutPage() {
28-
const pageData = await sanityFetch<SanityAboutPage | null>({
28+
const { data: pageData } = (await sanityFetch({
2929
query: getAboutPageQuery,
3030
tags: [CACHE_TAGS.ABOUT],
31-
});
31+
})) as { data: SanityAboutPage | null };
3232

3333
const heroData = pageData?.pageHero?.image
3434
? {

apps/next-web/src/app/amenities/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ export const metadata: Metadata = {
6464
};
6565

6666
export default async function AmenitiesPage() {
67-
const amenitiesPageData = await sanityFetch<SanityAmenitiesPage | null>({
67+
const { data: amenitiesPageData } = (await sanityFetch({
6868
query: getAmenitiesPageQuery,
6969
tags: [CACHE_TAGS.AMENITIES],
70-
});
70+
})) as { data: SanityAmenitiesPage | null };
7171

7272
// Prepare hero data from Sanity or use defaults
7373
const heroData = amenitiesPageData?.pageHero?.image
Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,9 @@
1+
import { defineEnableDraftMode } from "next-sanity/draft-mode";
2+
import { sanityClient } from "@/lib/sanity";
13
import { env } from "@/env";
2-
import { getSafeRedirectPath } from "@/lib/safe-redirect";
3-
import { sanityPreviewClient } from "@/lib/sanity";
4-
import { validatePreviewUrl } from "@sanity/preview-url-secret";
5-
import { draftMode } from "next/headers";
6-
import { redirect } from "next/navigation";
74

8-
export async function GET(request: Request) {
9-
const url = new URL(request.url);
10-
11-
console.log("[Draft Mode] Enable request received:", url.pathname + url.search);
12-
13-
if (!env.SANITY_API_TOKEN) {
14-
console.error("[Draft Mode] SANITY_API_TOKEN is not set");
15-
return new Response("Server misconfiguration: missing API token", { status: 500 });
16-
}
17-
18-
let isValid: boolean;
19-
let redirectTo: string;
20-
try {
21-
const result = await validatePreviewUrl(sanityPreviewClient(), url.toString());
22-
isValid = result.isValid;
23-
redirectTo = result.redirectTo ?? "/";
24-
} catch (error) {
25-
console.error("[Draft Mode] validatePreviewUrl threw:", error);
26-
return new Response("Failed to validate preview secret", { status: 500 });
27-
}
28-
29-
if (!isValid) {
30-
console.warn("[Draft Mode] Invalid secret – returning 401");
31-
return new Response("Invalid secret", { status: 401 });
32-
}
33-
34-
console.log("[Draft Mode] Secret valid, enabling preview. Redirecting to:", redirectTo);
35-
36-
const dm = await draftMode();
37-
dm.enable();
38-
39-
const safeRedirect = getSafeRedirectPath(redirectTo);
40-
redirect(safeRedirect);
41-
}
5+
export const { GET } = defineEnableDraftMode({
6+
client: sanityClient.withConfig({
7+
token: env.SANITY_API_TOKEN,
8+
}),
9+
});
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import { NextResponse } from "next/server";
22
import type { NextRequest } from "next/server";
3-
import { getSanityClient } from "@/lib/sanity";
3+
import { sanityClient } from "@/lib/sanity";
44
import { paginatedMediaImagesQuery } from "@chimborazo/sanity-config/queries";
55

66
export async function GET(request: NextRequest) {
77
const { searchParams } = request.nextUrl;
88
const start = parseInt(searchParams.get("start") || "0", 10);
99
const end = parseInt(searchParams.get("end") || "9", 10);
1010

11-
const client = getSanityClient();
12-
const images = await client.fetch(paginatedMediaImagesQuery, { start, end });
11+
const images = await sanityClient.fetch(paginatedMediaImagesQuery, { start, end });
1312

1413
return NextResponse.json(images);
1514
}

0 commit comments

Comments
 (0)