import { useState, useCallback, useEffect } from "react"; import { useStorage } from "@tensamin/storage/context"; import { Button } from "@tensamin/ui"; import { Checkbox } from "@tensamin/ui"; import { z } from "zod"; import { ErrorScreen } from "@tensamin/ui"; import { legalDocsSchema } from "@tensamin/shared/features/legal/schema"; import { log } from "@tensamin/shared/log"; import { Link } from "@tensamin/ui"; import { Label } from "@tensamin/ui"; import { CreateScreen } from "@tensamin/ui"; // Prevents the user from using Tensamin without accepting the privacy policy and terms of service. export default function Screen(props: { children: React.ReactNode }) { const { load, save } = useStorage(); const [error, setError] = useState(""); const [errorDescription, setErrorDescription] = useState(""); const [remoteDocs, setRemoteDocs] = useState< z.infer | undefined >(undefined); const [localDocs, setLocalDocs] = useState< z.infer | undefined >(undefined); const [loading, setLoading] = useState(true); const [acceptedPP, acceptPP] = useState(false); const [acceptedTOS, acceptTOS] = useState(false); const [hasContinued, setHasContinued] = useState(false); const [userId, setUserId] = useState(undefined); // Saves const handleContinueLegal = useCallback((): void => { const currentDocs = remoteDocs; if (!currentDocs) { return; } save("accepted_privacy_policy", true); save("accepted_terms_of_service", true); save("legal_docs", currentDocs); setHasContinued(true); }, [remoteDocs, save]); useEffect(() => { let active = true; void (async () => { try { const id = await load("user_id"); if (!active) { return; } setUserId(id); if (id === 0) { return; } const current = await fetch("https://legal.tensamin.net/api/current") .then((res) => res.json()) .catch((err) => { if (!active) { return undefined; } setError("Failed to load legal documents"); setErrorDescription( "An error occurred while fetching the legal documents from the server. Please try again later.", ); log(0, "Legal", "red", "Failed to fetch legal documents", err); return undefined; }); if (!active || current === undefined) { return; } const safeCurrent = legalDocsSchema.safeParse(current); if (!safeCurrent.success) { setError("Failed to load legal documents"); setErrorDescription( "The legal documents data received from the server is invalid. Please try again later.", ); log( 0, "Legal", "red", "Invalid legal documents data", safeCurrent.error, ); return; } setRemoteDocs(safeCurrent.data); const currentLocalDocs = await load("legal_docs"); setLocalDocs(currentLocalDocs); const [loadedAcceptedPP, loadedAcceptedTOS] = await Promise.all([ load("accepted_privacy_policy"), load("accepted_terms_of_service"), ]); if (!active) { return; } acceptPP( loadedAcceptedPP && !!currentLocalDocs && currentLocalDocs.pp.hash === safeCurrent.data.pp.hash, ); acceptTOS( loadedAcceptedTOS && !!currentLocalDocs && currentLocalDocs.tos.hash === safeCurrent.data.tos.hash, ); } finally { if (active) { setLoading(false); } } })(); return () => { active = false; }; }, [load]); if (error !== "" && errorDescription !== "") { return ; } if (loading || userId === undefined) { return null; } const docsMatch = localDocs !== undefined && remoteDocs !== undefined && localDocs.pp.hash === remoteDocs.pp.hash && localDocs.tos.hash === remoteDocs.tos.hash; if ( (acceptedPP && acceptedTOS && (docsMatch || hasContinued)) || userId === 0 ) { return <>{props.children}; } return (

Privacy Policy & ToS

{remoteDocs?.pp.version} / {remoteDocs?.tos.version}

); } // Components function ContinueButton({ onClick, disabled, }: { onClick: () => void; disabled?: boolean; }) { return (
); } function BigCheckbox({ id, label, checked, onChange, }: { id: string; label: string; checked: boolean; onChange: (checked: boolean) => void; }) { return (
); }