diff --git a/apps/web/components/privacy-policy/privacy-policy.ts b/apps/web/components/privacy-policy/privacy-policy.ts index 53b3edae..111376ff 100644 --- a/apps/web/components/privacy-policy/privacy-policy.ts +++ b/apps/web/components/privacy-policy/privacy-policy.ts @@ -26,7 +26,7 @@ export const privacyPolicyContent = [ { id: 6, description: - 'You means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable. Company (referred to as either “the Company”, “We”, “Us” or “Our” in this Agreement) refers to harkirat.classx.co.in.', + 'You means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable. Company (referred to as either “the Company”, “We”, “Us” or “Our” in this Agreement) refers to 100xdevs.com.', }, { id: 7, @@ -36,7 +36,7 @@ export const privacyPolicyContent = [ { id: 8, description: - 'Website refers to harkirat.classx.co.in, accessible fromharkirat.classx.co.in Service refers to the Website. Country refers to: Uttar Pradesh, India Service Provider means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used. Third-party Social Media Service refers to any website or any social network website through which a User can log in or create an account to use the Service.', + 'Website refers to 100xdevs.com, accessible from 100xdevs.com. Service refers to the Website. Country refers to: Uttar Pradesh, India Service Provider means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used. Third-party Social Media Service refers to any website or any social network website through which a User can log in or create an account to use the Service.', }, { id: 9, @@ -219,4 +219,3 @@ export const privacyPolicyContent = [ 'You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.', }, ]; - \ No newline at end of file diff --git a/apps/web/lib/notion.ts b/apps/web/lib/notion.ts index 3d4decd4..a0c92d7e 100644 --- a/apps/web/lib/notion.ts +++ b/apps/web/lib/notion.ts @@ -18,6 +18,28 @@ function normalizeBlocks(block: any) { return normalizedBlock; } +const EMPTY_RECORD_MAP_KEYS = [ + "collection", + "collection_view", + "notion_user", + "collection_query", + "signed_urls", +] as const; + +function normalizeRecordMap(recordMap: any) { + const normalized = recordMap ?? {}; + normalized.block = normalizeBlocks(normalized.block); + for (const key of EMPTY_RECORD_MAP_KEYS) normalized[key] = normalized[key] ?? {}; + return normalized; +} + +function mergeRecordMaps(target: any, source: any) { + for (const [key, value] of Object.entries(source ?? {})) { + if (!value || typeof value !== "object" || Array.isArray(value)) continue; + target[key] = { ...(target[key] ?? {}), ...value }; + } +} + function collectContentBlockIds(recordMap: any): string[] { const blocks = recordMap?.block; if (!blocks) return []; @@ -47,12 +69,11 @@ function collectContentBlockIds(recordMap: any): string[] { // page on the endpoints notion-client normally uses (`loadPageChunk`, `syncRecordValues`, // `queryCollection`), which took down every track/problem page with a 500. // -// Empirically, `loadCachedPageChunkV2` is NOT blocked from the cluster and returns the -// full page recordMap in a single request, so we fetch through that endpoint directly -// (via the client's public `fetch`) instead of `getPage`. We also authenticate with -// NOTION_TOKEN_V2 (private-page access) and aggressively throttle + cache, because the -// block is IP-reputation based: bursts of requests re-trigger a broader Cloudflare block, -// so keeping request volume low is what keeps this endpoint working. +// Empirically, `loadCachedPageChunkV2` is NOT blocked from the cluster, so we fetch +// through that endpoint directly (via the client's public `fetch`) instead of `getPage`. +// Some toggle descendants need a second cached-chunk request rooted at the missing block. +// We also authenticate with NOTION_TOKEN_V2 (private-page access) and aggressively +// throttle + cache, because bursts can re-trigger a broader Cloudflare block. let notionSingleton: NotionAPI | null = null; export function getNotionClient(): NotionAPI { @@ -97,6 +118,7 @@ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); // problem of a track in parallel, so we cap concurrency and space requests out. const MAX_CONCURRENCY = 2; const MIN_GAP_MS = 250; +const MAX_MISSING_BLOCK_FETCHES = 50; let active = 0; let lastStart = 0; const waiters: Array<() => void> = []; @@ -141,25 +163,45 @@ async function loadPageViaCachedChunk(notion: NotionAPI, rawPageId: string): Pro gotOptions: getGotOptions(), }); - const recordMap = res?.recordMap ?? {}; - recordMap.block = normalizeBlocks(recordMap.block); - // react-notion-x expects these maps to exist even when empty. - recordMap.collection = recordMap.collection ?? {}; - recordMap.collection_view = recordMap.collection_view ?? {}; - recordMap.notion_user = recordMap.notion_user ?? {}; - recordMap.collection_query = recordMap.collection_query ?? {}; - recordMap.signed_urls = recordMap.signed_urls ?? {}; + const recordMap = normalizeRecordMap(res?.recordMap); if (!recordMap.block || Object.keys(recordMap.block).length === 0) { throw new Error(`Notion page not found "${pageId}"`); } - const missing = collectContentBlockIds(recordMap).filter((id) => !recordMap.block[id]); + // Notion sometimes excludes toggle children from the root cached chunk. Its regular + // syncRecordValues endpoint is blocked by Cloudflare from our cluster, but the same + // cached endpoint can load a missing block as a small rooted chunk. Resolve those + // descendants iteratively so react-notion-x receives the complete toggle tree. + const fetched = new Set(); + let missing = collectContentBlockIds(recordMap).filter((id) => !recordMap.block[id]); + while (missing.length && fetched.size < MAX_MISSING_BLOCK_FETCHES) { + const blockId = missing.find((id) => !fetched.has(id)); + if (!blockId) break; + fetched.add(blockId); + + if (fetched.size > 1) await sleep(MIN_GAP_MS); + try { + const childRes: any = await notion.fetch({ + endpoint: "loadCachedPageChunkV2", + body: { + pageId: blockId, + limit: 100, + cursor: { stack: [] }, + chunkNumber: 0, + verticalColumns: false, + }, + gotOptions: getGotOptions(), + }); + mergeRecordMaps(recordMap, normalizeRecordMap(childRes?.recordMap)); + } catch (err) { + console.warn(`[notion] ${pageId}: failed to load nested block ${blockId}: ${(err as Error)?.message}`); + } + missing = collectContentBlockIds(recordMap).filter((id) => !recordMap.block[id]); + } + if (missing.length) { - // loadCachedPageChunkV2 returns the full page tree in practice; if a handful of nested - // blocks are missing we render what we have rather than hitting the (blocked) - // syncRecordValues endpoint. - console.warn(`[notion] ${pageId}: ${missing.length} nested block(s) missing from cached chunk`); + console.warn(`[notion] ${pageId}: ${missing.length} nested block(s) still missing after cached chunk fallback`); } return recordMap; diff --git a/apps/web/screens/footer-cta.tsx b/apps/web/screens/footer-cta.tsx index dd80efe6..f558fa9e 100644 --- a/apps/web/screens/footer-cta.tsx +++ b/apps/web/screens/footer-cta.tsx @@ -1,20 +1,9 @@ "use client"; import { Button } from "@repo/ui"; -import { Download, Sparkles } from "lucide-react"; +import { Sparkles } from "lucide-react"; import Link from "next/link"; -import Mockup from "../public/Mockup.png"; -import { motion } from "framer-motion"; -import Image from "next/image"; const FooterCTA = () => { - const floatingAnimation = { - y: [0, -10, 0], - transition: { - duration: 3, - ease: "easeInOut", - repeat: Infinity, - }, - }; return (
@@ -30,13 +19,7 @@ const FooterCTA = () => {

- - - - +
- - - Mockup - - );