diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..9847c21 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,6 @@ +[build] + command = "npm run build" + publish = "out" + +[functions] + directory = "netlify/functions" diff --git a/netlify/functions/get-iframe.ts b/netlify/functions/get-iframe.ts new file mode 100644 index 0000000..3282d90 --- /dev/null +++ b/netlify/functions/get-iframe.ts @@ -0,0 +1,32 @@ +export default async (req: Request) => { + if (req.method !== 'POST') { + return new Response('Method not allowed', { status: 405 }); + } + + const { org, project, body } = await req.json(); + + const rillServiceToken = process.env.RILL_SERVICE_TOKEN!; + const url = `https://admin.rilldata.com/v1/organizations/${org}/projects/${project}/iframe`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${rillServiceToken}`, + }, + body: JSON.stringify(body), + }); + + const data = await response.json(); + + if (!response.ok) { + return new Response(JSON.stringify({ error: data.message || 'Failed to fetch iframe URL' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } + + return new Response(JSON.stringify({ iframeUrl: data.iframeSrc }), { + headers: { 'Content-Type': 'application/json' }, + }); +}; diff --git a/next.config.ts b/next.config.ts index e9ffa30..65c102b 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: "export", }; export default nextConfig; diff --git a/src/app/components/IframeFetcher.tsx b/src/app/components/IframeFetcher.tsx index 0b755a8..b090535 100644 --- a/src/app/components/IframeFetcher.tsx +++ b/src/app/components/IframeFetcher.tsx @@ -13,15 +13,24 @@ interface IframeFetcherProps { }; } +const iframeCache = new Map(); + const IframeFetcher = ({ org, project, body }: IframeFetcherProps) => { const [iframeUrl, setIframeUrl] = useState(null); const [error, setError] = useState(null); const bodyString = useMemo(() => JSON.stringify(body), [body]); + const cacheKey = `${org}:${project}:${bodyString}`; useEffect(() => { + const cached = iframeCache.get(cacheKey); + if (cached) { + setIframeUrl(cached); + return; + } + const fetchUrl = async () => { try { - const res = await fetch('/api/get-iframe', { + const res = await fetch('/.netlify/functions/get-iframe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ org, project, body: JSON.parse(bodyString) }), @@ -29,6 +38,7 @@ const IframeFetcher = ({ org, project, body }: IframeFetcherProps) => { const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Failed to fetch iframe'); + iframeCache.set(cacheKey, data.iframeUrl); setIframeUrl(data.iframeUrl); } catch (err) { if (err instanceof Error) { @@ -41,7 +51,7 @@ const IframeFetcher = ({ org, project, body }: IframeFetcherProps) => { }; fetchUrl(); - }, [org, project, bodyString]); + }, [org, project, bodyString, cacheKey]); if (error) return

Error loading iframe: {error}

; if (!iframeUrl) return

Loading...

;