diff --git a/hosting/src/app/[site]/layout.tsx b/hosting/src/app/[site]/layout.tsx index 812c9f01..9aea231f 100644 --- a/hosting/src/app/[site]/layout.tsx +++ b/hosting/src/app/[site]/layout.tsx @@ -6,7 +6,6 @@ import "@/css/style.css"; import {useTanamSite} from "@/hooks/useTanamSite"; import "flatpickr/dist/flatpickr.min.css"; import "jsvectormap/dist/css/jsvectormap.css"; -import {usePathname, useRouter} from "next/navigation"; import React, {useEffect, useState} from "react"; interface RootLayoutProps { @@ -15,24 +14,42 @@ interface RootLayoutProps { const RootLayout: React.FC = ({children}) => { const [loading, setLoading] = useState(true); - const router = useRouter(); - const pathname = usePathname() ?? ""; - const site = pathname.split("/")[1]; - // TODO(Dennis): Fix cases if URL parameter is not a valid site ID - // See: https://github.com/oddbit/tanam/issues/347 - const {getSite} = useTanamSite(site ?? "foo"); + const [errorMessage, setError] = useState(""); + const {siteData, error} = useTanamSite(); useEffect(() => { - getSite().then((data) => { - document.title = data?.title ?? "Tanam"; + if (siteData || error) { setLoading(false); - }); - }, [getSite, site, router]); + } + + if (error) { + // TODO(Dennis): Check error type and set appropriate error message + setError(error); + } + + if (siteData) { + document.title = siteData.title ?? "Tanam"; + } else if (error) { + document.title = "Error"; + } + }, [siteData, error]); + + /** + * Abstraction to simplify the rendering of the site content after loading + * @return {React.ReactNode} The site content to render + */ + function getSiteContent(): React.ReactNode { + if (errorMessage) { + return
{errorMessage}
; + } else { + return children; + } + } return ( -
{loading ? : children}
+
{loading ? : getSiteContent()}
); diff --git a/hosting/src/hooks/useTanamSite.tsx b/hosting/src/hooks/useTanamSite.tsx index 059a907c..e4fcc3a7 100644 --- a/hosting/src/hooks/useTanamSite.tsx +++ b/hosting/src/hooks/useTanamSite.tsx @@ -1,34 +1,43 @@ -import {firestore} from "@/firebase"; // this is from you export an initialize the app +import {useState, useEffect} from "react"; +import {firestore} from "@/firebase"; import {doc, getDoc} from "firebase/firestore"; import {TanamSite} from "@/models/tanamSite"; +import {useParams} from "next/navigation"; -/** - * Hook for Tanam site - * - * @param {stirng} site ID of the site - * @return {Object} Tanam site hooks - */ -export function useTanamSite(site: string) { - /** - * Get Tanam site data - * - * @return {Promise} Tanam site data - */ - async function getSite(): Promise { - const docRef = doc(firestore, "tanam", site); - const snap = await getDoc(docRef); +export function useTanamSite() { + const {site} = useParams<{site: string}>() ?? {site: null}; + const [siteData, setSiteData] = useState(null); + const [error, setError] = useState(null); - if (!snap.exists()) { - return null; + useEffect(() => { + if (site) { + fetchSiteData(site); + } else { + // TODO(Dennis): Redirect to default site ? + setError("No site parameter provided"); + return; } + }, [site]); - const data = { - ...snap.data(), - id: snap.id, - }; + async function fetchSiteData(siteId: string) { + try { + const docRef = doc(firestore, "tanam", siteId); + const snap = await getDoc(docRef); - return TanamSite.fromJson(data); + if (snap.exists()) { + const data = { + ...snap.data(), + id: snap.id, + }; + setSiteData(TanamSite.fromJson(data)); + } else { + setError("Site not found"); + } + } catch (err) { + console.error("Error fetching site data:", err); + setError("No permission to read site data or site not found"); + } } - return {getSite}; + return {siteData, error}; }