diff --git a/app/[locale]/(user)/usecases/[useCaseSlug]/Dashboards.tsx b/app/[locale]/(user)/usecases/[useCaseSlug]/Dashboards.tsx
index cac3851f..9f233e37 100644
--- a/app/[locale]/(user)/usecases/[useCaseSlug]/Dashboards.tsx
+++ b/app/[locale]/(user)/usecases/[useCaseSlug]/Dashboards.tsx
@@ -6,6 +6,7 @@ import { Text } from 'opub-ui';
import { GraphQL } from '@/lib/api';
import { getSafeEmbedUrl } from '@/lib/dashboardEmbed';
import { Loading } from '@/components/loading';
+import JusticeHubEmbed, { isJusticeHubEmbed } from './JusticeHubEmbed';
const DashboardsList = graphql(`
query usecaseDashboards($usecaseId: Int!) {
@@ -41,7 +42,10 @@ const Dashboards = () => {
...dashboard,
embedUrl: getSafeEmbedUrl(dashboard.link),
}))
- .filter((dashboard) => dashboard.embedUrl);
+ .filter(
+ (dashboard): dashboard is typeof dashboard & { embedUrl: string } =>
+ Boolean(dashboard.embedUrl)
+ );
if (!isValidId || (!isLoading && dashboards.length === 0)) {
return null;
@@ -65,18 +69,28 @@ const Dashboards = () => {
{dashboard.name}
-
-
+
+ {isJusticeHubEmbed(dashboard.embedUrl) ? (
+
+ ) : (
+
+
+
+ )}
{
+ try {
+ const { hostname } = new URL(src);
+ return hostname === 'justicehub.in' || hostname === 'www.justicehub.in';
+ } catch {
+ return false;
+ }
+};
+
+const JusticeHubEmbed = ({ src, title }: { src: string; title: string }) => {
+ const [attempt, setAttempt] = useState(0);
+ const [isReady, setIsReady] = useState(false);
+ const hasRetried = useRef(false);
+ const revealed = useRef(false);
+ const timers = useRef[]>([]);
+
+ const queue = (fn: () => void, delay: number) => {
+ timers.current.push(setTimeout(fn, delay));
+ };
+
+ const reveal = useCallback(() => {
+ if (revealed.current) return;
+ revealed.current = true;
+ setIsReady(true);
+ }, []);
+
+ useEffect(() => {
+ revealed.current = false;
+ hasRetried.current = false;
+
+ const fallback = setTimeout(reveal, FALLBACK_MS);
+ timers.current.push(fallback);
+
+ return () => {
+ timers.current.forEach(clearTimeout);
+ timers.current = [];
+ };
+ }, [src, reveal]);
+
+ const handleLoad = () => {
+ if (revealed.current) return;
+
+ if (!hasRetried.current) {
+ hasRetried.current = true;
+ // First response means a reload is coming. Cancel the give-up timer
+ // so it cannot drop the spinner and then wipe the frame.
+ timers.current.forEach(clearTimeout);
+ timers.current = [];
+ queue(() => {
+ if (revealed.current) return;
+ setAttempt((current) => current + 1);
+ }, RETRY_MS);
+ queue(reveal, FALLBACK_MS);
+ return;
+ }
+
+ queue(reveal, REVEAL_MS);
+ };
+
+ return (
+ <>
+
+
+
+
+ {!isReady ? (
+
+
+ Loading dashboard
+
+ ) : null}
+
+
+ >
+ );
+};
+
+export default JusticeHubEmbed;