Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[build]
command = "npm run build"
publish = "out"

[functions]
directory = "netlify/functions"
32 changes: 32 additions & 0 deletions netlify/functions/get-iframe.ts
Original file line number Diff line number Diff line change
@@ -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' },
});
};
2 changes: 1 addition & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
output: "export",
};

export default nextConfig;
14 changes: 12 additions & 2 deletions src/app/components/IframeFetcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,32 @@ interface IframeFetcherProps {
};
}

const iframeCache = new Map<string, string>();

const IframeFetcher = ({ org, project, body }: IframeFetcherProps) => {
const [iframeUrl, setIframeUrl] = useState<string | null>(null);
const [error, setError] = useState<string | null>(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) }),
});

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) {
Expand All @@ -41,7 +51,7 @@ const IframeFetcher = ({ org, project, body }: IframeFetcherProps) => {
};

fetchUrl();
}, [org, project, bodyString]);
}, [org, project, bodyString, cacheKey]);

if (error) return <p className="text-red-500">Error loading iframe: {error}</p>;
if (!iframeUrl) return <p>Loading...</p>;
Expand Down